Вы находитесь на странице: 1из 704

Э. ТАНЕНБАУМ, А.

ВУДХАЛЛ

ОПЕРАЦИОННЫЕ
СИСТЕМЫ
Разработка и реализация

3-е издание

�nnTEP®
Москва· Санкт-Петербург· Нижний Новгород· Воронеж
Новосибирск Ростов-на-Дону Екатеринбург Самара
• • •

Киев Харьков Минск


• •

2007
ББК 3 2.973-018. 2
УДК 004.451
Т18

Таненбаум Э., Вудхалл А.


Т18 Операционные системы. Разработка и реализация (+CD). Классика CS. 3-е изд.
- СПб.: Питер, 2007. - 704 с.: ил.

ISBN 978-5-469-01403-4
5-469-01403-7
Третье издание классического труда Эндрю Таненбаума «Operating Systems: Design and
Implementation» - это единственный в своем роде учебник, в котором успешно сочетаются теория
и практика построения операционных систем. В книге подробно описываются процессы и
межпроцессное взаимодействие, семафоры, мониторы, передача сообщений, алгоритмы работы
планировщика, ввод/вывод, разрешение тупиковых ситуаций, драйверы устройств, алгоритмы
управления памятью, разработка файловых систем, а также затрагиваются вопросы
безопасности и защиты данных. В то же время обсуждается конкретная UNIХ-совместимая
операционная система MINIX и приводится ее исходный код (вы найдете его на компакт-диске).
Это позволяет не только изучать основополагающие принципы, но и наблюдать их применение
в реальных операционных системах.

ББК 3 2.973-018. 2
УДК 004.451

Права на издание получены по соглашению с Реагsоп Educatioп lпс


Все права защищены Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного
разрешения владепьцев авторских прав.

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

© 2006, 1997, 1987 Ьу Pearson Education, lnc.


ISBN О-1З-О-1З-142938-8 (англ.) © Перевод на русский язык ООО «П итер Пресс», 2007
ISBN 978-5-469-01403-4 © Издание на русском языке, оформление ООО «П итер Пресс», 2007
К р ат ко е с оде рж а н и е

Об авторах . 11
П редисловие 14
Глава 1 . Введение 18
Глава 2 . Процессы 78
Глава 3 . Ввод-вывод 252
Глава 4 . Управление памят ью 41 4
Глава 5 . Файловые системы 530
Глава 6 . Библиографи я . . . 669
Приложение А. Установка MI N IX 3 683
Приложение Б. С писок файлов MI N IX 3 на компакт-диске 69 1
Алфавитн ы й указател ь 694
Ком п акт-диск MINIX 3 . 703
Соде рж а н и е
Об авторах 11

Предисловие 14
От издателя перевода . . . . 17
Глава 1 . Введение . . 18
1 . 1 . Понятие операционной системы 21
1 . 1 . 1 . Операционная система как расширенная машина 21
1 . 1 .2. Операцион ная система как менеджер ресурсов . 22
1 .2. История развития операционных систем . . . . . . . . 24
1 .2. 1 . Первое поколение ( 1 945-1 955): электронные лампы и коммутационные панели 24
1 .2.2. Второе поколение ( 1955-1 965): транзисторы и системы пакетной обработки 25
1 .2.3. Третье поколение ( 1 965-1 980): интегральные схемы и многозадачность 27
1 .2.4. Четвертое поколение (с 1 980 года по наши дни): персональные компьютеры 33
1 .2.5. История MINIX 3 . 35
1 .3. Основные концепции 39
1 .3. 1 . Процессы 40
1 .3.2. Файлы . . . . 42
1 .3.3. Оболочка . . 46
1 .4. Системные вызовы 47
1 .4. 1 . Системные вызовы для управления процессами 50
1 .4.2. Системные вызовы для управления сигналами . 53
1 .4.3. Системные вызовы для управления файлами . . 55
1 .4.4. Системные вызовы для управления каталогами 60
1 .4.5. Системные вызовы для защиты . . . . . . . . 63
1 .4.6. Системные вызовы для управления временем 64
1 .5. Структура операционной систем ы . 65
1 .5. 1 . Монолитные системы . . . 65
1 .5.2. Многоуровневые системы . 67
1 .5.3. Виртуальные машины . 69
1 .5.4. Экзоядра . . . . . . . . 72
1 .5.5. Модель клиент-сервер . 72
1.6. Краткий обзор остальных глав . 74
Резюме . . . . . . . . 75
Вопросы и задания 75
Глава 2. Процессы . . 78
2.1 . Знакомство с процессами . 78
2.1.1 . Модель процессов 78
2. 1 .2. Создание процессов 80
2.1 .3. Завершение процессов 82
2. 1 .4. Иерархии процессов . 83
2. 1 .5. Состояния процессов . 84
2.1 .6. Реализация процессов 86
2. 1 .7. Программные потоки . 88
2.2. Взаимодействие между процессами 92
2.2. 1 . Гонки . . . . . . . . . . . . 93
Содержание 7

2.2.2. Критические секции . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94


2.2.3. Взаимное исключение с активным ожиданием . . . . . . . . . . . . . . . . . . 95
2.2.4. Примитивы взаимодействия между процессами 1 00
2.2.5. Семафоры 1 03
2.2.6. Мьютексы . . . . . . 1 05
2.2.7. Мониторы . . . . . . 1 06
2.2.8. Передача сообщений 1 10
2.3. Классические проблемы взаимодействия между процессами 1 13
2.3. 1 . Проблема обедающих философов . 1 13
2.3.2. Проблема читателей и писателей 116
2.4. Планирование . . . . . . . . . . . . . . 1 18
2.4. 1 . Основы планирования . . . . . . . 1 18
2.4.2. Планирование в системах пакетной обработки 1 24
2.4.3. Планирование в интерактивных системах . . . 1 27
2.4.4. Планирование в системах реального времени 1 34
2.4.5. Политика и механизм планирования . 1 35
2.4.6. Планирование программных потоков . 1 35
2.5. Процессы в MINIX 3 . . . . . . . . . . . . . 1 37
2.5. 1 . Внутренняя структура системы MINIX 3 1 38
2.5.2. Управление процессами в MINIX 3 . . . 141
2.5.3. Взаимодействие между процессами в MINIX 1 46
2.5.4. Планирование процессов в MINIX 3 1 48
2.6. Реализация процессов в MINIX 3 . . . . 151
2.6. 1 . Структура исходного кода MINIX 3 151
2.6.2. Компиляция и запуск MINIX 3 1 55
2.6.3. Общие заголовочные файлы . . . 1 57
2.6.4. Заголовочные файлы MINIX 3 . . 1 64
2.6.5. Структуры данных процессов и заголовочные файлы 173
2.6.6. Начальная загрузка MINIX 3 . . 1 84
2.6.7. Инициализация системы . . . . . . . . . . . . 1 88
2.6.8. Обработка прерываний в MINIX . . . . . . . . 1 95
2.6.9. Взаимодействие между процессами в MINIX 3 206
2.6. 1 О. Планирование процессов в MINIX 3 210
2.6. 1 1 . Аппаратная поддержка ядра 214
2.6. 1 2. Утилиты и библиотека ядра . 219
2.7. Системное задание в MINIX 3 . . . . 221
2.7. 1 . Обзор системного задания 223
2.7.2. Реализация системного задания 227
2.7.3. Реализация системной библиотеки 230
2.8. Таймерное задание в MINIX 3 . . . . . . 233
2.8. 1 . Аппаратное обеспечение часов . 234
2.8.2. Программное обеспечение часов 235
2.8.3. Обзор драйвера часов в MINIX 3 . 238
2.8.4. Реализация драйвера часов в MINIX 3 243
Резюме . . . . . . . . . . 245
Вопросы и задания . . . . . . . . . . . . . . 246
Глава 3. Ввод-вывод . 252
3. 1 . Аппаратное обеспечение ввода-вывода 252
3.1 . 1 . Устройства ввода-вывода . . . . . 253
3. 1 .2. Контроллеры устройств . . . . . . 254
3. 1 .3. Ввод-вывод с отображением на память 256
3.1 .4. Прерывания . . . . . . . . . . . . 257
3.1 .5. Прямой доступ к памяти . . . . . . .
. 258
3.2. Программное обеспечение ввода-выво да . . 261
3.2. 1 . Назначение программного обеспечения ввода-вывода 261
3.2.2. Обработчики прерываний 263
3.2.3. Драйверы устройств . . . . . . . . . . . . . . . . . . 263
8 Содержание

3.2.4. Независимое от устройств программное обеспечение ввода-вывода . . . . 265


3.2.5. Программное обеспечение ввода-вывода пользовательского пространства 268
3.3. Взаимная блокировка . . . . . . . . . 270
3.3. 1 . Ресурсы . . . . . . . . . . . . . . . . . . . . . . 270
3.3.2. Механизм взаимной блокировки . . . . . . . . . . 272
3.3.3. Алгоритм страуса . . . . . . . . . . . . . . . . . 276
3.3.4. Обнаружение и устранение взаимных блокировок 277
3.3.5. Предотвращение взаимных блокировок . . . . . . 278
3.3.6. Избежание взаимных блокировок . . . . . . . . . 280
3.4. Ввод-вывод в MINIX 3 . . . . . . . . . . . . . . . . . . 286
3.4. 1 . Обработчики прерываний и доступ к вводу-выводу в MINIX 3 286
3.4.2. Драйверы устройств в MINIX 3 . . . . . . . . . . . . . . . . 290
3.4.3. Аппаратно-независимый код ввода-вывода в MINIX . . . . . 294
3.4.4. Программы ввода-вывода пользовательского уровня в MINIX . 294
3.4.5. Взаимная блокировка в MINIX . . . . . . . . . . . . . 295
3.5. Блочные устройства в MINIX 3 . . . . . . . . . . . . . . . . 296
3.5. 1 . Обзор драйверов блочных устройств MINIX 3 . . . . . 296
3.5.2. Общие программы для драйверов блочных устройств 299
3.5.3. Библиотека поддержки драйверов . . . . . . . . . . . 303
3.6. Виртуальные диски . . . . . . . . . . . . . . . . . . . . . . 305
3.6. 1 . Аппаратное и программное обеспечение виртуального диска 306
3.6.2. Драйвер виртуального диска в MINIX 3 . . . . . . . 307
3.6.3. Реализация драйвера виртуального диска в MINIX 3 309
3.7. Реальные диски . . . . . . . . . . . . . . . . . 313
3.7. 1 . Аппаратное обеспечение диска . . . . . . 313
3.7.2. RAID . . . . . . . . . . . . . . . . . . . . 315
3.7.3. Программное обеспечение жестких дисков 316
3.7.4. Драйвер жестких дисков в MINIX 3 323
3.7.5. Реализация драйвера жесткого диска в MINIX 3 . 327
3.7.6. Дисковод гибких дисков . . . . . . . . 337
3.8. Терминалы . . . . . . . . . . . . . . . . . . 339
3.8. 1 . Аппаратное обеспечение терминала 340
3.8.2. Программное обеспечение терминала 345
3.8.3. Драйвер терминала в MINIX 3 . . . . . 354
3.8.4. Реализация аппаратно-независимого драйвера терминала 370
3.8.5. Реализация драйвера клавиатуры 390
3.8.6. Реализация драйвера экрана 398
Резюме . . . . . . . . . . . . . . . . 407
Вопросы и задания . . . . . . . . . . . 408
Глава 4. Управление памятью . . 41 4
4. 1 . Базовые механизмы управления памятью 41 5
4. 1 . 1 . Однозадачная система без подкачки и замещения страниц . 41 5
4. 1 .2. Многозадачная система с фиксированными разделами . 41 6
4. 1 .3. Переадресация и защита . . . . . . . . . . . . . . 41 8
4.2. Подкачка . . . . . . . . . . . . . . . . . . . . . . . . .
. 41 9
4.2. 1 . Управление памятью с помощью битовых карт . . . 422
4.2.2. Управление памятью с помощью связанных списков 423
4.3. Виртуальная память . . . . . . . . . . . . . . . 426
4.3. 1 . Замещение страниц . . . . . . . . . . . . 427
4.3.2. Таблицы страниц . . . . . . . . . . . . . 431
4.3.3. Буферы быстрого преобразования адресов . 435
4.3.4. Инвертированные таблицы страниц . . . . 438
4.4. Алгоритмы замещения страниц . . . . . . . . . 440
4.4. 1 . Оптимальный алгоритм заме щения страниц 441
4.4.2. Алгоритм NRU . . . . . . 442
4.4.3. Алгоритм FIFO . . . . . . 443
4.4.4. Алгоритм второго шанса . 443
4.4.5. Алгоритм часов . . . . . 444
Содержание 9

4.4.6. Алгоритм LRU . . . . . . . . . . . . . . . . 445


4.4.7. Программно е моделирование алгоритма LRU 446
4.5. Разработка систем замещения страниц . . . . . . . 448
4.5.1. Модель рабочего набора . . . . . . . . . . 449
4.5.2. Локальная и глобальная политики распределения памяти 451
4.5.3. Размер страницы . . . . . . . 454
4.5.4. Интерфейс виртуальной памяти 455
4.6. Сегментация . . . . . . . . . . . . . 456
4.6. 1 . Реализация сегментации 460
4.6.2. Сегментация с замещением страниц в lпtel Peпtium . 460
4.7. Знакомство с менеджером процессов в MINIX 3 466
4.7. 1 . Распределение памяти . . . . . . . . . . . . . . . 468
4.7.2. Обработка сообщений . . . . . . . . . . . . . . . . 471
4.7.3. Структуры данных и алгоритмы менеджера процессов 474
4.7.4. Системные вызовы fork, exit и wait . 478
4.7.5. Системный вызов ехес . 480
4.7.6. Системный вызов brk . . . 484
4.7.7. Обработка сигналов . . . . 484
4.7.8. Прочие системные вызовы 493
4.8. Управление памятью в MINIX . . . 494
4.8. 1 . Заголовочные файлы и структуры данных 494
4.8.2. Главная программа . . . . . . . . . . . 497
4.8.3. Реализация системных вызовов fork, exit и wait 503
4.8.4. Реализация системного вызова ехес 505
4.8.5. Реализация системного вызова brk . . 509
4.8.6. Реализация сигналов . . . . . . . . . 5 10
4.8.7. Реализация других системных вызовов 519
4.8.8. Утилиты управления памятью 522
Резюме . . . . . . . . . . . . . . . 524
Вопросы и задания . . . . . . . . . . 525
Глава 5. Файловые системы . 530
5.1 . Файлы . . . . . . . . . . 531
5.1 . 1 . Именование файлов 531
5.1 .2. Структура файла . 533
5.1 .3. Типы файлов . . . 535
5.1 .4. Доступ к файлам . 537
5.1 .5. Атрибуты файлов 538
5.1 .6. Операции с файлами 539
5.2. Каталоги . . . . . . . . . . 541
5.2. 1 . Простые каталоги . . 541
5.2.2. Иерархические системы ката логов 542
5.2.3. Пути . . . . . . . . . . . 543
5.2.4. Операции с каталогами . . . 546
5.3. Реализация файловой системы 547
5.3. 1 . Структура файловой системы 547
5.3.2. Реализация файлов . . . . . 549
5.3.3. Реализация каталогов . . . . 553
5.3.4. Организация дискового пространства 559
5.3.5. Надежность файловой системы . . . 563
5.3.6. Производительность файловой системы 571
5.3.7. Файловые системы с журнальной структурой 576
5.4. Безопасность . . . . . . . . . . . . . . . . . . 578
5.4. 1 . Безопасное окружение . . . . . . . . . . . 578
5.4.2. Общие виды атак на систему безопасности . 584
5.4.3. Принципы разработки механизмов безопасности 585
5.4.4. Аутентификация пользователей 586
5.5. Механизмы защиты 590
5.5. 1 . Домены защиты . . . . . . . . 590
1О Содержание

5.5.2. Списки управления доступом 593


5.5.3. Мандаты . . . . . . . . . . 595
5.5.4. Секретные каналы . . . . . 598
5.6. Обзор файловой системы MINIX 3 601
5.6. 1 . Сообщения . . . . . . . . . 602
5.6.2. Структура файловой системы 603
5.6.3. Битовые карты . 607
5.6.4. Индексные узлы 609
5.6.5. Кэш блоков 61 1
5.6.6. Каталоги и пути 613
5.6.7. Дескрипторы файлов 61 6
5.6.8. Блокировка файлов . 617
5.6.9. Каналы ввода-вывода и специальные файлы 618
5.6. 1 О. Пример системного вызова read . . . . . . 620
5.7. Реализация файловой системы MINIX 3 . . . . . .
. 621
5.7. 1 . Заголовочные файлы и глобальные структуры данных 621
5.7.2. Таблицы . . . . . . . . . . . . . 625
5.7.3. Главная программа . . . . . . . 634
5.7.4. Операции с отдельными файлами 638
5.7.5. Каталоги и пути . . . . . . . . . 648
5.7.6. Прочие вызовы файловой системы 652
5.7.7. Интерфейс устройств ввода-вывода . 654
5.7.8. Поддержка дополнительных системных вызовов 660
5.7.9. Утилиты файловой системы . 662
5.7. 10. Прочие компоненты MINIX 3 662
Резюме . . . . . . . . . . . . 663
Вопросы и задания . . . . . . . 664
Глава 6. Библиография . 669
6. 1 . Рекомендуемая литература 669
6. 1 . 1 . Вводные и общие публикации 669
6.1.2. Процессы . . . . . . 671
6. 1 .3. Ввод-вывод . . . . . 672
6. 1 .4. Управление памятью . 673
6. 1 .5. Файловые системы 674
6.2. Алфавитный список литературы 675
Приложение А. Установка MINIX 3 . 683
А. 1 . Подготовка к установке 683
А.2. Загрузка . . . . . . . . . . 685
А.3. Установка на жесткий диск . 686
А.4. Тестирование . . . . . . . 688
А.5. Использование симулятора 689
Приложение Б. Список файлов MINIX 3 на компакт-диске 691
Заголовочные файлы . 691
Драйверы 691
Ядро . . . . . . . . . 692
Файловая система . . 692
Менеджер процессов . 693
Алфавитный указатель 694

Компакт-диск MINIX 3 . 703


Системные требования . . . . . 703
Аппаратное обеспечение . . 703
Программное обеспечение . 703
Установка . . . . . 703
Поддержка продукта . . . . 703
Об а в т о р а х

Эндрю Таненбаум (Andгew S. Tanenbaurn) получил степень бакалавра в Масса­


чусетсском технологическом институте и степень доктора наук в Калифорнийском
университете в Беркли. Он является профессором кибернетики в университете
Врийе (Vгije) в Амстердаме, где возглавляет Группу компьютерных систем. Кроме
того, вплоть до 2005 года автор в течение 12 лет являлся деканом межуниверситет­
ской школы аспирантов по кибернетике и обработке изображений (Advanced School
fог Cornputing and Irnaging), занимающейся исследованиями в области современных
параллельных систем, распределенных систем и систем обработки изображений.
В прошлом автор занимался исследованиями компиляторов, операционных сис­
тем, компьютерных сетей и локальных распределенных систем. В настоящее время
его усилия в основном направлены на разработку систем безопасности, особенно
для операционных систем, компьютерных сетей и глобальных распределенных
систем. Результатом этих исследовательских проектов стали более 1 00 статей
в журналах и отчетах конференций. Э. Таненбаум является автором пяти книг.
Профессор Таненбаум написал множество программ. Под его руководством
разрабатывалась архитектура проекта Arnsteгdarn Cornpileг Кit - инструмента,
предназначенного для создания кросс-платформенных компиляторов. Кроме то­
го, он руководил созданием учебной операционной системы MINIX - упрощен­
ной версии системы UNIX, на базе которой была впоследствии разработана
система Linux. Вместе со своими аспирантами и программистами он участвовал
в разработке высокопроизводительной локальной распределенной операционной
системы Arnoeba. Также профессор является одним из разработчиков высоко­
производительной распределенной системы Globe, ориентированной на миллио­
ны пользователей. В настоящее время все эти программные продукты свободно
распространяются через Интернет.
Его аспиранты, многие из которых сами стали докторами наук, достигли боль­
ших успехов. Профессор Таненбаум очень гордится своими учениками. В этом
смысле он напоминает курицу-наседку.
Профессор Таненбаум является членом Ассоциации по вычислительной технике
(Association fог Cornputing Machineгy, АСМ), почетным членом Института ин­
женеров по электротехнике и электронике ( Institute of Electгical and Electгonics
Engineeгs, IEEE), членом Голландской королевской академии искусств и наук.
В 1 994 году ему была присуждена премия АСМ Карла В. Карлстрома ( Кагl
V. Kaгlstгorn) как выдающемуся преподавателю, в 1997 году - премия АСМ/
SIGCSE за выдающийся вклад в обучение кибернетике, в 2004 году - премия
12 Об авторах

Texty за лучший учебник. А в 2005 году Э. Таненбаум стал одним из пяти новых
профессоров Королевской Академии ( Royal Acaderny). Его домашняя страница
в Интернете расположена по адресу http://www . cs .vu . nl/-ast/.
Альберт Вудхалл (Albert S. Woodhull) получил степень бакалавра в Массачу­
сетсском технологическом университете и степень доктора в университете Ва­
шингтона. Поступив в Массачусетсский институт, чтобы стать электротехником,
он окончил его как биолог. Сам себя он называет �ученым, неплохо разбираю­
щимся в технике•. Более 20 лет он был преподавателем Школы естественных
наук Хэмпширского колледжа, Массачусетс, преподавая параллельно в несколь­
ких других колледжах и университетах. Как биолог, пользующийся электрон­
ным оборудованием, он начал работать с микрокомпьютерами, когда они стали
доступными. Его технические курсы для студентов развились в лекции, посвя­
щенные взаимодействию и программированию задач реального времени.
Доктор Вудхалл всегда испытывал большой интерес к преподаванию и к вопро­
сам влияния науки и технологии на производство. Перед поступлением в аспи­
рантуру он в течение двух лет преподавал естественные науки в Нигерии. Позже
он потратил несколько своих отпусков на обучение студентов вычислительной
технике в Никарагуа, в Universidad Nacional de Ingenieria и Universidad Nacional
Autonorna de Nicaragua.
В сферу его интересов входят компьютеры как электронные системы и взаимо­
действие компьютеров с другими электронными системами. Он особенно насла­
ждается преподаванием архитектуры вычислительной техники, операционных
систем и компьютерных коммуникаций, программирования на языке ассемблер.
А. Вудхалл также работал консультантом по разработке электронного оборудо­
вания и связанного с ним программного обеспечения, а также системным адми­
нистратором.
Помимо этого у него немало других, не академических интересов, включая спор­
тивные игры на открытом воздухе, радиолюбительство и чтение. Он любит путеше­
ствовать и изучать другие языки помимо родного английского. Вудхалл являет­
ся пользователем и горячим сторонником системы MINIX. Его страничка в Сети
управляется MINIX и располагается по адресу http://minix1 . ham psh ire.edu/asw/.
Сюзанне, Барбаре, Марвину, памяти моих дорогих п и Брэма.
Э. Таненбаум

Барбаре и Гордону.
А. Вудхалл
Пр еди с л о в и е

Большинство книг, посвященных операционным системам, в основном касаются


теории, а не практики. Та же, которую вы держите в руках, в этом смысле более
сбалансирована. В ней скрупулезно рассматриваются все теоретические основы,
в том числе процессы, взаимодействие между процессами, семафоры, мониторы,
передача сообщений, планирование, ввод-вывод, взаимные блокировки, драйве­
ры устройств, управление памятью, замещение страниц, разработка файловых
систем, безопасность и защита данных. В то же время обсуждается конкретная
UNIХ-совместимая операционная система MINIX и приводится копия ее исход­
ных кодов (на компакт-диске). Это позволяет не только изучать основополагаю­
щие принципы, но и видеть, как эти принципы применяются в реальных опера­
ционных системах.
Появившись в 1987 году, первая редакция этой книги в определенной степени
произвела революцию в понимании того, как нужно изучать операционные сис­
темы. До того большинство книг посвящалось только теоретической части. С по­
явлением MINIX во многих школах стали проводить лабораторные занятия, на
которых ученики могли �изнутри» увидеть, как работают операционные систе­
мы. Мы сочли эту тенденцию весьма желательной и надеемся, что она сохранит­
ся и в третьей редакции.
За первые десять лет операционная система MINIX претерпела множество из­
менений. Первоначальный код был рассчитан на IBM РС с процессором 8088
и 256 Кбайт памяти с двумя дисководами, но без жестких дисков. В основе
MINIX лежала система UNIX версии 7 . С течением времени система MINIX
развивалась в различных направлениях: появилась поддержка компьютеров с 32-
разрядным защищенным режимом, оснащенных оперативной памятью и жесткими
дисками большого объема. Кроме того, система теперь базируется не на UNIX
версии 7, а на международном стандарте P O S I X ( I E E E 1 003. 1 и I S O 9945 - 1 ) .
Добавлено множество новых возможностей - на наш взгляд, пожалуй, даже
слишком много. (Впрочем, некоторым и этого мало, что в конце концов и привело
к появлению Linux.) В дополнение, система MINIX была перенесена на множе­
ство других платформ, включая Macintosh, Amiga, Ataгi и SP ARC. Вторая редак­
ция данной книги, в которой рассматривалась именно эта версия MINIX, вышла
в свет в 1997 году и широко использовалась в университетах.
Операционная система MINIX сохраняет свою популярность, о чем свидетельст­
вует количество запросов по слову MINIX в поисковой системе Google.
Предисловие 15

В третью редакцию книги внесено множество изменений. Практически весь


материал переработан; кроме того, к нему добавлен значительный объем новой
информации. Главными нововведениями являются рассмотрение новой версии
операционной системы MINIX под названием MINIX 3. Хотя MINIX 3 является
продолжением MINIX 2, многие ключевые аспекты новой операционной систе­
мы принципиально иные.
К созданию MINIX 3 разработчиков подтолкнули громоздкость, низкое быстро­
действие и ненадежность существующих операционных систем. Операционные
системы выходят из строя значительно чаще, чем электронные устройства -
телеприемники, сотовые телефоны и DVD-плееры, а кроме того, имеют столь
огромное количество функций и параметров, что практически ни один человек
не способен эффективно управлять ими и освоить их полностью. Разумеется,
существенную роль в этом играют и разнообразные виды вредоносных программ
(вирусы, черви, шпионские программы, спам и др.), что приняло масштабы самой
настоящей эпидемии.
Многие перечисленные проблемы в значительной степени обусловлены фунда­
ментальным недостатком существующих операционных систем - отсутствием
модульности. Современная операционная система - это одна огромная испол­
няемая программа, скомпилированная из миллионов строк кода, написанного на
языках С и С++, и функционирующая в режиме ядра. Ошибка хотя бы в одной
строке может стать причиной выхода операционной системы из строя. Обеспе­
чить корректность всего кода невозможно: 70 % его объема составляют драйверы
устройств, написанные сторонними разработчиками, которые находятся вне по­
ля зрения специалистов, занятых поддержкой операционной системы.
Посредством MINIX 3 мы демонстрируем, что монолитная архитектура опера­
ционной системы не является единственно возможной. Ядро MINIX 3 включает
всего лишь 4000 строк исполняемого кода, в противовес •миллионным� ядрам
Windows, Linux, Мае OS Х и FreeBSD. Остальная часть операционной системы,
в том числе драйверы всех устройств (за исключением таймера), представляет
собой совокупность компактных модульных процессов, работающих в пользова­
тельском режиме. Деятельность каждого процесса четко ограничена; кроме того,
жестко регламентировано и взаимодействие между процессами.
Несмотря на то что работа над MINIX 3 еще далека от завершения, мы полага­
ем, что архитектура на основе совокупности пользовательских процессов с высо­
кой степенью инкапсуляции в будущем приведет к созданию более надежных
операционных систем. Система MINIX 3 предназначена главным образом для
небольших компьютеров, распространенных в странах •третьего мира�, и встраи­
ваемых систем, всегда резко ограниченных в ресурсах. Как бы то ни было, сту­
дентам значительно проще ознакомиться с принципами работы операционной
системы на примере модульной архитектуры, нежели изучать монолитную и гро­
моздкую структуру.
Данная книга снабжена компакт-диском. Вставьте его в дисковод, перезагрузите
компьютер, и через несколько секунд на экране появится экран входа MINIX 3.
Вы можете войти под именем root и опробовать систему, не устанавливая ее на
16 Предисловие

жесткий диск. Разумеется, установка на жесткий диск также предусмотрена. Под­


робные указания по установке вы найдете в приложении А.
Как уже отмечалось, над MINIX 3 ведется постоянная работа, результатом которой
является периодическое появление новых версий операционной системы. Вы
можете загрузить наиболее свежий образ установочного компакт-диска с офици­
ального веб-сайта www . minixЗ.org. На сайте вы также найдете большое количество
нового программного обеспечения, документации и новостей, касающихся раз­
работки MINIX 3. Дискуссиям и вопросам о MNIX 3 посвящена группа новостей
comp . os. minix. Пользователи, лишенные возможности работать с группами ново­
стей, могут следить за обсуждениями по адресу http ://groups. google. com/group/
comp.os . m inix.
Вы можете работать с MINIX 3 при помощи одного из симуляторов ПК, пере­
численных на главной странице официального веб-сайта MINIX. Использование
симулятора избавит вас от необходимости устанавливать операционную систему
на жесткий диск компьютера.
Преподаватели, применяющие данную книгу в качестве пособия в рамках уни­
верситетского курса, могут получить решения задач у местного представителя
издательства Pгentice Hall. Книге посвящен отдельный веб-сайт; вы можете по­
сетить его, указав оригинальное название книги ( Operating Systems Design and
Implementation) на странице http ://www . p renhall . com/tanenbaum .
В процессе работы над книгой нам посчастливилось сотрудничать с людьми,
оказавшими неоценимую помощь. В первую очередь, следует отметить вклад Бе­
на Граса ( Ben Gras) и Джоррит Хердер ( Jorrit Herder), написавших большинство
программ для новой редакции. Им пришлось работать в условиях жестко ограни­
ченных сроков и зачастую отвечать на электронные сообщения глубоко за пол­
ночь. Кроме того, они ознакомились с содержанием рукописи и внесли немало
полезных замечаний. Мы выражаем Бену и Джоррит свою глубокую призна­
тельность.
Кис Бот (Kees Bot) принял деятельное участие в работе над предыдущими ре­
дакциями книги, создав нам хороший задел для текущей работы. Он написал
объемные фрагменты кода для версий до 2.0.4, исправил ошибки и ответил на
многочисленные вопросы. Филипп Хомбург (Philip Homburg) написал большую
часть программного кода для работы с компьютерной сетью, а также сделал еще
много полезного, особенно в части комментариев к рукописи.
Следует упомянуть людей, принимавших участие в кодировании самых первых
версий MINIX и •поставивших� эту операционную систему •на ноги�. Число их
столь велико, а результаты их работы подверглись столь значительным измене­
ниям, что мы сочли разумным выразить им общую благодарность.
Некоторые из читавших рукопись книги предоставили нам свои замечания. Осо­
бую благодарность за помощь мы выражаем Джойко Бабику ( Goiko ВаЬiс ), Майк­
лу Кроули (Michael Crowley), Джозефу М. Кицца (Joseph М. Кizza), Сэму Кону
(Sam Kohn), Александру Манову (Alexander Manov) и Ду Цангу (Du Zhang).
От издателя перевода 17

Наконец, мы ничего не добились бы без наших семей. Сьюзан прошла через


все это уже шестнадцать раз, Барбара - пятнадцать, а Марвин - четырнадцать.
Я (Эндрю С. Таненбаум) всегда признателен за вашу любовь и поддержку.
Что же до Барбары Альберта, то это ее второе испытание. Без ее помощи, терпе­
ния и хорошего чувства юмора вообще ничего бы не получилось. Гордон стал
для нас терпеливым слушателем. Это счастье - иметь сына, которого интересу­
ют и заботят те же вещи, что восторгают меня (Альберта С. Вудхалла). Наконец,
первый день рождения внука Зайна совпал с выходом в свет операционной сис­
темы MINIX 3. Когда-нибудь он по достоинству оценит это.
Эндрю С. Таненбаум
Альберт С. ByдxШlll

От и здателя перевода
Ваши замечания, предложения и вопросы отправляйте по адресу электронной
почты comp@piter.com (издательство � Питер� , компьютерная редакция).
Мы будем рады узнать ваше мнение!
Все исходные тексты, приведенные в книге, вы можете найти по адресу http://
www. piter. com/download.
Подробную информацию о наших книгах вы найдете на веб-сайте издательства:
http://www. piter.com.
Глава 1
Вв еде ни е

Без программного обеспечения любой компьютер - просто бесполезная груда


железа. Именно благодаря программам компьютер может хранить, обрабатывать
и искать информацию, воспроизводить музыку и видео, отсылать сообщения
электронной почты, вести поиск в Интернете и решать множество других важных
задач, для которых он и предназначен. Программное обеспечение можно грубо
разбить на две большие группы: системные программы, управляющие работой
самого компьютера, и прикладные программы, предназначенные для решения поль­
зовательских задач. Самая главная системная программа - это операционная
система, она управляет всеми системными ресурсами и обеспечивает основу для
работы прикладных программ. Именно операционные системы являются пред­
метом рассмотрения в данной книге. В качестве примера, демонстрирующего
принципы архитектуры и их практическую реализацию, приведена ОС MINIX 3.
Современный компьютер состоит из одного или нескольких процессоров, опера­
тивной памяти, дисков, клавиатуры, монитора, принтеров, сетевых интерфейсов
и других устройств ввода-вывода, то есть является сложной системой. Написание
программ, которые отслеживают все компоненты, корректно используют их и при
этом оптимально работают, представляет собой крайне трудную задачу. Если бы
каждому программисту приходилось задумываться о том, как работают жесткие
диски, помнить о множестве нюансов, которые могут произойти при чтении блока
данных, то многие программы, скорее всего, вообще не были бы написаны.
Еще много лет назад стало очевидно, что нужно как-то оградить программистов
от тонкостей, связанных с аппаратным обеспечением. Постепенно был вырабо­
тан следующий путь: поверх аппаратуры работает дополнительная программная
прослойка, которая управляет всем оборудованием и предоставляет пользова­
телю интерфейс, или виртуальную машину, более простую для понимания и про­
граммирования, чем аппаратура. Операционная система и является этой про­
граммной прослойкой.
Место операционной системы в общей структуре компьютера показано на ри­
сунке 1 . 1 . Внизу находится аппаратное обеспечение, которое во многих случаях
само состоит из двух или более уровней (или слоев). Самый нижний уровень
содержит физические устройства, состоящие из интегральных микросхем, про­
водников, источников питания, электронно-лучевых трубок и т. п. То, как они
устроены и как работают, относится к сфере деятельности инженеров, специали­
стов по электронике.
Введение 19

Банковская Заказ
система авиабилетов Веб-браузер } Программы-приложения
Компиляторы Редакторы Интерпретаторы
команд } с"""'""ые -··ы

)
Операционная система
Машинный язык
Микроархитектура Оборудооа"'"·""""""" "
Физические устройства
Рис . 1 . 1. Компьютер состоит из аппаратного обеспечения, а также
системных и прикладных программ
Далее следует микроархитектурный уровень, на котором физические устройства
группируются в функциональные блоки. Как правило, на микроархитектурном
уровне находятся внутренние регистры ЦПУ (центральное процессорное устрой­
ство) и тракт данных, включающий арифметико-логическое устройство. На каждом
такте процессора данные извлекаются из регистров и обрабатываются арифме­
тико-логическим устройством (к примеру, участвуют в операции арифметического
или логического суммирования). Результат сохраняется в одном или нескольких
регистрах. В некоторых компьютерах функционирование тракта данных находит­
ся под управлением особой программы, называемой микропрограммой. В осталь­
ных случаях управление обеспечивают аппаратные схемы.
Тракт данных предназначен для выполнения наборов команд. Некоторые на­
боры могут быть выполнены в одном цикле тракта данных, другие же требуют
нескольких тактов. В распоряжении команд находятся различные аппаратные
средства, в том числе регистры. Аппаратное обеспечение и команды, доступ­
ные программисту на языке ассемблера, образуют архитектуру набора команд
(Instгuction Set Architecture, ISA). Зачастую данный уровень называют машин­
ным языком.
Обычно машинный язык содержит от 50 до 300 команд, служащих преимуще­
ственно для перемещения данных в пределах компьютера, выполнения ариф­
метических операций и сравнения величин. Управление устройствами на этом
уровне осуществляется путем загрузки определенных величин в специальные
регистры устройств. Например, диску можно дать команду чтения, записав в его
регистры адрес места на диске, адрес в основной памяти, число байтов для
чтения и направление действия (чтение или запись). На практике нужно переда­
вать больше параметров, а информация о статусе операции, возвращаемая дис­
ком, достаточно сложна. Кроме того, при программировании многих устройств
ввода-вывода (Input/Output, 1/0) очень важную роль играют временн Ьl:е соот­
ношения.
Основное предназначение операционной системы - скрыть все эти сложности
и предоставить программисту более удобную систему команд. Чтение блока из
20 Глава 1 . Введение

файла в этом случае представляется намного более простым действием, чем в слу­
чае, когда программисту приходится думать о перемещении головок диска, о за­
держках, связанных с их установкой в нужное место и т. д.
Поверх операционной системы на нашем рисунке расположены остальные сис­
темные программы. Здесь находятся интерпретатор команд (оболочка) , ком­
пиляторы, редакторы и т. д. Важно понимать, что подобные программы не яв­
ляются частью операционной системы, хотя обычно поставщики компьютеров
устанавливают их на машины. Это очень важное, хотя и тонкое, замечание. Под
операционной системой обычно понимается то программное обеспечение, кото­
рое запускается в режиме ядра или, как его еще называют, режиме супервизора.
Операционная система защищена от вмешательства пользователя с помощью
аппаратных средств (мы не рассматриваем в данный момент некоторые старые
микропроцессоры, которые вообще не имеют аппаратной защиты). Компилято­
ры и редакторы запускаются в полъзователъском режиме. Если пользователю не
нравится какой-либо компилятор, он при желании может написать собственный,
но ему не удастся написать собственный обработчик прерываний от системных
часов, являющийся частью операционной системы и обычно защищенный аппа­
ратно от попыток его модифицировать.
Подобная классификация имеет весьма размытые границы во встраиваемых сис­
темах, допускающих отсутствие ядра, и в интерпретируемых системах (к приме­
ру, в Jаvа-системах, где компоненты разделяются путем интерпретации, а не ап­
паратно ) . Тем не менее в традиционных компьютерах операционная система
является элементом, исполняемым в режиме ядра.
Во многих системах применяются программы, хотя и исполняемые в пользо­
вательском режиме, но призванные помогать операционной системе или ре­
шать привилегированные задачи. Распространенный пример - программа сме­
ны пароля пользователями. Она не является частью операционной системы и не
работает в режиме ядра, однако несет важную функцию и, очевидно, требует осо­
бой защиты.
В некоторых системах, включая MINIX 3, описанный подход реализован столь
утрированно, что компоненты, традиционно относимые к операционной системе
(к примеру, файловая система), функционируют в пользовательском режиме. Гра­
ница между системным и прикладным программным обеспечением размывается.
Очевидно, что компоненты, исполняемые в режиме ядра, относятся к операцион­
ной системе, однако многие 4Пользовательские� программы также несут систем­
ные функции либо, как минимум, тесно связаны с ними. К примеру, в MINIX 3
файловая система представляет собой не что иное, как большую С-программу,
исполняемую в пользовательском режиме.
Подведем итог вышесказанному: поверх системных программ выполняются при­
кладные программы. Обычно они покупаются пользователем (или пишутся им)
для решения собственных проблем - обработки текста, электронных таблиц,
технических расчетов или хранения информации в базе данных.
1 . 1 . Понятие операционной системы 21

1 . 1 . П онятие опера ц ионной системы


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

1 . 1 . 1 . Опера ционная система


как расш и ренная маш ина
Как было упомянуто ранее, архитектура (система команд, организация памяти,
ввод-вывод данных и структура шин) большинства компьютеров на уровне
машинного языка примитивна и: неудобна для работы с программами, особенно
в отношении ввода-вывода данных. Чтобы это утверждение не показалось го­
лословным, кратко рассмотрим пример того, как происходит ввод-вывод данных
с гибкого диска через совместимые микросхемы контроллера NEC PD765, ис­
пользуемые на большинстве персональных компьютеров с процессором lntel.
(В этой книге мы будем использовать термины 4гибкий диск• и 4дискета• как
синонимы.) Контроллер PD765 поддерживает 1 6 команд, каждая требует пере­
дачи от 1 до 9 байт в регистр устройства. Это - команды для чтения и записи
данных, перемещения головки диска и форматирования дорожек, а также для
инициализации, распознавания, установки в исходное положение и калибровки
контроллера и приводов.
Основными командами являются команды read (чтение) и wr i te (запись). Ка­
ждая из них требует 13 параметров, упакованных в 9 байт. Эти параметры опре­
деляют такие вещи, как адрес блока на диске, который нужно прочитать, количе­
ство секторов на дорожке, физический режим записи, расстановку промежутков
между секторами. Они же сообщают, что делать с метками адресов удаленных
данных. Если вы не можете сразу все осмыслить, не волнуйтесь - полностью это
могут понять лишь несколько посвященных. Когда выполнение операции завер­
шается, чип контроллера возвращает упакованные в 7 байт 23 параметра, отра­
жающие наличие и типы ошибок. Но и этого недостаточно; программист при ра­
боте с гибким диском должен также постоянно знать, включен двигатель или
нет. Если двигатель выключен, его следует включить (с длительным ожиданием
запуска) прежде, чем данные будут прочитаны или записаны. Двигатель не может
оставаться включенным слишком долго, так как гибкий диск изнашивается. Про­
граммист вынужден выбирать между длинными задержками во время загрузки
и изнашивающимися гибкими дисками (с вероятностью потери данных на них).
Даже если не вдаваться в подробности этого процесса, становится ясно, что
обыкновенный программист вряд ли захочет столкнуться с такими деталями при
работе с гибким диском (или жестким диском, работа с ним не менее сложна, но
22 Глава 1 . Введение

происходит совершенно иначе). Вместо этого программисту нужны простые вы­


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

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

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


В результате получилась бы полная неразбериха. Операционная система наводит
порядок в подобных ситуациях, буферизируя на диске все данные, предназначен­
ные для печати. В процессе работы программы операционная система сохраняет ее
выходные данные на диске во временном файле. Затем, по окончании работы этой
программы, система отправляет данные на принтер, в то время как другая програм­
ма может продолжать формировать свои выходные данные, не обращая внимания
на то, что они пока еще фактически не посылаются на печатающее устройство.
Когда с компьютером (или сетью) работают несколько пользователей, сложность
управления памятью, устройствами ввода-вывода, другими ресурсами и их за­
щиты значительно возрастает, поскольку пользователи могут обращаться к ним
в абсолютно непредсказуемом порядке. К тому же часто приходится распреде­
лять между пользователями не только оборудование, но и информацию (файлы,
базы данных и т. д.). С этой точки зрения основная задача операционной систе­
мы заключается в отслеживании того, кто и какой ресурс использует, в обработ­
ке запросов на ресурсы, в подсчете степени загрузки и разрешении проблем кон­
фликтующих запросов от различных программ и пользователей.
В рамках управления ресурсами различают два варианта их мультиплексирования
(совместного использования): временной и пространственный. При временном
мультиплексировании программы или пользователи задействуют ресурс по оче­
реди. Примером является исполнение нескольких программ в однопроцессор­
ной среде. Сначала операционная система предоставляет центральный процес­
сор одной программе, затем, по прошествии достаточного временного интерва­
ла, - другой и так далее, до тех пор пока очередь снова не дойдет до первой
программы. Определение алгоритма мультиплексирования (порядок и длитель­
ность доступа к ресурсу) является задачей операционной системы. Еще один при­
мер временного мультиплексирования - совместное использование принтера.
При постановке нескольких заданий печати в очередь необходимо решить, какое
задание будет выполнено следующим.
Второй вид мультиплексирования - пространственный. Компоненты, желающие
использовать ресурс, не выстраиваются в очередь; вместо этого каждый из них
получает долю ресурса в свое распоряжение. Пространство оперативной памяти,
как правило, расходуется одновременно несколькими программами. Это позво­
ляет им оставаться резидентными, например, для того, чтобы по очереди полу­
чать доступ к центральному процессору. При достаточном объеме оперативной
памяти компьютера эффективнее держать в ней сразу несколько программ, не­
жели предоставлять одной программе всю память целиком (особенно если про­
грамме требуется лишь небольшая ее доля). Разумеется, такой подход ставит во­
просы равноправия при выделении ресурса, защиты и т. д., и разрешение этих
вопросов возлагается на операционную систему. Еще одним примером ресур­
са с пространственным мультиплексированием является жесткий диск. Во мно­
гих компьютерах несколько пользователей одновременно хранят свои файлы на
единственном жестком диске. Типичная задача управления ресурсами, решаемая
операционной системой, - выделение областей на жестком диске и наблюдение
за теми, кто ими пользуется.
24 Глава 1 . Введение

1 . 2 . И стория развития
операционны х систем
История развития операционных систем насчитывает уже много лет. В следую­
щих разделах книги мы кратко рассмотрим некоторые основные моменты. Так
как операционные системы появились и развивались в процессе конструирова­
ния компьютеров, то эти события исторически тесно связаны. Поэтому чтобы
представить, как выглядели операционные системы, мы обсудим следующие друг
за другом поколения компьютеров. Такая схема взаимосвязи поколений опера­
ционных систем и компьютеров довольно груба, но она обеспечивает некоторую
структуру, без которой ничего не было бы понятно.
Первый настоящий цифровой компьютер был изобретен английским математи­
ком Чарльзом Бэббиджем (Charles Babbage, 1792- 187 1 ). Хотя большую часть жиз­
ни Бэббидж посвятил попыткам создания своей �аналитической машины� , он
так и не смог заставить ее работать должным образом. Это была чисто механиче­
ская машина, а технологии того времени не были достаточно развиты. Не стоит
и говорить, что аналитическая машина Бэббиджа не имела операционной системы.
Интересный исторический факт: Бэббидж понимал, что для аналитической ма­
шины ему необходимо программное обеспечение, поэтому он нанял молодую
женщину по имени Ада Лавлейс (Ada Lovelace), дочь знаменитого британского
поэта Лорда Байрона. Она и стала первым в мире программистом, а язык про­
граммирования Ada назван в ее честь.

1 . 2 . 1 . П ервое поколение ( 1 945- 1 9 55 ):


электрон ные лампы и ком мутационные панел и
После неудачных попыток Бэббиджа вплоть д о Второй мировой войны в конст­
руировании цифровых компьютеров не было практически никакого прогресса.
Примерно в середине 1 940-х Говард Айкен ( Howard Aiken) в Гарварде, Джон
фон Нейман (John von Neumann) в Принстонском институте, Дж. Преспер Эк­
керт (J. Presper Eckert), Вильям Мочли (William Mauchley) в Пенсильванском
университете, Конрад Цузе (Konrad Zuse) в Германии и многие другие продолжи­
ли работу в направлении создания вычислительных машин. На первых машинах
использовались механические реле, но они были очень медлительны, длитель­
ность такта составляла несколько секунд. Позже реле заменили электронными
лампами. Машины получались громоздкими, занимающими целые комнаты, с де­
сятками тысяч электронных ламп, но все равно они были в миллионы раз мед­
леннее, чем даже самый дешевый современный персональный компьютер.
В те времена каждую машину и разрабатывала, и строила, и программировала,
и эксплуатировала, и поддерживала в рабочем состоянии одна команда. Все
программирование выполнялось на абсолютном машинном языке, управление
основными функциями машины осуществлялось просто путем соединения ком­
мутационных панелей проводами. Тогда еще не были известны языки програм-
1 . 2. История развития о п ерацион н ых систем 25

мирования (даже ассемблера не было). Об операционных системах никто и не


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

1 . 2 . 2 . Второе поколение ( 1 955- 1 96 5 ) :


транзисторы и системы пакетной обработки
В середине 50-х изобретение и применение транзисторов радикально изменило
всю картину. Компьютеры стали достаточно надежными, машины с высокой ве­
роятностью могли работать довольно долго, выполняя при этом полезные функ­
ции. Впервые сложилось четкое разделение между проектировщиками, сборщи­
кам.11 , опера.торами, программистами и обслуживающим персоналом.
Машины, теперь называемые мзйнфреймами, располагались в специальных комна­
тах с кондиционированным воздухом, где ими управлял целый штат профессио­
нальных операторов. Только большие корпорации, правительственные учрежде­
ния или университеты могли позволить себе технику, цена которой исчислялась
миллионами долларов. Чтобы выполнить задание (то есть программу или комплект
программ), программист сначала должен был записать его на бумаге (на языке
FORTRAN или на ассемблере), а затем перенести на перфокарты. После этого
требовалось принести колоду перфокарт в комнату ввода данных, передать од­
ному из операторов и идти пить кофе в ожидании, когда будет готов результат.
Когда компьютер заканчивал выполнение какого-либо из текущих заданий, опе­
ратор подходил к принтеру, отрывал лист с полученными данными и относил
его в комнату для распечаток, где программист позже мог его забрать. Затем опе­
ратор брал одну из колод перфокарт, принесенных из комнаты ввода данных,
и помещал в механизм считывания. Если в процессе расчетов был необходим
компилятор языка FORTRAN, то оператору приходилось брать его из картотечного
шкафа и загружать в машину отдельно. Из-за одного только хождения операторов
по машинному залу впустую терялась масса драгоценного компьютерного времени.
Если учитывать высокую стоимость оборудования, не удивительно, что люди до­
вольно скоро занялись поиском оптимизации использования машинного времени.
Общепринятым решением стала система пакетной обработки. Первоначально
замысел состоял в том, чтобы собрать все задания (колоды перфокарт) в комнате
входных данных и затем переписать их на магнитную ленту, используя неболь­
шой и (относительно) недорогой компьютер, например IBM 140 1 , который был
26 Глава 1 . Введение

очень хорош для считывания карт, копирования лент и печати выходных дан­
ных, но не подходил для числовых вычислений.
Другие, более дорогостоящие машины, такие как IBM 7094, использовались для
настоящих вычислений (рис. 1 .2).

Устройство
записывания Входная Системная
лента Выходная
Устройство магнитных лент лента

1111111111111111111111 1111111111111111111111
7094 1401

а б в г д е

Ранняя система пакетной обработки: программист приносит карты для IBM 1401 ;
Рис. 1 . 2 . а -

б- IBM 1401 записывает пакет заданий на магнитную ленту; в оператор приносит входные
-

данные на ленте к IBM 7094; IBM 7094 выполняет вычисления; д оператор переносит
г - -

ленту с выходными данными на IBM 1 401 ; е IBM 1 401 печатает выходные данные
-

Примерно после часа, затрачиваемого на сбор пакета заданий, лента перематыва­


лась и ее относили в машинную комнату, где устанавливали на лентопротяжном
устройстве. Затем оператор загружал специальную программу (прообраз се­
годняшней операционной системы), которая считывала первое задание с ленты
и запускала его. Выходные данные записывались на вторую ленту вместо того,
чтобы идти на печать. Завершив очередное задание, операционная система авто­
матически считывала с ленты следующее и начинала обрабатывать его. После
обработки всего пакета оператор снимал ленты с входной и выходной инфор­
мацией, ставил новую ленту со следующим заданием, а готовые данные поме­
щал на IBM 140 1 для печати в автономном режиме (то есть без связи с главным
компьютером).
Структура типичного входного задания показана на рис. 1 .3. Оно начиналось
с карты $ЗАДАНИЕ, на которой указывалось максимальное время выполне­
ния задания в минутах, загружаемый учетный номер и имя программиста. Затем
поступала карта $FORTRAN, дающая операционной системе указание загру­
зить компилятор языка FORTRAN с системной магнитной ленты. Эта карта
следовала за программой, которую нужно было компилировать, а после нее шла
карта $ЗАГРУЗИТЬ, указывающая операционной системе загрузить только что
скомпилированную объектную программу. ( Скомпилированные программы час­
то записывались на временных лентах, данные с которых могли стираться сразу
после использования, и их загрузка должна была выполняться явно.) Следом
шла карта $ЗАПУСТИТЬ с данными, дающая операционной системе команду
выполнять программу. Наконец, карта завершения $КОНЕЦ отмечала конец за­
дания. Эти примитивные управляющие перфокарты были предшественниками
современных языков управления и интерпретаторов команд.
1 . 2. История развития о п ерационных систем 27

/ $КОНЕЦ
/ /

/
/
Данные для программы /
/

/ /
, ,

1
/$ЗАПУСТИТЬ -

/$ЗАГРУЗИТЬ /
/ /

,
/
/
Программа на Фортране /
/
/

/ / ,___ /
1 -

/$FORTRAN f--

/
/
/
$ЗАДАНИЕ, 1 0,6610802, Марвин Таненбаум -
/
/

,____

Рис. 1 . 3 . Структура типичного FМS-задания


Большие компьютеры второго поколения использовались главным образом для
научных и технических вычислений, таких как решение дифференциальных
уравнений в частных производных, часто встречающихся в физике и инженер­
ных расчетах. В основном на них программировали на языке FORTRAN и ас­
семблере, а типичными операционными системами были FMS (Fortran Monitor
System) и IBSYS (операционная система, созданная корпорацией IBM для ком­
пьютера IВМ 7094).

1 . 2 . З . Третье поколение ( 1 965-1 980) :


интеграл ьные схемы и м ногозадачность
К началу 60-х годов большинство производителей выпускало две полностью не­
совместимые линейки компьютеров. С одной стороны, существовали большие
компьютеры с пословной обработкой текста типа IBM 7094, использовавшиеся для
числовых вычислений в науке и технике. С другой стороны, выпускались коммер­
ческие компьютеры с посимвольной обработкой, такие как IBM 1 4 0 1 , широко
применявшиеся в банках и страховых компаниях для сортировки и печати данных.
Развитие, поддержка и маркетинг двух совершенно разных линеек компьютеров
для изготовителей были достаточно дорогим удовольствием. Кроме того, многим
покупателям изначально требовалась небольшая машина, однако позже ее воз­
можностей становилось недостаточно, и требовался более мощный компьютер,
который имел бы ту же архитектуру и работал бы с теми же самыми программа­
ми, но быстрее.
Корпорация I B M попыталась решить эти проблемы разом, выпустив линейку
машин IBM/360. Это была серия программно совместимых машин, начиная от
28 Глава 1 . Введение

компьютеров размером с IBM 1401 и заканчивая машинами, значительно более


мощными, чем IBM 7094. Они различались только ценой и производительностью
(максимальным объемом памяти, быстродействием процессора, количеством
устройств ввода-вывода и т. д.). Так как все машины имели одинаковую архитек­
туру и набор команд, программы, написанные для одного компьютера, могли
работать на всех других (по крайней мере, в теории). Кроме того, семейство ма­
шин 360 было разработано для поддержки как научных (то есть численных), так
и коммерческих вычислений. Одно семейство машин могло удовлетворить нужды
всех покупателей. В последующие годы, используя более современные техноло­
гии, корпорация IBM выпустила компьютеры, совместимые с 360, эти машины
известны под номерами 370, 4300, 3080, 3090 и Z.
Семейство машин 360 стало первой основной линейкой компьютеров, на кото­
рой использовались малые интегральные схемы, дававшие преимущество в цене
и качестве по сравнению с машинами второго поколения, созданными на базе от­
дельных транзисторов. Корпорация IBM добилась мгновенного успеха, а идею
семейства совместимых компьютеров скоро приняли и все остальные основные
производители. В компьютерных центрах до сих пор можно встретить потомков
этих машин. Они еще используются для управления огромными базами дан­
ных (например, для систем бронирования и продажи билетов на авиалиниях)
или как серверы узлов Интернета, которые должны обрабатывать тысячи за­
просов в секунду.
Основное преимущество � одного семейства» оказалось одновременно и вели­
чайшей его слабостью. По замыслу его создателей все программное обеспечение,
включая операционную систему 05/360, должно было одинаково хорошо рабо­
тать на всех моделях компьютеров: и в небольших системах, которые часто заме­
няли машины 1401 и применялись для копирования перфокарт на магнитные
ленты, и на огромных системах, заменяющих машины 7094 и использовавшихся
для расчета прогноза погоды и других сложных вычислений. Кроме того, пред­
полагалось, что одну операционную систему можно будет использовать как с не­
сколькими внешними устройствами, так и с большим их количеством; а также
как в коммерческих, так и в научных областях. Но самым важным было, чтобы
это семейство машин давало результаты независимо от того, кто и как его ис­
пользует.
Однако ни IBM, ни кому-либо другому пока не удалось написать программного
обеспечения, удовлетворяющего всем этим противоречивым требованиям. В ре­
зультате появилась огромная и необычайно сложная операционная система, при­
мерно на два или три порядка превышающая по сложности FMS. Она состояла
из миллионов строк, написанных на ассемблере тысячами программистов, содер­
жала тысячи и тысячи ошибок, что повлекло за собой непрерывный поток новых
версий, в которых устранялась часть ошибок, но вместо них появлялись новые,
так что общее их число, вероятно, оставалось постоянным.
Один из разработчиков OS/360, Фред Бруке (Fred Brooks), впоследствии написал
остроумную и язвительную книгу с описанием своего опыта работы с OS/360
[ 14 ] . Мы не можем здесь дать полную оценку этой книги, но достаточно будет
1 . 2. История развития о п ерацион н ых систем 29

сказать, что на ее обложке изображено стадо доисторических животных, увяз­


ших в яме с дегтем. Обложка книги [ 1 07] демонстрирует похожую точку зрения
на операционные системы, бывшие динозаврами в мире компьютеров.
Несмотря на свои огромные размеры и недостатки, система OS/360 и подобные
ей операционные системы третьего поколения, созданные другими произво­
дителями компьютеров, на самом деле достаточно неплохо удовлетворяли тре­
бованиям большинства клиентов. Они даже сделали популярными несколько
ключевых технических приемов, не поддерживаемых в операционных системах
второго поколения. Самым важным достижением явилась мноzозадачностъ. На
компьютере IBM 7094, когда текущая работа приостанавливалась в ожидании
операций ввода-вывода с магнитной ленты или других устройств, центральный
процессор просто бездействовал до окончания операции ввода-вывода. В слож­
ных научных вычислениях и при ограниченных возможностях процессора уст­
ройства ввода-вывода задействовались довольно редко, так что это потраченное
впустую время не играло существенной роли. Но при коммерческой обработке
данных время ожидания устройства ввода-вывода могло занимать 80 или 90 %
всего рабочего времени, поэтому необходимо было что-нибудь сделать во избе­
жание длительного простоя весьма дорогостоящего процессора.
Решение этой проблемы заключалось в разбиении памяти на несколько частей,
называемых разделами, каждому из которых давалось отдельное задание, как по­
казано на рис. 1 .4. Пока одно задание ожидало завершения работы устройства
ввода-вывода, другое могло использовать центральный процессор. Если в опе­
ративной памяти содержалось достаточное количество заданий, центральный
процессор мог быть загружен почти на все 100 % по времени. Множество одно­
временно хранящихся в памяти заданий требовало наличия специального обору­
дования для защиты каждого задания от возможного любопытства и ущерба со
стороны остальных заданий. Машина 360 и другие системы третьего поколения
были снабжены подобными аппаратными средствами.

Задание З
Задание 2
Разделы
Задание 1 памяти
Операционная
система
Рис. 1 . 4 . Многозадачная система с тремя заданиями в памяти

Другим важным достоинством операционных систем третьего поколения стала


способность считывать задание с перфокарт на диск по мере того, как их прино­
сили в машинный зал. Всякий раз, когда текущее задание заканчивалось, опе­
рационная система могла загрузить новое задание с диска в освободившийся
раздел памяти и запустить его. Этот технический прием называется подкачкой
данных, или спулинzом (английское слово spooling произошло от аббревиатуры
30 Глава 1 . Введение

SPOOL, которая расшифровывается как Simultaneous Peripheral Operation On


Line - совместные периферийные операции в режиме подключения), и его также
используют для выдачи полученных данных. С появлением механизма подкачки
машины 140 1 стали более не нужными, а многократные перемещения магнитных
лент сошли �на нет�.
Хотя операционные системы третьего поколения вполне подходили для слож­
ных научных вычислений и справлялись с крупными коммерческими задачами,
они все еще, по существу, представляли собой разновидности систем пакетной
обработки. Многие программисты тосковали по первому поколению машин, ко­
гда они могли распоряжаться всей машиной в течение нескольких часов и имели
возможность быстро отлаживать свои программы. В системах третьего поколе­
ния временной промежуток между передачей задания и возвращением результа­
тов часто составлял несколько часов, так что одна лишняя запятая могла стать
причиной сбоя при компиляции, и получалось, что программист тратил впустую
половину дня.
Желание сократить время ожидания ответа привело к разработке системы раз­
деления времени, варианту многозадачной системы, в которой у каждого пользо­
вателя есть свой диалоговый терминал. Если двадцать пользователей зарегист­
рированы в системе, работающей в режиме разделения времени, и семнадцать из
них думают, беседуют или пьют кофе, то центральный процессор по очереди
предоставляется трем пользователям, желающим работать на машине. Так как
люди, отлаживая программы, обычно выполняют короткие команды (например,
компилировать процедуру на пяти страницах) чаще, чем длинные (например,
упорядочить файл с миллионами записей), то компьютер может обеспечивать
быстрое интерактивное обслуживание нескольких пользователей. При этом он
может работать с большими пакетами в фоновом режиме, когда центральный
процессор не занят другими заданиями. Первая серьезная система разделения
времени под назв;шием CTSS ( CompatiЪle Time Sharing System - совместимая
система разделения времени) была разработана в Массачусетсском технологиче­
ском институте (М.1.Т.) на специально переделанном компьютере IBM 7094 [23] .
Однако разделение времени не стало действительно популярным до тех пор, по­
ка среди машин третьего поколения не получили широкого распространения не­
обходимые технические средства защиты.
После успеха системы CTSS Массачусетсский технологический институт, иссле­
довательские лаборатории Bell Labs и корпорация General Electric (тогда - глав­
ный изготовитель компьютеров ) решили начать разработку � компьютерного
приложения� - машины, которая должна была поддерживать одновременную
работу сотен пользователей в режиме разделения времени. Образцом для новой
машины послужила система распределения электроэнергии. Когда вам нужна
электроэнергия, вы просто вставляете вилку в розетку и получаете энергии столь­
ко, сколько вам нужно. Проектировщики этой системы, известной как MUL ТICS
(MULTiplexed Information and Computing Service - мультиплексная информаци­
онная и вычислительная служба), представляли себе одну огромную вычислитель­
ную машину, воспользоваться которой мог каждый человек в районе Бостона.
1 . 2 . История развития о п ерацион н ых систем 31

Мысль о том, что машины, гораздо более мощные, чем их мэйнфрейм GE-645,
будут продаваться миллионами по цене тысяча долларов за штуку всего лишь
через тридцать лет, казалась чистейшей научной фантастикой, как если бы сего­
дня кто-либо вздумал проектировать сверхзвуковые трансатлантические подвод­
ные поезда.
Успех MULTICS не был полным. Предполагалось, что система сможет обслужи­
вать сотни пользователей, будучи лишь немногим мощнее персональных компь­
ютеров на базе Intel 80386 (хотя система MULТICS значительно превосходила
их в объеме ввода-вывода). Идея была не столь безумна, как кажется, поскольку
в то время люди умели писать компактные и эффективные программы (похоже,
впоследствии это умение было утрачено). Системе M ULTI CS не удалось поко­
рить мир в силу целого ряда причин, весьма важной из которых является ис­
пользование языка PL/I для ее создания. Компилятор PL/I появился с опозда­
нием на несколько лет и оказался практически нефункциональным. Кроме того,
для своего времени система M U LT I C S была чересчур амбициозной, подобно
аналитической машине Бэббиджа в XIX веке.
В итоге система MULTICS стала источником многих конструктивных идей для
компьютерных теоретиков, но превратить ее в серьезный продукт и добиться
коммерческого успеха оказалось намного труднее, чем ожидалось. Группа иссле­
довательских лабораторий Bell Labs выбыла из проекта, а компания General
Electric совсем оставила компьютерный бизнес. Однако Массачусетсский техно­
логический институт проявил упорство и со временем получил вполне работо­
способную систему. В конце концов, она была продана как коммерческое из­
делие компанией Honeywell, купившей компьютерный бизнес General Electric,
и установлена примерно в восьмидесяти больших компаниях и университетах по
всему миру. Несмотря на небольшой тираж системы MULTICS, ее пользователи
проявили исключительную лояльность к своему приобретению. Компании General
Motors, Ford и Национальное агентство безопасности США свернули системы
MULТICS лишь в конце 90-х, а последняя машина MULТICS, работавшая в Ми­
нистерстве обороны Канады, была снята с эксплуатации в октябре 2000 г. Не­
смотря на неудачу с точки зрения коммерции, система MULTICS значительно
повлияла на последующие операционные системы [24, 25, 28, 94, 1 0 1 ) . В Интерне­
те имеется веб-сайт, на котором представлена обширная информация о системе
MULTICS, ее разработчиках и пользователях (www . multicians.org).
Словосочетание �компьютерное приложение� вышло из употребления, однако
в последние годы его идея получила �вторую жизнь�. В простейшем случае компь­
ютеры, или рабочие станции (персональные компьютеры большой мощности),
расположенные в компании или классной комнате, посредством локальной сети
подключаются к файловому серверу, хранящему все программы и данные. При
такой топологии системный администратор устанавливает и обеспечивает защиту
единственного набора программ и данных. Администратору не нужно заботить­
ся об извлечении и сохранении локальных данных неисправного компьютера;
он может без проблем переустановить его программное обеспечение. В неодно­
родном окружении появляется дополнительный класс программ, называемых
32 Глава 1 . Введение

промежуточным, или связующим, проzраммным обеспечением и заполняющих про­


бел между локальными пользователями и файлами, программами и базами дан­
ных, расположенными на удаленных серверах. Благодаря связующим програм­
мам пользователи воспринимают сетевые персональные компьютеры и рабочие
станции как локальные. Связующие программы создают единый пользователь­
ский интерфейс в условиях неоднородности серверов, компьютеров и рабочих
станций. Хорошим примером является веб-браузер. Он отображает пользовате­
лю документы в едином виде, при этом текст документа может находиться на од­
ном сервере, графика - на другом, а таблица стилей, определяющая формат до­
кумента, - на третьем. Как правило, веб-интерфейс используется компаниями и
университетами для доступа к базам данных и запуска программ на компьютере,
расположенном в другом здании или даже в другом городе. Может показаться,
что связующие программы образуют операционную систему для распределенной
системы, однако, на самом деле, они вообще не являются операционной систе­
мой и их рассмотрение выходит за рамки темы этой книги. Более подробную
информацию о распределенных системах вы найдете в [ 1 1 7 ) .
Еще одним важным моментом развития третьего поколения машин был феноме­
нальный рост числа мини-компьютеров после выпуска машины PDP- 1 корпораци­
ей DEC в 1961 году. Компьютеры PDP- 1 обладали оперативной памятью, состоя­
щей всего лишь из 4 К 18-разрядных слов, но стоили они по 120 тысяч долларов
за штуку (это меньше 5 % цены IBM 7094) и поэтому расхватывались как горячие
пирожки. На некоторых видах нечисловой работы они работали почти с такой
же скоростью, как I B M 7094, что стало толчком к рождению новой индустрии.
За этой машиной последовала целая серия других машин семейства PDP (в отли­
чие от семейства IBM, полностью несовместимых), и как кульминация - PDP- 1 1 .
Кен Томпсон ( Ken Thompson), один из специалистов по компьютерам в Bell Labs,
работавший над проектом M U LT I C S , впоследствии нашел мини-компьютер
PDP-7, которым никто не пользовался, и решил написать усеченную однопользо­
вательскую версию системы MULTICS. Эта работа позже развилась в операцион­
ную систему UNIX, ставшую популярной в академическом мире, в правительст­
венных управлениях и во многих компаниях.
История развития UNIX уже многократно описывалась в самых различных кни­
гах (см" например, [ 1 03) ). По причине широкой доступности исходного кода
различные организации создавали собственные (несовместимые) версии, что при­
вело к хаосу. Были разработаны две главные версии UNIX: Systeт V корпорации
АТ&Т и BSD ( Beгkeley Softwaгe Distгibution) Калифорнийского университета
Беркли. Эти системы, в свою очередь, распадаются на отдельные разновидности,
среди которых в настоящее время известны FreeBSD, OpenBSD и NetBSD. Чтобы
можно бьmо писать программы, работающие в любой системе UNIX, Институт
инженеров по электротехнике и электронике (Institute of Electrical and Electronic
Engineers, IEEE) разработал стандарт системы UNIX, называемый POSIX, кото­
рый теперь поддерживают большинство версий UNIX. Стандарт POSIX опреде­
ляет минимальный интерфейс системного вызова, который должны поддержи­
вать совместимые с UNIX системы. Некоторые другие операционные системы
1 . 2. История развития о п ерационных систем 33

теперь тоже поддерживают интерфейс POSIX. Информация, необходимая для


написания программ в стандарте POSIX, вполне доступна [64, 79] . Далее в этой
книге под именем UNI X мы будем понимать все Р О S I Х-совместимые опера­
ционные системы, за исключением особо оговоренных случаев. Несмотря на
внутренние отличия, эти системы следуют стандарту POSIX и весьма схожи
друг с другом с точки зрения программиста.

1 2 4 Ч етвертое поколение (с 1 980 года


. . .

по наш и дни) : персонал ьные ком п ьютеры


Следующий период эволюции операционных систем связан с появлением боль­
ших интегральных схем (Large Scale Integration, LSI) - кремниевых микросхем,
содержащих тысячи транзисторов на одном квадратном сантиметре. Это поколе­
ние стало поколением персональных компьютеров на базе микропроцессоров .
С точки зрения архитектуры персональные компьютеры (первоначально назы­
ваемые микрокомпьютерами) были во многом похожи на мини-компьютеры
класса PDP- 1 1 , но, конечно, отличались по цене. Если появление мини-компью­
теров позволило отделам компаний и факультетам университетов иметь собст­
венный компьютер, то с появлением микропроцессоров каждый человек полу­
чил возможность купить собственный персональный компьютер.
Было создано несколько семейств микрокомпьютеров. В 197 4 году фирма Intel
представила первый 8-разрядный микропроцессор общего назначения 8080. Ряд
компаний производил полноценные компьютерные системы, оснащенные про­
цессором 8080 (или совместимым Zilog Z80) и операционной системой СР/М
( Control Program for Microcomputers - программа управления микрокомпью­
терами) компании Digital Research. Такие системы получили широкое распро­
странение. Для СР /М было написано множество программ, а сама операционная
система удерживала лидерство в мире персональных компьютеров на протяже­
нии приблизительно 5 лет.
Фирма Motorola также представила 8-разрядный микропроцессор - 6800. После
того как группа инженеров внесла ряд предложений по его усовершенствованию,
которые были отвергнуты, она выделилась в компанию MOS Technology и раз­
работала центральное процессорное устройство 6502. Этим устройством были
оснащены некоторые первые персональные компьютеры. Одна из их разновид­
ностей, Apple 1 1 , стала главным конкурентом СР /М на рынке домашних и об­
разовательных компьютеров. Тем не менее популярность СР/М была столь
высока, что многие владельцы Apple 11 приобретали сопроцессорные платы рас­
ширения Z-80 для получения возможности работать с системой СР/М (процес­
сор 6502 несовместим с этой ОС). Платы СР/М выпускала небольшая компания
Microsoft, заодно являвшаяся поставщиком интерпретаторов BASIC для ряда
микрокомпьютеров под управлением СР /М.
Следующее поколение микропроцессоров было 1 6-разрядным. Фирма Intel пред­
ложила процессор 8086, а в начале 1 980-х компанией I B M был выпущен пер­
сональный компьютер I B M РС на основе процессора Intel 8088 (внутренний
34 Глава 1 . Введение

процессор 8086 с внешним 8-разрядным трактом данных). Компания Microsoft


предложила IBM пакет, включавший собственный интерпретатор BASIC и опера­
ционную систему DOS (Disk Operating System - дисковая операционная система).
Система DOS была разработана другой компанией, однако компания Microsoft
приобрела продукт и заключила с автором договор об усовершенствовании опера­
ционной системы. В результате появилась операционная система MS-DOS (Micro­
soft DOS), быстро завоевавшая рынок I В М РС.
Операционные системы СР/М, MS- DOS и APPLE DOS имели интерфейс ко­
мандной строки, с помощью которого пользователи вводили команды с кла­
виатуры. Несколькими годами ранее сотрудник исследовательского института
Стэнфорда Дуг Энгельбарт ( Doug Elgelbart) изобрел zрафический интерфейс
пользователя ( Graphical User Interface, GU I ) , включавший окна, значки, меню
и мышь. Стив Джобс ( Steve Jobs) из компании Apple увидел возможность со­
здания системы с дружественным интерфейсом (для пользователей, не только
ничего не знающих о компьютерах, но и не желающих заниматься их изучени­
ем), и в начале 1 984 было объявлено о создании компьютера Apple Macintosh.
Компьютер был оснащен 1 6-разрядным центральным процессором Motorola
68000 и 64 Кбайт постоянной памяти ( Read Only Memory, ROM) для поддерж­
ки графического интерфейса. В последующие годы компьютер Macintosh был
усовершенствован: процессоры стали 32-разрядными, а затем фирма Apple пере­
шла к использованию процессоров I B M PowerPC с RISС-архитектурой, сначала
32-, позднее - 64-разрядной. В 200 1 году Apple кардинально изменила операци­
онную систему, выпустив Мае OS Х с новой версией графического интерфейса
Macintosh на основе Berkley UNIX. В 2005 году компания заявила о намерении
перейти к использованию процессоров Intel.
Чтобы составить конкуренцию Macintosh, компания Microsoft разработала опе­
рационную систему Windows. Изначально Windows представляла собой лишь
графическую среду, работающую поверх 1 6-разрядной ОС M S - D O S ; другими
словами, она являлась скорее оболочкой, нежели настоящей операционной сис­
темой. Что же касается текущих версий Windows, это - потомки ОС Windows
NT, написанной •с нуля� .
Другой значимый претендент на лидерство в мире персональных компьютеров -
операционная система UNIX и различные ее ответвления. Она доминирует на
рабочих станциях и других мощных компьютерах, таких как сетевые серверы.
Особенно распространена эта ОС среди высокопроизводительных систем на RISC­
пpoцeccopax. На компьютерах с процессором Pentium популярной альтернати­
вой Windows является ОС Linux, все чаще используемая студентами и корпора­
тивными клиентами (в данной книге термином • Pentium� мы будем обозначать
все семейство одноименных процессоров, включая маломощные Celeron, высо­
копроизводительные Xeon и совместимые микропроцессоры AMD).
Хотя многие пользователи UNIX, особенно опытные в программировании, пред­
почитают командную строку графическому интерфейсу, практически все систе­
мы UNIX поддерживают оконную среду Х Windows, разработанную М.1.Т. Эта
среда позволяет пользователю создавать, удалять, перемещать и изменять размеры
1 . 2. История развития о перационных систем 35

окон при помощи мыши. Зачастую существует возможность использования пол­


ноценного графического интерфейса пользователя на базе Х Window, к примеру,
интерфейса Motif. Это позволяет любителям графического режима придать
UNIX внешний вид, напоминающий Macintosh и Microsoft Windows.
С середины 80-х годов начали расти и развиваться сети персональных компьюте­
ров, управляемых сетевыми и распределенными операционными системами [ 1 17].
В сетевой операционной системе пользователи знают о существовании много­
численных компьютеров, могут регистрироваться на удаленных машинах и ко­
пировать файлы с одной машины на другую. Каждый компьютер работает под
управлением локальной операционной системы и имеет собственного локально­
го пользователя (или пользователей). Как правило, компьютеры работают неза­
висимо друг от друга.
Сетевые операционные системы мало отличаются от однопроцессорных опера­
ционных систем. Ясно, что они нуждаются в сетевом интерфейсном контроллере
и специальном низкоуровневом программном обеспечении, поддерживающем
работу контроллера, а также в программах, разрешающих пользователям удален­
ную регистрацию в системе и доступ к удаленным файлам. Но эти дополнения,
по сути, не изменяют структуру операционной системы.
Распределенная операционная система, напротив, представляется пользователям
традиционной однопроцессорной системой, хотя она и функционирует на мно­
жестве процессоров. При этом пользователи не должны беспокоиться о том, где
работают их программы или где расположены файлы; все это должно автомати­
чески и эффективно обрабатываться самой операционной системой.
Чтобы создать настоящую распределенную операционную систему, недостаточ­
но просто добавить несколько страниц кода к однопроцессорной операционной
системе, так как распределенные и централизованные системы имеют сущест­
венные различия. Распределенные системы, например, часто позволяют при­
кладным заданиям одновременно обрабатываться на нескольких процессорах,
поэтому требуется более сложный алгоритм загрузки процессоров для оптими­
зации распараллеливания.
Наличие задержек при передаче данных в сетях означает, что эти алгоритмы
должны справляться с неполной, устаревшей или даже неправильной информа­
цией. Эта ситуация радикально отличается от однопроцессорной системы, в ко­
торой операционная система обладает полной информацией относительно со­
стояния системы.

1 . 2 . 5 . История M I N IX 3
Во времена молодости UNI X (версии 6) ее исходные коды были широко дос­
тупны по лицензии АТ&Т и активно изучались. Джон Лайонс (John Lions) из
университета Нового Южного Уэльса в Австралии даже написал небольшую
брошюру, шаг за шагом описывающую работу UNIX [82]. С разрешения АТ&Т
эта брошюра использовалась во многих университетских курсах по операцион­
ным системам.
36 Глава 1 . Введение

С выходом версии 7 стало ясно, что система UNIX превратилась в дорогостоя­


щий коммерческий продукт, поэтому лицензия, под которой распространялась
версия 7, запрещала преподавание исходного кода на учебных курсах, чтобы не
подвергать риску его статус коммерческого секрета. Поэтому многие универси­
теты просто прекратили изучение UNIX, довольствуясь одной теорией.
К сожалению, изучение одной только теории формирует у студентов однобо­
кий взгляд на то, какой в действительности может быть операционная система.
В книгах и курсах, посвященных операционным системам, в подробностях рас­
сматриваются чисто теоретические вопросы, например алгоритмы планирова­
ния, которые на практике не столь важны. Действительно важные вещи, такие
как ввод-вывод и файловые системы, зачастую опускаются, так как им не посвя­
щено достаточно теории.
Чтобы исправить ситуацию, один из авторов этой книги (Э. Таненбаум) решил
написать собственную операционную систему, которая с точки зрения пользова­
теля совместима с UNIX, но внутри совершенно самостоятельна. Так как в этой
системе не используется ни строчки кода АТ & Т, она не попадает под действие
лицензионных ограничений и может свободно использоваться при обучении.
Таким образом, студенты могут <1:вскрывать� реальную операционную систему,
чтобы увидеть, как она устроена изнутри, точно так же, как студенты-медики
вскрывают лягушек. Название MINIX происходит от rnini-UNIX, так как эта
система достаточно мала, чтобы даже начинающий мог понять, как она работает.
У MINIX есть и еще одно преимущество перед UNIX. Она на десять лет моложе
UNIX, поэтому ее код в большей степени обладает модульной структурой. На­
пример, начиная с первой версии MINIX, файловая система и менеджер памяти
вообще не являются частью операционной системы, а работают как отдельные
пользовательские программы. В текущем выпуске ОС (MINIX 3) такая модуль­
ность распространилась и на драйверы устройств ввода-вывода - все они, за ис­
ключением драйвера часов, выполняются в пользовательском режиме. Другое от­
личие в том, что система UNIX создавалась, чтобы быть эффективной, а MINIX -
чтобы быть понятной (насколько может быть понятным текст любой программы
из тысяч страниц). Поэтому, например, в коде системы MINIX имеются множе­
ство комментариев.
ОС MINIX разрабатывалась в расчете на совместимость с UNIX версии 7. Эта
версия была выбрана за основу благодаря ее простоте и элегантности. Иногда го­
ворят, что версия 7 лучше не только по сравнению с предыдущими версиями, но
и по сравнению с последующими. С пришествием POSIX, развитие MINIX нача­
ло стремиться к новому стандарту, поддерживая в то же время обратную совмес­
тимость с существующими программами. Это - обычный для компьютерной ин­
дустрии путь развития, так как никакой производитель не захочет поставлять
систему, которой никто не может пользоваться. Рассматриваемая в этой книге
система MINIX версии 3 базируется на стандарте POSIX.
Как и UNIX, О С MINIX написана на языке программирования С, чтобы упро­
стить ее перенос на различные компьютеры. Первая реализация предназначалась
для IBM РС, а затем система была перенесена на целый ряд других платформ.
1 . 2. История развития операционных систем 37

Придерживаясь философии �меньше да лучше� , M INIX изначально не требо­


вала для работы жесткого диска, тем самым вписываясь в студенческий бюд­
жет (сейчас это может показаться удивительным, но в середине 80-х, когда ОС
MINIX впервые увидела свет, жесткие диски все еще были дорогостоящей дико­
винкой). Со временем и функциональность, и объем системы росли, и в итоге
потребовался жесткий диск. Но философия MINIX не была забыта, и для рабо­
ты вполне достаточно раздела объемом 200 Мбайт. В противоположность этому,
даже небольшая система Linux на сегодняшний день требует 500 Мбайт диско­
вого пространства, а для установки основных приложений необходимо несколь­
ко гигабайтов.
Для среднего пользователя, сидящего за IBM РС, MINIX мало отличается от
UNIX. Имеются стандартные программы, такие как c a t , grep, l s , rnake, выпол­
няющие те же действия, что и их аналоги в UNIX. Как и сама операционная сис­
тема, эти программы были полностью переписаны автором, студентами и неко­
торыми другими посвященными людьми, чтобы избежать использования кода,
являющегося собственностью АТ & Т или других компаний. В настоящее время
существует большое количество бесплатно распространяемых программ, и во
многих случаях их удалось перенести (рекомпилировать) на MINIX.
Развитие MINIX продолжалось в течение 1 0 лет. В результате в 1997 году по­
явилась система MINIX версии 2, а с ней - и вторая редакция этой книги, описы­
вавшая новую операционную систему. Изменения были значительными, но эво­
люционными, к примеру, от 1 6-разрядного процессора 8088 в реальном режиме
и гибких дисков был сделан переход к 32-разрядному процессору 386 в защи­
щенном режиме и использованию жесткого диска.
Неторопливая и систематичная работа продолжалась до 2004 года, когда Та­
ненбаум пришел к выводу о том, что программное обеспечение стало слишком
громоздким и утратило надежность. Он решил вдохнуть в MINIX новую жизнь
и совместно с программистами и студентами Университета Врие (Vrije Universiteit)
в Амстердаме создал операционную систему MINIX 3. Она получилась в результа­
те значительной переработки предыдущих версий: структура ядра была изменена,
его объем сокращен, а акцент сделан на модульности и надежности. Новая вер­
сия предназначена как для персональных компьютеров, так и для встроенных
систем, где компактность, модульность и надежность являются первостепенными
факторами. Несмотря на настойчивое желание некоторых разработчиков полно­
стью переименовать операционную систему, в конечном счете было решено на­
звать ее MINIX 3, поскольку имя MINIX уже получило достаточную извест­
ность. Аналогичным образом поступила и фирма Apple, оставляя свою прежнюю
операционную систему Мае OS 9 и заменяя ее вариантом Berkley Unix. Новая
ОС получила имя Мае OS Х, а не APPLIX или нечто подобное. Фундаменталь­
ные изменения имели место и в семействе Windows, тем не менее операционные
системы сохранили первоначальное имя.
Объем ядра MINIX 3 составляет менее 4000 строк исполняемого кода, в то время
как объем Windows, Linux, FreeBSD или любой другой операционной системы
составляет миллионы строк. Компактность ядра важна, поскольку ошибки в нем
38 Глава 1 . Введение

имеют значительно более деструктивный эффект, нежели ошибки в пользова­


тельских программах, а с ростом объема кода растет и число ошибок. В результате
скрупулезного исследования [6] было установлено, что число выявленных оши­
бок на 1 000 строк исполняемого программного кода составляет от 6 до 1 6. Сле­
дует ожидать, что фактическое число ошибок в коде гораздо выше, поскольку
исследователям известны лишь ошибки, которые удалось обнаружить. Согласно
другому исследованию [95 ] , даже после 12 выпусков программного обеспечения
в нем остается примерно 6 % файлов, содержащих ошибки, обнаруживаемые
впоследствии. Кроме того, в определенный момент уровень ошибок стабили­
зируется, а не продолжает асимптотически стремиться к нулю. Этот результат
подтверждается исследованием надежных версий Linux и OpenBSD с помощью
простого автоматического инструмента проверки. В ядре обнаруживаются сот­
ни ошибок, преимущественно в драйверах устройств [20, 4 2 ] . По этой причине
в MINIX 3 драйверы были исключены из ядра. В пользовательском режиме вре­
доносный эффект от содержащихся в них ошибок значительно меньше.
Операционная система MINIX 3 будет использоваться во всех примерах, пред­
ставленных в этой книге. Тем не менее большинство комментариев, касающихся
системных вызовов MINIX 3 (в противоположность комментариям о непосред­
ственном коде), актуально для других систем UNIX. Следует иметь эту ремарку
в виду при чтении текста.
Отступая от темы, можно сказать несколько слов о LINUX и связи LINUX
с MINIX. Вскоре после создания MINIX для обсуждения этой операционной
системы была сформирована группа новостей. За несколько недель на нее под­
писалось более сорока тысяч человек, и большинство из них хотели добавить
в систему множество новых возможностей, чтобы сделать ее лучше и больше (или
просто больше). Каждый день несколько сотен человек давали советы, предлагали
идеи и фрагменты кода. Создатель системы несколько лет успешно сопротивлял­
ся этому напору, чтобы система оставалась достаточно компактной и понятной
для студентов. Некоторые пользователи, недовольные MS- DOS, рассматривали
существующую альтернативу в виде MINIX (с исходным кодом) как побудитель­
ный мотив для приобретения персонального компьютера.
Одним из таких пользователей стал финский студент по имени Линус Торвальдс
(Linus Torvalds). Он установил MINIX на свой новый компьютер и тщательно
изучил исходный код. Торвальдс хотел иметь возможность читать группы ново­
стей (в частности - comp.os . m inix) не только в университете, но и у себя дома,
однако в MINIX отсутствовали необходимые для этого средства. Он написал недос­
тающую программу, но обнаружил, что нужный ему драйвер терминала также
отсутствует. Тогда он своими силами решил и эту проблему. Когда он столкнулся
с задачей загрузки и сохранения корреспонденции, он написал дисковый драй­
вер и создал файловую систему. В августе 1991 года в распоряжении Торвальдса
было собственное примитивное ядро операционной системы. 25 августа 1991 года
он обнародовал свои достижения в группе comp.os.minix. Другие люди заинтере­
совались его работой и стали принимать в ней участие. В результате 13 марта
1 994 года свет увидела операционная система LINUX версии 1 .0. С этой даты
и начинается отсчет существования LINUX.
1 .3 . Основные конце п ции 39

LINUX является ярким достижением движения omtqJыmozo исходноzо кода, в ста­


новлении которого свою роль сыграла и операционная система M INIX. Во
многих средах LINUX конкурирует с UNIX и Windows. Отчасти это обусловле­
но тем, что производительность персональных компьютеров, поддерживающих
LINUX, сравнима с производительностью специализированных RIS С-систем,
требуемых некоторыми реализациями UNIX. Другие программы с открытым
исходным кодом, в особенности веб-сервер Apache и база данных MySQL, удач­
но взаимодействуют с LINUX в коммерческих приложениях. LINUX, Apache,
MySQL, а также языки программирования Perl и Р Н Р с открытыми кодами
зачастую совместно используются на веб-серверах. Иногда их в совокупности
обозначают акронимом LAMP. Более подробную информацию об истории LINUX
и программного обеспечения с открытыми исходными кодами вы найдете в ли­
тературе [37, 89, 92].

1 . 3 . Основные кон ц еп ц и и
Интерфейс между операционной системой и пользовательскими программами
определяется набором «расширенных инструкций� , предоставляемых системой.
По традиции эти расширенные инструкции называют системными вызовами, хо­
тя сейчас для их реализации используются несколько разных способов. Чтобы
действительно понять, что может делать операционная система, нужно тщатель­
но изучить этот интерфейс. Поддерживаемые вызовы у разных операционных
систем могут значительно различаться (хотя скрывающиеся за ними концепции
оказываются схожими).
Таким образом, при описании основных концепций, относящихся к операци­
онным системам, нам пришлось делать выбор между размытыми обобщения­
ми ( «операционные системы поддерживают системные вызовы для чтения фай­
лов�) и спецификой конкретной системы ( « MINIX 3 поддерживает системный
вызов READ с тремя параметрами: один указывает, какой файл будет считы­
ваться, другой - куда поместить считанные данные, а третий задает количество
считываемых байтов� ) .
Мы выбрали второй подход. Он сложнее, но дает гораздо больше в плане по­
нимания того, как работают операционные системы. В пункте 1 .4 представлен
более подробный обзор основных системных вызовов, поддерживаемых UNIX
(включая различные версии BSD), LINUX и MINIX 3. Для простоты мы будем
рассматривать только M INIX 3, но в большинстве случаев соответствующие
вызовы UNIX и LINUX тоже основаны на стандарте POSIX. Однако перед рас­
смотрением реальных системных вызовов имеет смысл дать общий обзор MINIX 3,
чтобы почувствовать, что это за система. Этот обзор в равной степени применим
и к UNIX и LINUX.
Системные вызовы MINIX 3 можно грубо разделить на две категории: вызовы
для работы с процессами и вызовы для работы с файловой системой. Рассмот­
рим каждую из этих групп.
40 Глава 1 . Введение

1 . 3 . 1 . П роцессы
Ключевое понятие MINIX 3 и любой другой операционной системы процесс. -

Процессом, по существу, называют программу в момент ее выполнения. С каж­


дым процессом связывается его адресное пространство список адресов в па­
-

мяти от некоторого минимума (обычно нуля) до некоторого максимума, которые


процесс может прочесть и в которые он может писать. Адресное пространство
содержит саму программу, данные к ней и ее стек. Со всяким процессом связы­
вается некий набор регистров, включая счетчик команд, указатель стека и другие
аппаратные регистры, плюс вся остальная информация, необходимая для запус­
ка программы.
Мы более детально рассмотрим понятие процесса в главе 2, но сейчас для того,
чтобы интуитивно осознать, что это такое, вспомним о многозадачных системах.
Предположим, что операционная система периодически решает остановить ра­
боту одного процесса и запустить другой, потому что первый израсходовал отве­
денную для него часть рабочего времени центрального процессора в прошедшую
секунду.
Если процесс был приостановлен подобным образом, позже он должен быть за­
пущен заново из того же состояния, в каком его остановили. Следовательно, всю
относящуюся к процессу информацию нужно где-либо явно хранить на время
его приостановки. Например, процесс может одновременно открыть для чтения
несколько файлов. Связанный с каждым файлом указатель дает текущую пози­
цию (то есть номер байта или записи, которые будут прочитаны следующими).
При приостановке процесса все указатели нужно сохранить так, чтобы команда
чтения, выполненная после возобновления выполнения процесса, прочла пра­
вильные данные. Во многих операционных системах вся информация каждого
процесса, дополняющая содержимое его собственного адресного пространства,
хранится в таблице операционной системы. Эта таблица называется таблицей
процессов и представляет собой массив (или связанный список) структур, по од­
ной на каждый существующий в данный момент процесс.
Таким образом, приостановленный процесс состоит из собственного адресного
пространства, обычно называемого образом памяти (core image 1 ), и компонентов
таблицы процесса, содержащей, помимо других величин, значения его регистров.
Главными системными вызовами, управляющими процессами, являются вызовы,
связанные с созданием и завершением процессов. Рассмотрим типичный при­
мер. Процесс, называемый интерпретатором команд, или оболочкой (shell), чи­
тает команды с терминала. Пользователь только что напечатал команду, содер­
жащую запрос на компиляцию программы. После этого оболочка должна создать
новый процесс, который запустит компилятор. Когда процесс закончит компи­
ляцию, он выполнит системный вызов, завершающий его собственную работу.

1 Слово �core�. которое можно перевести как �сердечник� . напоминает об использовавшейся дав­
ным-давно памяти на магнитных сердечниках.
1 .3 . Основные концепции 41

В Windows и других операционных системах, оснащенных графическим интер­


фейсом пользователя, двойной щелчок мышью на значке, расположенном на
рабочем столе, запускает программу почти так же, как если бы вы ввели ее имя
в командной строке. Хотя мы и не будем уделять много внимания графическим
интерфейсам пользователя, они, на самом деле, представляют собой простые ин­
терпретаторы команд.
Если процесс может создавать несколько других процессов (называющихся
дочерними) , а эти процессы, в свою очередь, тоже могут создать дочерние про­
цессы, перед нами предстает дерево процессов (рис. 1 .5). Связанные процессы -
это процессы, которые объединены для решения некоторой задачи, и им нуж­
но часто передавать данные от одного к другому и синхронизировать свою дея­
тельность. Такое взаимодействие называется межпроцессным и будет обсуж­
даться в главе 2 .

Рис. 1 . 5 . Дерево процессов. Процесс А создал два дочерних процесса В и С.


Процесс В создал три дочерних процесса D, Е и F
Другие системные вызовы предназначаются для запросов о предоставлении
дополнительной памяти (или освобождении не использующейся памяти), ожи­
дании завершения дочерних процессов и оверлейном выполнении программы
вместе с другой.
Время от времени необходимо передавать информацию работающему процессу
так, чтобы он не простаивал в ожидании получения этой информации. Напри­
мер, процесс, связанный с другим процессом на удаленном компьютере, делает
это, посылая сообщения по сети. Чтобы предотвратить возможность потери со­
общения или ответа на него, отправитель может потребовать от собственной опе­
рационной системы уведомления, если по истечении определенного интервала
ожидания не будет получено подтверждение о получении сообщения. В этом
случае он сможет повторить отправку сообщения. После установки таймера про­
грамма продолжит выполнение другой работы.
Если по истечении определенного количества секунд ответа нет, операционная
система посылает процессу аварийный сигнал, который вызывает временную
остановку работы процесса независимо от того, что процесс делает в данный
момент; сохраняет его регистры в стеке и запускает специальную процедуру об­
работки сигнала (например, передающую повторно предположительно поте­
рянное сообщение). После завершения обработки сигнала работающий процесс
запускается заново в том состоянии, в котором он находился до сигнала. Сигна­
лы являются программными аналогами аппаратных прерываний и могут быть
42 Глава 1 . Введение

сгенерированы по различным причинам, а не только из-за истечения какого-ли­


бо интервала времени. Многие аппаратные прерывания (например, вызванные
выполнением недопустимой команды или использованием неправильного адре­
са) также преобразуются в сигналы, направляемые процессу, являющемуся ис­
точником ошибки.
Каждому пользователю, которому разрешено работать с системой MINIX 3, систем­
ный администратор присваивает идентификатор полъзователя (User IDentification,
UID). У каждого работающего процесса есть идентификатор пользователя, запус­
тившего процесс. Дочерний процесс получает тот же самый идентификатор поль­
зователя, что и его родитель. Пользователи могут являться членами групп, каждой
из которых присваивается идентификатор группы (Group IDentification, GID).
Пользователь с особым идентификатором пользователя, называемый в UNIX су­
перпользователем (superuser), имеет особые полномочия и может игнорировать
множество правил защиты. В больших системах только системный администра­
тор знает пароль, необходимый для того, чтобы стать суперпользователем. Одна­
ко множество обыкновенных пользователей (особенно студентов) тратят массу
времени и сил на поиски брешей в системе защиты, которые позволили бы им
стать суперпользователями без пароля.
Процессы, межпроцессное взаимодействие и сопутствующие вопросы рассмат­
риваются в главе 2.

1 . 3 . 2 . Ф айл ы
Другая обширная группа системных вызовов относится к файловой системе. Как
было замечено ранее, основной функцией операционной системы является скры­
тие особенностей устройства дисков и других устройств ввода-вывода и предо­
ставление пользователю понятной и удобной абстрактной модели независимых
от устройств файлов. Системные вызовы очевидно необходимы для создания,
удаления, чтения или записи файлов. Перед тем как прочитать файл, его нужно
разместить на диске и открыть, а после прочтения его нужно закрыть. Все эти
функции осуществляют системные вызовы.
Предоставляя место для хранения файлов, операционные системы используют
понятие каталога (directory) как средства объединения файлов в группы. На­
пример, студент может иметь по одному каталогу для каждого изучаемого им
курса (для программ, необходимых в рамках этого курса) , каталог для элек­
тронной почты и еще один - для своей домашней веб-страницы. Для создания
и удаления каталогов также необходимы системные вызовы. Они же обеспечива­
ют перемещение существующего файла в каталог и удаление файла из каталога.
Содержимое каталогов моrут составлять файлы или другие каталоги. Эта мо­
дель образует иерархию - файловую систему (рис. 1 .6).
Иерархии и процессов, и файлов организованы в виде деревьев, однако на этом
их сходство заканчивается. Иерархия процессов обычно не очень глубока (в ней
редко бывает больше трех уровней), тогда как файловая структура достаточно
часто имеет четыре, пять или даже больше уровней в глубину. Иерархия процес-
1 .3 . Основные конце п ции 43

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

Корневой каталог

Студенты Факультет

Профессор Профессор
Гр• Уайт

Курсы

CS1 01 t.! �О Ж
CS1 05

� �Файловая система факультета университета


Файлы

Рис. 1 .6.

Каждый файл в иерархии каталогов можно определить, задав его имя пути, на­
зываемое обычно полны.м именем файла. Путь начинается из вершины структуры
каталогов, называемой корневым каталогом. Такое абсолютное имя пути состо­
ит из списка каталогов, которые нужно пройти от корневого каталога к файлу,
с разделением отдельных компонентов косой чертой. На рис. 1 . 6 путь к файлу
C S 1 0 1 выглядит как / Fa cu l ty / Pro f . B rown / Cours e s / C S 1 0 1 . Первая косая
черта говорит о том, что этот путь - абсолютный, то есть начинается от корнево­
го каталога. В MS- DOS и Windows для разделения компонентов вместо символа
косой черты используется символ обратной косой черты ( \ ) . Тогда этот путь
будет выглядеть так: \ F acu l ty \ Pro f . Brown \ Cour s e s \ C S 1 0 1 . В нашей книге
для записи пути мы в основном будем использовать соглашения UNIX.
В каждый момент времени у каждого процесса есть текущий ра,бачий каталог, в ко­
тором ищутся имена путей, не начинающиеся с косой черты. Например, если на
рис. 1 . 6 каталог / F a c u l ty / Pro f . Brown является рабочим, то использование
44 Глава 1 . Введение

пути Cours e s / C S 1 0 1 даст тот же самый файл, что и показанный ранее абсолют­
ный путь. Процессы могут изменять свой рабочий каталог, используя системные
вызовы.
В операционной системе MINIX 3 каждому файлу и каталогу присваивается
1 1-разрядный двоичный код защиты. Код защиты включает три 3-разрядных поля:
одно - для владельца, одно - для прочих членов группы владельца (разбиение
пользователей на группы осуществляется системным администратором) и од­
но - для всех остальных пользователей. Два оставшихся бита мы рассмотрим
позднее. В каждом поле имеется бит доступа по чтению, бит доступа по записи и
бит доступа по исполнению. Эти три бита в совокупности называют rwх-бита­
ми 1 . К примеру, код защиты rwxr - x - - x означает, что владелец файла может
читать, записывать и запускать файл, другие члены группы владельца - только
читать и запускать файл (без права записи), и, наконец, все прочие пользовате­
ли - только исполнять файл (без права чтения и записи) . Для каталога, в от­
личие от файла, доступ по исполнению означает возможность поиска. Прочерк
указывает на отсутствие разрешения (значение соответствующего бита равно О).
Перед тем как прочесть или записать файл, его нужно открыть, в это же время
проверяется разрешение доступа. Если доступ разрешен, система возвращает не­
большое целое число, называемое дескриптором файла и используемое в после­
дующих операциях. Если доступ запрещен, то возвращается код ошибки ( - 1 ) .
Другое важное понятие в MINIX 3 - это смонтированная файловая система.
Почти все персональные компьютеры имеют один или несколько дисководов
для компакт-дисков, куда можно вставить и откуда можно вынуть диск. Что­
бы предоставить возможность общения со сменными носителями ( СD-дисками,
DVD-дисками, дискетами, Ziр-дисками), MINIX 3 позволяет присоединять фай­
ловую систему сменного диска к главному дереву. Рассмотрим ситуацию, пока­
занную на рис. 1 .7, а. Перед вызовом системной процедуры rnount корневая фай­
ловая система на жестком диске и вторая файловая система на компакт-диске
существуют раздельно и никак не связаны между собой.
Однако файлы на компакт-диске нельзя использовать, потому что для них не­
возможно определить путь. MINIX 3 не позволяет присоединять к началу пути
название диска или его номер, так как это привело бы к жесткой зависимости
от устройств, которой операционная система должна избегать. Вместо этого
системный вызов rnoun t позволяет присоединять файловую систему на гибком
диске к корневой файловой системе в том месте, где этого захочет программа.
На рис. 1 .7, б файловая система диска О установлена в каталог Ь, таким обра­
зом, обеспечен доступ к файлам по путям / Ы х / и / Ь / у . Если каталог Ь со­
держал какие-либо файлы, они будут недоступны, пока смонтирован гибкий
диск, так как теперь имя /Ь ссылается на корневой каталог диска О. ( Невоз­
можность доступа к этим файлам не так страшна, как кажется с первого взгля­
да: файловые системы почти всегда устанавливаются в пустые каталоги.) Если

1 Аббревиатура rwx образована тремя английскими словами: read - чтение, write - запись, execution -
исполнение - Примеч. пер .
1 .3 . Основные конце п ции 45

система содержит несколько жестких дисков, они все могут быть встроены в од­
но дерево таким же образом.

Корень Дискета Корень

N с{Ъ а б
Монтирование файловой системы: - перед монтированием файлы на диске О
Рис. 1 . 7 . а
недоступны; б - после монтирования они становятся частью общей файловой структуры
Еще одно важное понятие в MINIX 3 - это специальный файл. Специальные
файлы служат для того, чтобы устройства ввода-вывода выглядели как файлы.
При этом можно прочесть информацию из специальных файлов или записать ее
туда с помощью тех же самых системных вызовов, что используются для чтения
и записи файлов. Существует два вида специальных файлов: блочные специ­
альные файлы и си.м,вольные специальные файлы . Блочные специальные файлы
используются для моделирования устройств, состоящих из набора произволь­
но адресуемых блоков, таких как диски. Открывая блочный специальный файл
и читая, скажем, блок 4, программа может напрямую получить доступ к блоку 4
на устройстве без обращения к содержащейся на нем файловой системе. Таким
же образом символьные специальные файлы используются для моделирования
принтеров, модемов и других устройств, которые принимают или выдают поток
символов. По соглашению специальные файлы хранятся в каталоге / dev. На­
пример, файл / dev / lp может быть строковым принтером.
И последнее понятие, которое мы здесь обсудим, - это каналы (pipe), имеющие
отношение и к процессам, и к файлам. Канал (также иногда называемый трубой)
представляет собой псевдофайл, который можно использовать для связывания
двух процессов, как показано на рис. 1 .8. Если процессы А и В захотят пооб­
щаться с помощью канала, они должны установить его заранее. Когда процесс А
решает отправить данные процессу В, он пишет их в канал, как если бы это был
выходной файл. Процесс В может прочесть данные, читая их из канала, как если
бы он был файлом с входными данными. Таким образом, взаимодействие между
процессами в UNIX очень похоже на обычные чтение и запись файлов. Более того,
только сделав специальный системный вызов, процесс может обнаружить, что
выходной файл, в который он пишет данные, - это не реальный файл, а канал.

Процесс Процесс


Рис. 1 . 8 . Два процесса, соединенные каналом
46 Глава 1 . Введение

1 . 3 . 3 . Оболочка
Операционная система представляет собой программу, выполняющую систем­
ные вызовы. Редакторы, компиляторы, ассемблеры, компоновщики и командные
интерпретаторы не являются частью операционной системы, несмотря на их
большую важность и полезность. Поскольку есть риск запутаться в этих поняти­
ях, мы кратко рассмотрим только командный интерпретатор MINIX 3, называе­
мый оболочкой (shell). Хотя оболочка не входит в операционную систему, но во
всю пользуется многими функциями операционной системы и поэтому является
хорошим примером того, как могут применяться системные вызовы. Кроме это­
го, оболочка предоставляет основной интерфейс между пользователем, сидящим
за своим терминалом, и операционной системой, если, конечно, пользователь не
использует графический интерфейс. Существуют целый ряд оболочек, включая
c sh, ksh, z sh и bash. Все они поддерживают описываемую здесь функциональ­
ность исходной оболочки ( sh).
Когда какой-либо пользователь входит в систему, запускается оболочка. Стан­
дартным входным и выходным устройством для оболочки является терминал.
Оболочка начинает работу с печати приzлашения (prompt) - знака доллара,
говорящего пользователю, что оболочка ожидает ввода команды. После этого
пользователь может вводить команды, например:
da t e

В этом случае оболочка создает дочерний процесс и запускает программу da t e .


Пока дочерний процесс работает, оболочка ожидает его завершения. После за­
вершения дочернего процесса оболочка опять печатает приглашение и пытается
прочесть следующую введенную строку. Пользователь может перенаправить стан­
дартный вывод данных в файл:
da t e > f i l e

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


ные данные:
sort < f i l e l > f i l e 2

Эта команда предписывает программе сортировки считать данные из файла f i l e l


и вывести результат в файл f i l e 2 .
Выходные данные одной программы можно использовать в качестве входных
данных для другой, соединив их каналом, например:
cat f i l e l f i l e 2 f i l e З 1 s o r t > / dev / l p

Эта команда предписывает программе c a t объединить (concatenate) три файла


и послать выходные данные программе s o r t , которая расставит все строки в ал­
фавитном порядке. Результат работы программы s or t перенаправляется в файл
/ dev / lp, обычно обозначающий принтер.
Если пользователь вводит после команды знак &, оболочка не ждет окончания
выполнения команды. В этом случае она немедленно выводит новое приглаше­
ние. Например:
cat f i l e l f i l e 2 f i l e З 1 s o r t > / dev / l p &
1 . 4 . Системные вызовы 47

В результате выполнения этой команды сортировка запустится как фоновое


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

1 . 4 . Систем ные вы з овы


Вооружившись общим пониманием того, как MINIX 3 работает с процессами
и файлами, можно приступить к изучению интерфейса между операционной
системой и пользовательскими программами, то есть системных вызовов. Хотя
это обсуждение затрагивает конкретно стандарт POSIX (международный стан­
дарт 9945- 1 ), а следовательно, MINIX 3, UNIX и LINUX, большинство других
современных операционных систем поддерживает системные вызовы, выполняю­
щие те же самые функции, хотя детали могут различаться. Так как фактический
механизм обращения к системным функциям является в высокой степени ма­
шинно-зависимым и часто должен реализовываться на ассемблере, существуют
библиотеки процедур, делающие возможным обращение к системным процеду­
рам из программ на С и на других языках с тем же успехом.
Следует помнить о том, что любой однопроцессорный компьютер способен вы­
полнять лишь одну команду за раз. Если процесс выполняет программу в поль­
зовательском режиме и требует поддержки со стороны операционной системы
(к примеру, при чтении данных из файла), необходимо прерывание или систем­
ный вызов для того, чтобы передать операционной системе управление. Опера­
ционная система определяет запрошенное процессом действие по переданным
параметрам, выполняет системный вызов и возвращает управление команде,
следующей за вызовом. В этом смысле системный вызов похож на вызов проце­
дуры, однако в отличие от процедуры системный вызов осуществляет вход в яд­
ро или другие привилегированные компоненты операционной системы.
Для того чтобы прояснить механизм системных вызовов, кратко рассмотрим сис­
темный вызов re ad. Как упоминалось ранее, у него есть три параметра: первый
служит для задания файла, второй указывает на буфер, третий определяет ко­
личество байтов, которое нужно прочитать. Вызов из программы на С может
выглядеть так:
c ount = read ( f d , bu f f e r , nby t e s ) ;

Системный вызов (и библиотечная процедура) возвращает количество действи­


тельно прочитанных байтов в переменной c ount . Обычно эта величина совпада­
ет с параметром nby t e s , но может быть меньше, если, например, в процессе чте­
ния процедуре встретился символ конца файла.
Если системный вызов не может быть выполнен или из-за неправильных пара­
метров или из-за дисковой ошибки, значение счетчика c ount устанавливает­
ся равным - 1 , а номер ошибки помещается в глобальную переменную errno.
48 Глава 1 . Введение

Программы всегда должны проверять результат системного вызова, чтобы от­


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

Табл ица 1 . 1 . Основные системные вызовы MINIX (fd обозначает дескриптор


файла, n - число байтов)
Вызов Описание
Управление процессами
pid = fork( ) Создает дочерний процесс, идентичный родительскому
pid = waitpid(pid, &statloc, options) Ожидает завершения дочернего процесса
s = wait( &status) Старая версия waitpid
s = execve ( name, argv, envp) Перемещает образ памяти процесса
exit( status) Завершает выполнение процесса и возвращает статус
size = brk( addr) Устанавливает размер сегмента данных
pid = getpid ( ) Возвращает идентификатор процесса, сделавшего вызов
p i d = getpgrp( ) Возвращает идентификатор группы процессов для
сделавшего вызов процесса
pid = setsid ( ) Открывает новый сеанс и возвращает для него
идентификатор группы процессов
1 = ptrace ( req , pid , addr, data) Используется для отладки
Сигналы
s = sigaction(sig, &act, &oldact) Устанавливает реакцию на сигнал
s = sigretu rn( &context) Возвращается из обработчика сигнала
s = sigprocmask( how, &set, &ol d ) Определяет или устанавливает маску сигналов для процесса
s = sigpending(set) Определяет набор блокированных сигналов
s = sigsuspend(sig mask) Устанавливает маску сигналов для процесса
и приостанавливает его
s = ki l l ( p i d , sig) Посылает сигнал процессу
residual = alarm (seconds) Устанавливает сигнальный таймер
s = pause ( ) Приостанавливает процесс до прихода следующего сигнала
Управление файлами
fd = creat( name , mode) Устаревший способ создать файл
fd = m knod ( name, mode , addr) Создает обычный, специальный или относящийся
к каталогу индексный узел
fd=open(file, how, . . . ) Открывает файл для чтения, записи или того и другого
s = close(fd) Закрывает открытый файл
1 .4. Системн ые вызовы 49

Вызов Описание
п read(fd, buffer, пbytes)
= Читает данные из файла в буфер
п write(fd, buffer, пbytes)
= Пишет данные из буфера в файл
pos lseek(fd, offset, wheпce)
= Передвигает указатель файла
s stat(пame, &buf)
= Получает информацию о состоянии файла
s fstat(fd, &buf)
= Получает информацию о состоянии файла
fd dup(fd)
= Закрепляет за открытым файлом новый дескриптор
s pipe(&fd[O])
= Создает канал
s ioctl(fd, request, argp)
= Специальные действия с файлом
s access(пame, amode)
= Проверяет доступность файла
s reпame(old, пеw)
= Переименовывает файл
s fcпtl(fd, cmd, ... )
= Захватывает файл и выполняет другие действия
Управление каталогами и файловой системой
s mkdir(пame, mode)
= Создает новый каталог
s rmdir(пame)
= Удаляет пустой каталог
s liпk(пame1 , паmе2)
= Создает новый элемент с именем паmе2, указывающий
на паmе1
s = uпliпk(пame) Удаляет элемент каталога
s = mouпt(special, паmе, flag) Монтирует файловую систему
s = umouпt(special) Демонтирует файловую систему
s = sупс() Сбрасывает все кэшированные блоки на диск
s = chdir(dirпame) Изменяет рабочий каталог
s = chroot(dirпame) Изменяет корневой каталог
Защита
s chmod(пame, mode)
= Изменяет биты защиты файла
uid getuid()
= Определяет идентификатор пользователя для вызвавшего
gid getgid()
= Определяет идентификатор группы для вызвавшего
s setuid(uid)
= Устанавливает идентификатор пользователя для вызвавшего
s setgid(gid)
= Устанавливает идентификатор группы для вызвавшего
s chowп(пame, owпer, group)
= Меняет идентификатор владельца файла
oldmask umask(complmode)
= Меняет режим маскирования
Управление временем
secoпds time(&secoпds)
= Получает время, прошедшее с 1 января 1 970 года
s stime(tp)
= Устанавливает время, прошедшее с 1 января 1 970 года
s utime(file, timep)
= Устанавливает время последнего доступа к файлу
s times(buffer)
= Определяет время работы пользовательского процесса
и системы
Особое внимание следует обратить на то, что преобразование вызовов РОSIХ­
процедур в системные вызовы не является взаимно однозначным. Стандарт
POSIX определяет ряд процедур, которые должны поддерживать совместимые
системы, но он не указывает, являются ли они системными вызовами, библио­
течными вызовами или чем-нибудь еще. В некоторых случаях РОSIХ-процеду­
ры поддерживаются в MINIX 3 библиотечными функциями. Иногда требуемые
50 Глава 1 . Введение

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


вызов обрабатывает сразу несколько библиотечных вызовов.

1 4 1
. Систем ные вызовы
. .

для управления процессам и


Первая группа вызовов в табл. 1 . 1 управляет процессами. Начнем рассмотре­
ние с вызова f o rk. Системный вызов f ork (разветвление) является единствен­
ным способом создания нового процесса в MINIX 3. Он создает точную копию
исходного процесса, включая дескрипторы файла, регистры и т. п. После вызова
f o rk исходный процесс и его копия (родительский и дочерний процессы) раз­
виваются отдельно друг от друга. Все переменные имеют одинаковые величины
во время вызова f o rk, но как только родительские данные скопированы для со­
здания дочернего процесса, последующие изменения в одном из них уже не влияют
на другой. (Текст программы, который не изменяется, распределяется между роди­
тельским и дочерним процессами.) Вызов fork возвращает величину, равную ну­
лю в дочернем процессе и равную идентификатору дочернего процесса в родитель­
ском. Используя возвращенный идентификатор процесса (Process IDentifier, PID),
два процесса могут выяснить, какой из них родительский, а какой - дочерний.
В большинстве случаев после вызова f o rk дочернему процессу необходимо вы­
полнить программный код, отличный от кода родительского процесса. Рассмотрим
пример оболочки. Она читает команды с терминала, запускает дочерний процесс,
ждет, пока дочерний процесс выполнит команду, и читает следующую команду
после завершения работы дочернего процесса. Ожидая, пока дочерний процесс
закончит работу, родительский процесс выполняет системный вызов wa i t p i d,
который ожидает завершения дочернего процесса (или всех дочерних процессов,
если их на данный момент несколько). Вызов wa i t p i d может ждать окончания
какого-либо определенного дочернего процесса или любого дочернего процес­
са, для этого нужно задать первый параметр вызова равным - 1 . Когда вызов
wa i t p i d выполнен, указатель, задаваемый вторым параметром s t at l o c, пока­
жет статус завершения дочернего процесса (нормальное или аварийное заверше­
ние и выходное значение). Третий параметр определяет различные необязатель­
ные настройки. Вызов wa i t p i d заменяет ранее использовавшийся вызов wa i t ,
который на данный момент устарел и поддерживается лишь для обратной со­
вместимости.
Теперь рассмотрим, как вызов fork используется оболочкой. Когда печатается
команда, оболочка создает дочерний процесс, который должен выполнить коман­
ду пользователя. Он делает это с помощью системного вызова execve, заме­
няющего весь его образ памяти файлом, указанным в первом параметре. ( Фак­
тически самим системным вызовом является е х е с , но несколько различных
библиотечных процедур вызывают его с разными параметрами и незначительно
отличающимися именами. Мы здесь воспользуемся ими как системными вызо­
вами. ) Весьма упрощенная оболочка, иллюстрирующая использование команд
f o rk, wa i t p i d и ехе с , показана в листинге 1 . 1 .
1 .4. С и стемные вызовы 51

Листинг 1 . 1 . Усеченная оболочка. Здесь и далее в книге предполагается,


что значение TR U E равно 1
ll de f ine TRUE 1
wh i l e ( TRUE ) { / * вечный цикл * /
type_prompt ( ) ; / * печать приглашения на экране * /
read_command ( command , pa rame t e r s ) ; / * читать входные данные с терминала * /

if ( f ork ( ) ! = 0 ) { / * запускает дочерний процесс * /


/ * код родительского проце с с а * /
wa i t p i d ( - 1 , & s t a t u s , О ) ; / * ждать окончания дочернего процесса * /
else {
/ * код дочерне го проце с с а * /
execve ( c ommand , pa rame t e r s , 0 ) ; / * выполнение coшmand * /

В самом общем случае у команды е х е с есть три параметра: имя выполняемого


файла, указатель на массив apryмelffoв и указатель на массив переменных окруже­
ния. Эти параметры мы кратко обсудим в дальнейшем. Различные библиотечные
программы, включая exe c l , execv, exec l e и execve, разрешают опускать па­
раметры или определять их другими способами. В книге мы воспользуемся на­
званием ехес для того, чтобы представить системный вызов, вызываемый всеми
этими процедурами.
Рассмотрим следующую команду:
ер f i l e l f i l e2

Эта команда используется для копирования файла f i l e l в файл f i l e 2 . После


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

Основной модуль программы е р (как и большинство других головных программ


на языке С) содержит определение:
ma in ( a rgc , a rgv , envp )

В этом определении в параметр argc входит количество записей в командной


строке, включая имя программы. Например, для показанной строки параметр
argc равен 3.
Второй параметр a rgv является указателем на массив указателей. Элемент i
массива указывает на i-ю запись в командной строке. В нашем примере параметр
argv [ О ] должен указывать на строку ер, а argv [ 1 ] и argv [ 2 ] - на строки
f i l e l и f i l e 2 соответственно.
Третий параметр функции rna in с именем envp является указателем на массив
строковых переменных окружения вида имя величина, которые используются
=

для передачи программе такой информации, как тип терминала или имя домаш­
него каталога. В листинге 1 . 1 третий параметр равен нулю, поскольку дочернему
процессу ничего не передается.
Если команда ехее кажется сложной, не огорчайтесь, потому что это - один из
наиболее сложных системных вызовов в POSIX. Все остальные намного про­
ще. В качестве еще одного примера рассмотрим вызов ex i t , процессы должны
52 Глава 1 . Введение

использовать его при завершении работы. У него есть всего один параметр, статус
выхода, изменяющийся от О до 255. Он возвращается родительскому процессу
через переменную s t a t l oc в системном вызове wa i t p i d. Младший байт этой
переменной содержит значение статуса выхода, который равен О при нормальном
завершении работы и ненулевому значению при завершении по ошибке. Старший
байт содержит статус завершения дочернего процесса (от О до 255). Например:
n = wa i t p i d ( - 1 , & s t atus , opt i ons ) ;

Если родительский процесс выполнит эту команду, то его работа будет приоста­
новлена до завершения дочернего процесса. Если дочерний процесс завершится,
скажем, через вызов exi t с параметром 4, то когда родительский процесс про­
должит работу, n будет содержать PID дочернего процесса, а s t a t l o c значе­ -

ние Ох0400 (в языке С принято писать символы Ох перед шестнадцатеричными


числами, это соглашение постоянно используется в книге).
В MINIX 3 под процессы отводится часть памяти, которая, в свою очередь, де­
лится на три сегмента: текста (код программы), данных (переменные) и стека.
Сегмент данных растет снизу вверх, а стек увеличивается сверху вниз , как пока­
зано на рис. 1 .9. Между ними существует часть неиспользованного адресного
пространства. Стек автоматически занимает такую часть этого участка памяти,
какую необходимо, но расширение сегмента данных выполняется явным обра­
зом. Для этого используется специальный системный вызов br k, задающий новый
адрес для границы сегмента данных. Этот адрес может быть как больше текуще­
го значения (сегмент растет), так и меньше (сегмент уменьшается). Но, конечно
же, этот адрес должен быть меньше, чем указатель стека, так как в противном
случае данные и стек могут перекрываться, что недопустимо.

Адрес (шестнадцатеричный)
�---� FFFF
Стек
Промежуток
Данные
Текст
'------' 0000
Рис. 1 . 9 . Под процессы отводится три сегмента: текст, данные и стек. В данном примере
все три расположены в едином адресном пространстве, однако также поддерживаются
раздельные пространства сегментов команд и данных
Для удобства программиста предлагается библиотечная процедура sbrk, так­
же меняющая размер сегмента данных. Ее единственный параметр указывает, на
сколько должен быть увеличен размер сегмента (чтобы уменьшить сегмент, нуж­
но передавать отрицательные значения). Процедура работает так: вызовом brk
определяется текущий размер сегмента, затем вычисляется новый размер, после
чего делается еще один системный вызов, запрашивающий требуемое количест­
во байтов. Оба вызова (brk и sbrk) не относятся к стандарту POSIX. Для дина­
мического выделения памяти программисты могут использовать библиотечную
1 .4. Системные вызовы 53

процедуру ma l l oc. Поскольку впрямую эту процедуру применяют очень редко,


ее стандартизацию посчитали нецелесообразной.
Следующий системный вызов для работы с процессами, getp i d, является заодно
и простейшим из них. Этот вызов просто возвращает идентификатор вызвавшего
его процесса. Обратите внимание на то, что при вызове f ork значение идентифи­
катора дочернего процесса получает только родительский процесс. Если дочерне­
му процессу потребуется узнать собственный идентификатор процесса, ему придет­
ся использовать вызов getpid. Вызов getgrp возвращает идентификатор группы
процессов, в которую входит процесс, сделавший вызов. Вызов s e t s i d открывает
новый сеанс и устанавливает идентификатор группы процессов равным иден­
тификатору процесса, сделавшего вызов. Сеансы относятся к необязательной
функции POSIX, называемой управлением заданиями и в MINIX 3 не реализо­
ванной, поэтому данная функция в дальнейшем не упоминается.
Последний системный вызов из этой группы, pt race, используется от-дадчиками
для управления отлаживаемой программой. Он позволяет отладчику обращаться
к памяти отлаживаемого процесса, а также управлять им другими способами.

1 . 4 . 2 . Систем ные вызовы


для уп равления сигналами
Хотя обычно все взаимодействие между процессами запланировано, существуют
ситуации, когда требуется незапланированное взаимодействие. Например, если
пользователь случайно потребовал от текстового редактора показать содержи­
мое очень большого файла и заметил свою ошибку, должен существовать способ
прервать работу редактора. В M INIX 3 пользователь может нажать клавиши
Ctrl+C; это сочетание посылает текстовому редактору соответствующий сигнал.
Получив его, редактор прекращает распечатку. Сигналы могут использоваться
и для того, чтобы перехватывать некоторые аппаратные исключения, например
недопустимые инструкции или переполнение при операциях с плавающей точ­
кой. Задержки также реализуются через сигналы.
Если процесс никак не анонсировал свое желание отвечать на сигналы, то, по­
лучив сигнал, он просто принудительно завершается. Чтобы избежать подобной
судьбы и рассказать о своем желании отвечать на сигналы определенного типа,
процесс может использовать системный вызов s i gac t i on, который позволяет
указать новый адрес процедуры для обработки сигнала и узнать адрес прежней
процедуры. После того как такой вызов сделан, при получении сигнала соот­
ветствующего типа (например, при нажатии клавиш Ctrl+C) состояние процесса
будет сохранено в стеке и вызван обработчик сигнала. Обработчик может ра­
ботать сколь угодно долго и делать при этом любые системные вызовы. Но на
практике обработчики сигналов обычно весьма коротки. Завершив свою работу,
обработчик делает вызов s i greturn, чтобы продолжить работу процесса с того
места, где он бьш прерван. Вызов s i ga c t i on заменяет прежний системный вы­
зов s i gna l , который теперь для обратной совместимости реализован в виде
библиотечной процедуры.
54 Глава 1 . Введение

В MINIX 3 сигналы можно блокировать. Блокированный сигнал задерживается


до тех пор, пока не будет разблокирован. То есть сигнал не передается, но и не
теряется. Вызов s i gprocrna s k позволяет процессу задать набор блокируемых
сигналов, передавая ядру битовую карту. Кроме того, процесс может узнать, ка­
кие сигналы имели место, но были заблокированы. Для этого служит системный
вызов s i gpending, возвращающий набор сигналов в виде битовой маски. Нако­
нец, системный вызов s i gsuspend позволяет процессу атомарно задать бито­
вую карту блокированных сигналов и приостановить свое выполнение.
Вместо того чтобы передавать в качестве обработчика сигнала указатель на функ­
цию, программа может использовать константу S IG_IGN, чтобы все последую­
щие сигналы определенного типа игнорировались. Передав константу S IG_DFL,
можно восстановить обработку сигнала по умолчанию. По умолчанию процесс
либо принудительно завершается по сигналу, либо сигнал просто игнорируется.
Это зависит от конкретного сигнала. В качестве примера того, как используется
константа S I G_IGN, рассмотрим работу оболочки при запуске фонового процес­
са с помощью такой команды:
c oпunand &

В этом случае сигнал от нажатия клавиш Ctrl+C не должен влиять на работу фо­
нового процесса, поэтому после вызова f orc, но перед вызовом ехес оболочка
делает следующие вызовы:
s i ga c t ion ( S I G I NT , S I G_I GN , NULL ) ;
s igac t i on ( S I GQUI T , S I G_IGN , NULL ) ;

Эти вызовы отключают обработку сигналов S IG INT и S IGQU IT (сигнал S I GQUI T


генерируется нажатием клавиш Ctrl+\ и делает т о ж е самое, что и Ctrl+C, но, если
его не обрабатывать или не блокировать, позволяет получить дамп памяти завер­
шенного процесса). Для обычных процессов, выполняющихся не в фоновом ре­
жиме, игнорировать сигналы не нужно.
Нажатие клавиш Ctrl+C не единственный способ послать процессу сигнал. Сис­
-

темный вызов k i l l позволяет одному процессу послать сигнал другому (у обоих


процессов должен быть одинаковый идентификатор пользователя, так как не­
связанные процессы не могут послать друг другу сигналы). Возвращаясь к рас­
сматриваемому примеру, предположим, что запущенный фоновый процесс по­
требовалось завершить. Сигналы S IGQU I T и S IG INT этот процесс игнорируют,
так что требуется что-то другое. Решение дает программа k i l l , которая с помо­
щью системного вызова K I LL может послать сигнал любому процессу. Процесс
можно принудительно завершить, послав ему сигнал 9 ( S IGKILL). Этот сигнал
нельзя обработать или игнорировать.
Во многих приложениях реального времени процесс нужно прерывать по исте­
чении некоторого интервала времени, за который он должен успеть выполнить
свою работу, например повторно передать пакет данных по ненадежной линии
связи. Для этой ситуации предназначен системный вызов a l a rrn. Параметр это­
го вызова описывает интервал времени, по истечении которого процессу переда­
ется сигнал S I GALARМ. В любой момент времени процесс может запланировать
1 . 4 . С и стемные вызовы 55

только один сиrnал. Если сделать вызов a l arrn, указав задержку 1 0 секунд, а за­
тем, по истечении 3 секунд, еще раз выполнить вызов a l arrn с параметром 20 се­
кунд, придет сигнал только от того вызова, который сделан последним. Первый
вызов отменяется. Чтобы отменить сделанный ранее вызов a l arrn, нужно еще
раз сделать вызов a l arrn, передав в качестве аргумента О. Если пришедший сиг­
нал S I GALARМ не обрабатывать, то выполняется действие по умолчанию, и про­
цесс завершается.
Иногда возникают ситуации, когда процессу нечем заняться до прибытия сиrnала.
Например, рассмотрим компьютерную программу для проверки скорости чтения
и внимательности. Эта программа выводит некоторый текст, а затем делает вы­
зов a l a rrn с параметром 30 секунд. Пока ученик читает текст, программа ничего
не должна делать. Программа может выполнять пустой цикл, но это будет бес­
смысленной тратой процессорного времени, которое может потребоваться дру­
гому процессу. Гораздо лучше использовать системный вызов pau s e , который
заставляет MINIX 3 приостановить выполнение процесса до прихода сигнала.

1 . 4 . З . Систем ные вызовы


для уп равления файлами
Многие системные вызовы имеют отношение к файловой системе. В этом разде­
ле мы рассмотрим вызовы, работающие с отдельными файлами, а в следующем
разделе обратимся к вызовам, которые оперируют каталогами или файловой
системой в целом. Для создания нового файла служит вызов creat (ответ на во­
прос, почему именно c r e a t , а не crea t e , затерялся в тумане времен). Его пара­
метры - имя файла и права доступа, например:
fd = c r e a t ( " abc " , 0751) ;

Эта команда создаст файл с именем аЬс и установит для него права доступа 075 1
(в С числа с ведущим нулем считаются восьмеричными). Младшие 9 бит этого
числа показывают, что владелец файла имеет права доступа rwx (7 означает
права на чтение, запись и исполнение) , члены группы владельца имеют права
на чтение и исполнение (5), а прочие пользователи - только на исполнение ( 1 ).
Вызов creat не только создает новый файл, но и открывает его для записи неза­
висимо от указанного режима. Дальнейшая запись в файл производится через
его дескриптор fd, значение которого возвращается вызовом. Если выполнить
вызов c reat для существующего файла, то файл усекается до нулевой длины
(если, конечно, права доступа позволяют это). Сейчас вызов crea t устарел и под­
держивается для обратной совместимости, вместо него нужно использовать
вызов open.
Чтобы создать специальный файл, нужно вместо creat выполнить вызов rnknod.
Вот типичный пример:
fd = mknod ( " / dev/ t ty c 2 " , 020744 , Ох 0 4 0 2 ) ;

Эта команда создает файл с именем / dev / t ty c 2 (обычно это имя соответствует
второй консоли) и задает для него права доступа 0207 44 (специальный символьный
56 Глава 1 . Введение

файл с правами rwxr - - r - - ) . Третий параметр составлен из пары байтов, из


которых старший задает основное устройство ( 4 ), а младший - вторичное уст­
ройство (2). Основное устройство может быть любым, а вторичное для файла
/ dev / t tyc2 должно быть равно 2. Делать вызов rnknod может только супер­
пользователь, в противном случае возникает ошибка.
Чтобы прочитать или записать файл, его сначала нужно открыть при помощи
вызова open. Для этого вызова указывается имя открываемого файла (задается
или абсолютный путь файла, или ссылка на рабочий каталог) и код O_RDONLY,
O_WRONL У или O_RDWR, означающий, что файл открывается для чтения, записи
или того и другого. Для создания нового файла служит код O_CREAT. Возвра­
щаемый дескриптор файла затем можно употребить при чтении или записи. По­
том файл закрывается с помощью вызова c l o s e , который делает дескриптор
файла доступным для последующего вызова c reat или open.
Наиболее часто используемыми вызовами, без сомнения, являются read и wri t e .
Вызов read м ы уже обсуждали, wr i t e имеет т е ж е самые параметры.
Несмотря на то что большинство программ читает и записывает файлы путем
последовательного доступа, некоторым прикладным программам необходима
возможность доступа к любой случайно выбранной части файла. Связанный с ка­
ждым файлом указатель содержит текущую позицию в файле. Когда чтение (за­
пись) осуществляется последовательно, он обычно указывает на байт, который
должен быть прочитан (записан) следующим. Вызов l s eek может изменить зна­
чение позиции указателя, так что следующий вызов read или wr i t e начнет опе­
рацию где-либо в другой части файла.
У вызова l s e e k есть три параметра: первый - это идентификатор файла, вто­
рой - позиция в файле, а третий говорит, является ли второй параметр позицией
в файле относительно начала файла (абсолютная позиция), относительно теку­
щей позиции или относительно конца файла. Вызов l s eek возвращает абсолют­
ную позицию в файле после изменения указателя.
Для каждого файла M INIX хранит следующие данные: тип файла (обычный,
специальный, каталог и т. д. ), размер, время последнего изменения и другую ин­
формацию. Программа может запросить эту информацию через системные вызо­
вы s t a t и f s t a t . Они различаются только тем, что первый из них требует име­
ни файла, а второй полагается на дескриптор файла и полезен для открытых
файлов, особенно для файлов стандартного ввода и вывода, имена которых не
всегда известны. У обоих вызовов второй параметр указывает на структуру, куда
нужно поместить информацию. Эта структура показана в листинге 1 .2.
Структура, используемая для получения информации от системных вызовов STAT
Листинг 1 . 2.
и FSTAT. В фактическом коде для некоторых типов используются символические имена
s t ruc t stat {
short s t_dev ; /* у с тройс т в о , которому принадлежит i -у з е л * /
uns i gned sho rt s t_ino ; /* номер inode * /
uns i gned short s t_mode ; /* режим д о с тупа * /
sho rt s t_n l ink ; /* число с сылок на файл * /
1 . 4 . Системные вызовы 57

short s t_u i d ; /* идентификатор поль з о в ателя */


short s t_g i d ; /* идентификатор группы * /
sho r t s t_rdev ; /* основное и вторичное * !
!* у с тройс тво для с пециальных файлов * /
long s t _s i z e ; !* р а з мер файла * !
l ong s t_a t i me ; /* время последнего обращения *!
l ong s t_mt ime ; /* время последнего изменения *!
l ong s t _c t ime ; /* время последнего изменения для i -узла * !
};

При работе с дескрипторами файлов иногда может оказаться полезным систем­


ный вызов dup. Например, рассмотрим программу, которая закрывает стандарт­
ный вывод (дескриптор 1 ), подставляет в качестве стандартного вывода другой
файл, вызывает функцию, которая что-то пишет в этот файл, а затем восстанав­
ливает исходное состояние. Если просто закрыть стандартный вывод и открыть
новый файл, то новый файл станет стандартным выводом (если используется
стандартный ввод, дескриптор которого равен О), но восстановить состояние
будет невозможно. Решение дает следующая команда, в которой используется
системный вызов dup:
fd = dup ( 1 ) ;

При выполнении этой команды в переменную fd помещается новый дескриптор,


который будет соответствовать тому же файлу, что и стандартный вывод ( 1 ).
Затем стандартный вывод можно закрыть, после чего открыть новый файл и ис­
пользовать его. Когда понадобится восстановить исходное состояние, нужно за­
крыть дескриптор 1 и выполнить код:
n = dup ( f d ) ;

В результате самый меньший из дескрипторов файлов, а именно 1 , станет соот­


ветствовать тому же файлу, что и fd. Наконец, fd можно закрыть, в результате
мы вернемся к той же ситуации, с которой начали.
У системного вызова dup есть второй вариант, с помощью которого можно связать
неиспользованный дескриптор с уже открытым файлом. Он записывается так:
dup 2 ( f d , f d2 ) ;

Здесь fd это дескриптор открытого файла, а fd2


- не связанный ни с каким
-

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


же файл. Таким образом, если fd ссылается на стандартный ввод (дескриптор
равен О), а дескриптор fd2 равен 4, то после выполнеnия вызова и О и 4 будут
соответствовать стандартному вводу.
Как уже отмечалось, для обеспечения взаимодействия между процессами в MINIX 3
можно использовать каналы. Например:
cat fi lel f ile2 1 sort

Когда пользователь дает оболочке эту команду, то оболочка создает канал и со­
единяет стандартный вывод первого процесса с входом канала, а стандартный
ввод второго процесса с выходом канала. Чтобы создать канал, применяется сис­
темный вызов p ipe, возвращающий два дескриптора файлов: один для чтения
из канала, другой для записи в него:
58 Глава 1 . Введение

pipe ( & f d [ O ) ) ;

Здесь fd - массив двух целых чисел, fd [ О ] - дескриптор для чтения, а fd [ 1 ] -


дескриптор для записи. Как правило, после этого делается вызов fork, роди­
тельский процесс закрывает дескриптор для чтения, а дочерний процесс - де­
скриптор для записи (или наоборот) , чтобы один процесс мог писать в канал,
а другой - читать из него.
В листинге 1 . 3 приведен каркас процедуры, создающей два процесса таким обра­
зом, что выход первого из них передается через канал во второй (более реали­
стичный пример обеспечивал бы проверку ошибок и обработку аргументов ) .
Сначала создается поток, затем делается вызов f ork, и родительский процесс
становится первым процессом в канале, а дочерний процесс - вторым. Так как
запускаемые файлы, p r o c e s s l и pr o c e s s 2 , ничего не знают о том, что они
соединяются каналом, для работы программы необходимо, чтобы стандартный
вывод первого процесса был соединен со стандартным вводом второго процесса
каналом. Сначала родительский процесс закрывает дескриптор для чтения из ка­
нала. Затем он закрывает стандартный вывод и делает вызов dup, после которого
стандартным выводом становится вход канала. Важно понимать, что вызов dup
всегда возвращает наименьший допустимый дескриптор, в данном случае 1. -

Наконец, исходный дескриптор для записи в канал закрывается.


Листинг 1 . 3 . Каркас процедуры, создающей конвейер из двух процессов
# de f ine STD_I NPUT О / * Дес криптор файла для с тандартного ввода * /
# d e f ine STD_OUTPUT 1 / * Д е с криптор файла для с тандартного вывода * /

pipe l ine ( p ro c e s s l , pro c e s s 2 ) / * Ука з атели на имена про грамм * /


char *proc e s s l , * p r o c e s s 2 ;
{
int f d [ 2 ) ;

pipe { & f d [ O ] ) ; / * с о з дать конвейер * /


i f ( f ork ( ) ! = О )
/ * Эти выражения исполняются родительс ким проце с с ом * /
c lose ( fd [ O J ) ; / * Проце с с 1 не нуждается в чт ении с конвейера * /
c l o s e ( STD_OUTPUT ) ; / * Подготовка н о в о г о с тандартного вывода * /
dup ( f d [ l ] ) ; / * Сделать с тандартным выводом у с тройс тво f d [ l ] * /
c lose ( fd [ l ) ) ; / * Этот дес криптор файла больше не нужен * /
exe c l ( proc e s s l , proc e s s l , 0 ) ;
else {
/ * Эти выражения исполняются проце с с ом - н а с л едником * /
c lose ( fd [ l ) ) ; / * Проц е с с 2 н е нуждается в з аписи в конвейер * /
c l o s e ( S TD_I NPUT ) ; / * Подготовка нового с тандартного ввода * /
dup ( f d [ O J ) ; / * Сделать с тандартным в водом у с тройство f d [ O J * /
c lose ( fd [ O J ) ; / * Этот дес криптор файла больше не нужен * /
exec l ( p r o c e s s 2 , proc e s s 2 , 0 ) ;

После вызова ехес стартует процесс, у которого дескрипторы О и 2 остались без


изменений, а дескриптор 1 соответствует записи в канал. Код дочернего про­
цесса аналогичен. Значение параметра функции exec l повторяется потому, что
1 . 4 . Системные вызовы 59

первым ее параметром должно быть имя программы, а вторым - значение перво­


го аргумента запускаемой программы, которое для большинства программ долж­
но совпадать с именем.
Следующий системный вызов, i o c t l , потенциально применим ко всем специаль­
ным файлам. Например, он используется драйверами блочных устройств, таких
как SСSl-драйверы для работы с ленточными накопителями и CD-ROM. Тем не
менее он в основном применяется к символьным специальным файлам, особен­
но к терминалам. В стандарте POSIX определено несколько функций, которые
транслируются библиотекой в вызовы i o c t l . С помощью функций t c g e t a t t r
и t c s e t a t t r можно изменить характеристики терминала, такие как коррекция
ошибок, режим и т. д. Эти функции используют вызов i oc t l .
Режим с обработкой ( cooked mode) - это нормальный режим работы терминала,
в котором возможно удаление символов, клавиши Ctrl +S и Ctrl +Q соответствен­
но останавливают и запускают вывод информации на терминал, клавиши Ctrl+D
задают конец файла, а клавиши Ctrl+C генерируют сигнал прерывания. Нажатие
клавиш Ctrl +\ в этом режиме генерирует сигнал, по которому процесс принуди­
тельно прерывается, и выводится дамп памяти.
В режиме без обработки (raw mode) вся эта дополнительная обработка не вы­
полняется, и символы передаются программе напрямую. Более того, в этом ре­
жиме терминал не ждет окончания ввода строки, а передает символы программе
сразу. В таком режиме зачастую работают экранные редакторы.
Режим с прерыванием (cbreak mode) - промежуточный между двумя предыдущи­
ми. В этом режиме при редактировании не работают клавиши стирания и удале­
ния, а также комбинация Ctrl+D, но клавиши Ctrl+S, Ctrl+Q, Ctrl+C и Ctrl+\ работа­
ют. Как и в режиме без обработки, символы передаются программам сразу, не
дожидаясь окончания ввода строки (так как редактирование строк не работает,
не обязательно дожидаться окончания ввода, потому что пользователь не сможет
передумать и удалить введенные символы, как в режиме с обработкой).
Режимы с обработкой, без обработки и с прерыванием в стандарте POSIX не
описаны. Вместо этого POSIX определяет канонический режим ( canonical mode ),
соответствующий режиму с обработкой. В нем определено одиннадцать специаль­
ных символов, и ввод ведется построчно. В неканоническом режиме (noncanonical
mode) ввод данных определяется минимальным воспринимаемым количеством
символов и временем (в десятых долях секунды). Стандарт POSIX очень гибок
и определяет множество различных флагов, управляя которыми можно прибли­
зить неканонический режим как к режиму без обработки, так и к режиму с пре­
рыванием. Старые термины более содержательны, поэтому мы неформально бу­
дем их придерживаться.
У вызова i oc t l есть три параметра. Например, вызов функции t c s e t a t t r ,
устанавливающей параметры терминала, выглядит так:
i oc t l ( f d , TCSETS , & t e rm i o s ) ;

Первый параметр задает файл, второй - выполняемую операцию, а третий -


адрес РОSIХ-структуры, содержащей флаги и массив управляющих символов.
60 Глава 1 . Введение

Другие коды операций позволяют отложить изменения до завершения выво­


да, считать текущие значения параметров и отбросить не полностью считанные
данные.
Системный вызов a c c e s s позволяет узнать, разрешено ли системой ограни­
чения доступа обращение к определенному файлу. Этот вызов необходим по­
тому, что некоторые программы могут работать под другим идентификатором
пользователя. Для этого программами используется механизм SETU ID, кото­
рый описан далее.
Чтобы присвоить файлу новое имя, применяется системный вызов renarne. Его
параметры задают старое и новое имя файла.
Наконец, для управления файлами служит вызов f cnt l , в чем-то подобный
вызову i oc t l (и оба этих вызова - так называемые грязные хаки). У этого вы­
зова есть несколько параметров, самые важные из которых служат для управ­
ления захватом файла. Вызовом f cn t l можно захватывать и освобождать от­
дельные части файлов, а также определять, захвачен ли нужный участок. Этот
вызов никак не определяет семантику захвата файла. Программы должны сами
решать, что делать.

1 4 4 Системные вызовы
. . .

для управления каталогами


В этом разделе м ы рассмотрим некоторые системные вызовы, относящиеся ско­
рее к каталогам и файловой системе в целом, нежели просто к тому или иному
файлу. Первые два вызова, rnkd i r и rrnd i r , соответственно создают и удаля­
ют пустые каталоги. Следующий вызов - l ink. Он разрешает одному файлу
появляться под двумя или более именами, часто в разных каталогах. Этот вы­
зов обычно применяется, когда несколько программистов, работающих в одной
команде, должны совместно использовать один общий файл. Тогда этот файл
может появиться в каталоге у каждого из программистов, возможно, под дру­
гим именем. Разделение (совместное использование) файла - это не то же самое,
что копирование файла для каждого члена команды. При совместном исполь­
зовании файла изменения, производимые одним программистом, немедленно
становятся видимыми для остальных - все происходит в одном файле. А при
создании копии файла последующие изменения не влияют на другие копии
этого файла.
Чтобы понять, как работает вызов l i nk, рассмотрим рис. 1 . 1 0 , а. Два пользо­
вателя, Ast и Jirn, имеют собственные каталоги a s t и j irn с файлами. Предпо­
ложим, что пользователь Ast запускает программу, содержащую системный
вызов:
l ink ( " / u s r / j im/ meщo " , " / u s r / a s t / no t e " ) ;

Тогда файл rnerno из каталога j irn появится в каталоге a s t под названием not e.
Соответственно, имена / u s r / j irn/rnerno и / u s r / a s t / not e после этого будут
ссылаться на один и тот же файл.
1 . 4 . Системные вызовы 61

/usr/ast /usr/jim /usr/ast /usr/jim


1 6 mail 31 Ып 1 6 mail 3 1 Ып
81 games 70 memo 8 1 games 70 memo
40 test 59 f.c. 40 test 59 f.c.
38 p rog 1 70 поtе 38 p rog 1

а б
Рис. 1 . 1 0 .Механизм выполнения вызова liпk: а -два каталога до присоединения
/usr/jim/memo к каталогу ast; б - те же каталоги после вызова liпk
Возможно, станет понятнее, что делает системный вызов 1 ink, если разобраться
в том, как он работает. Каждый файл в UNI X имеет уникальный номер, который
идентифицирует файл. Этот номер представляет собой индекс в таблице индекс­
ных узлов (index nodes), или i-узлов (i-nodes), содержащей по одному индексному
узлу на файл. Каждый индексный узел включает в себя информацию о владель­
це файла, о том, какие блоки на диске он занимает и т. д. Каталог представляет
собой просто файл, содержащий набор пар из номера индексного узла и АSСП­
имени. В первых версиях UNIX каждый элемент каталога занимал 16 байт: 2 на
номер индексного узла и 14 на имя. Для поддержки длинных имен файлов
требуется более сложная структура, однако концептуально каталог представля­
ет собой файл с парами номеров индексных узлов и АSСП-имен. На рис. 1 . 1 0
файл rna i l имеет номер индексного узла 1 6 и т . д . Действие вызова l ink заклю­
чается в создании нового элемента каталога, имя которого, возможно, является
новым, а номер индексного узла равен номеру индексного узла существующе­
го файла. На рис. 1 . 1 0 , б два элемента имеют одинаковый номер индексного
узла (70) и, таким образом, ссылаются на один и тот же файл. Если впоследст­
вии один из них будет удален с помощью системного вызова unl ink, другой
элемент останется. Если будут удалены оба файла, UNIX обнаружит, что больше
нет записей, соответствующих этому файлу (поле в таблице индексных узлов
хранит данные с номером элемента каталога, указывающего на файл), и удалит
файл с диска.
Как упоминалось ранее, системный вызов rnount позволяет две файловых систе­
мы объединить в одну. Обычная ситуация такова: на жестком диске находится
корневая файловая система, содержащая двоичные (исполняемые) версии об­
щих команд и наиболее часто использующиеся файлы. При этом пользователь
может вставить в дисковод компакт-диск с файлами для чтения.
При помощи системного вызова rnount файловую систему с гибкого диска можно
присоединить к корневой файловой системе, как показано на рис. 1 . 1 1 . Типич­
ная команда на языке С, выполняющая монтирование, выглядит так:
mount ( " / de v / c dromO " , " / mnt " , 0) ;

Здесь первым параметром является имя специального блочного файла на дис­


ке О, второй параметр - это место в дереве, куда будет вмонтирована файловая
система, а третий параметр говорит о том, монтируется ли встроенная файловая
система для чтения и записи или только для чтения.
62 Гл а ва 1. Введение

Ып dev lib mnt usr

а б
Рис. 1 . 1 1 . Файловая система: а - до вызова mount; б - после вызова mount
После вызова rnoun t доступ к файлу на диске О можно получить, просто указав
его путь из корневого или рабочего каталога, независимо от того, на каком диске
он находится. В действительности второй, третий и четвертый диски тоже мож­
но встроить в любое удобное место в дереве. Вызов rnount позволяет объединить
съемные носители в единую интегрированную файловую структуру, не заботясь
о том, на каком из устройств фактически находится файл. Хотя в нашем примере
рассматривались компакт-диски, жесткие диски или их части, часто называемые
раздела.ми (partition), или второстепенными устройства.ми (minor devices), мон­
тируются аналогично. Когда файловая система более не нужна, ее можно демон­
тировать с помощью системного вызова urnount .
MINIX 3 поддерживает блочное кэширование, то есть система кэширует в памяти
последние блоки, к которым были совершены обращения, чтобы избежать слиш­
ком частых обращений к диску. Если блок в кэше модифицирован (например,
вызовом wr i t e ) и система рухнет до того, как обновленный блок будет записан
на диск, и файловая подсистема окажется поврежденной. Чтобы снизить риск
повреждения, важно периодически сбрасывать содержимое кэша на диск, чтобы
в случае сбоя системы терялось только небольшое количество данных. Систем­
ный вызов sync заставляет MINIX 3 сбросить на диск все кэшированные блоки,
измененные с момента считывания. Обычно при старте MINIX вместе с систе­
мой запускается фоновая программа upda t e , каждые 30 секунд выполняющая
вызов sync.
Для работы с каталогами служат еще два вызова, chd i r и chroot . Первый из
них меняет текущий каталог, а второй корневой. Например:
chd i r ( " / u s r / a s t / t e s t " ) ;

После этого вызова открытие файла xy z приведет к открытию файла / u s r /


a s t / t e s t / xy z . Вызов chroot работает аналогично. Как только процесс изме­
няет корневой каталог системы, все абсолютные пути (те, которые начинают­
ся с символа / ), начинают указывать на другой корень. Вы спросите, для чего
это нужно? С целью безопасности: серверные программы протоколов FГР (File
Transfer Protocol - протокол передачи файлов) и НТТР ( HyperText Transfer
Protocol - протокол передачи гипертекста) меняют корневой каталог, чтобы
сделать недоступными все файлы, находящиеся в иерархии выше. Правом за­
пуска вызова chro o t обладают только суперпользователи, но и они выполня­
ют его нечасто.
1 . 4 . Системные вызовы 63

1 4 5 Систем ные вызовы для защиты


. . .

В M INIX 3 для каждого файла определен используемый для его защиты 1 1 -раз­
рядный код режима, иногда также называемый модой (mode). Код режима вклю­
чает 9 бит, по три (чтение, запись и выполнение) для владельца, для членов
группы владельца и для других пользователей. Системный вызов chmod предо­
ставляет возможность изменения кода режима для файла. Например, следующий
вызов предоставит всем, кроме владельца, доступ к файлу только для чтения;
владелец же сможет еще и выполнять файл:
chmod ( " f i l e " , 0644 ) ;

Другие два бита зашиты, 02000 и 04000, называются соответственно битами


SETGID ( Set-Group-Id - установить идентификатор группы) и SETUID (Set­
User-Id - установить идентификатор пользователя). Когда пользователь запус­
кает программу, для которой установлен бит SETUID, то на время выполнения
процесса идентификатор пользователя заменяется идентификатором владельца
программы. Эта специальная возможность широко применяется для того, чтобы
позволить пользователям выполнять функции, доступные только суперпользо­
вателям, такие как создание каталогов. Именно для создания каталога требуется
вызов mknod, доступный только суперпользователю. Если же владельцем про­
граммы mkdi r окажется суперпользователь и для нее будут установлены права
доступа 04755, обычные пользователи смогут запускать ее и тем самым делать
вызов mknod, но весьма ограниченным образом.
Когда процесс исполняет файл, в разрешениях которого выставлен бит SETUID
или SETGID, эффективный идентификатор пользователя или группы отличает­
ся от реального значения. Но иногда для процесса важно знать, чему равны эф­
фективные и реальные значения идентификаторов пользователя и группы. Что­
бы получить эту информацию, процесс может делать системные вызовы getuid
и g e t g i d. Оба этих вызова возвращают одновременно и эффективный, и реаль­
ный идентификаторы, а чтобы получать эти значения по отдельности, служат че­
тыре библиотечные процедуры: ge t u i d, g e t g i d, g e t e u i d, ge t e g i d. Первые
две возвращают реальные значения, вторые две - эффективные.
Для обычных пользователей единственный способ изменить свой идентифика­
тор - запустить программу, у которой установлен бит SETUID. Но для супер­
пользователей существует и другая возможность, предоставляемая системным
вызовом s e t u i d, устанавливающим одновременно реальное и эффективное зна­
чения идентификатора пользователя. Вызов s e t g i d, соответственно, устанав­
ливает реальное и эффективное значения идентификатора группы. Кроме того,
суперпользователь может менять владельца файла при помощи системного вы­
зова chown. Другими словами, у суперпольователя есть множество возможно­
стей нарушать все возможные правила защиты. Это объясняет, почему многие
студенты посвящают столь много времени попыткам стать суперпользователем.
Два последних системных вызова из данной категории могут делаться и обыч­
ными процессами. Первый из них, uma sk, устанавливает системную битовую
64 Глава 1 . Введение

маску, применяемую для маскирования битов прав доступа к файлу при его со­
здании. Например:
шna s k ( 0 2 2 ) ;

Если сделать такой вызов, то при вызовах c r e a t или mknod в правах доступа
к создаваемому объекту будут маскироваться биты 022. Иначе говоря, следую­
щий вызов создаст файл с правами доступа 0755, а не 0777:
creat ( " f i l e " , 0777 ) ;

Кроме того, битовая маска наследуется дочерними процессами, поэтому если


оболочка сразу после входа сделает вызов uma s k, ни одной из запущенных поль­
зователем в этом сеансе программ не удастся создать файл, в который смогут за­
писывать данные другие пользователи.
Когда владельцем программы является суперпользователь и у нее установлен бит
SETUID, то она может обращаться к любому файлу. Но часто программе нужно
знать, имеет ли вызвавший ее пользователь разрешение обращаться к данному
файлу. Простая попытка обращения к файлу ничего не даст, так как она всегда
завершится успехом.
Следовательно, необходимо иметь возможность узнать, разрешен ли доступ для
реального (а не эффективного) идентификатора пользователя. Это можно сделать
с помощью системного вызова ac c e s s . Чтобы проверить возможность чтения,
код режима должен быть равен 4, параметр записи - 2 и параметр исполнения - 1 .
Эти значения можно комбинировать. Например, если код режима равен 6 , то вы­
зов вернет О, если разрешены и запись, и чтение. В противном случае вызов вер­
нет - 1 . Если код режима равен О, проверяется, что файл существует и возможен
поиск в ведущих к нему каталогах.
Хотя механизмы защиты UNIХ-подобных операционных систем, в общем, яв­
ляются схожими, имеются и различия, которые приводят к ошибкам, а следо­
вательно, к уязвимости системы безопасности. Более подробную информацию
см. в [ 1 6] .

1 . 4 . 6 . Системные вызовы
для управления временем
Для работы с системными часами MINIX 3 поддерживает четыре системных вы­
зова. Вызов t ime возвращает текущее время в секундах, причем нулем считается
полночь 1 января 1970 года (имеется в виду начало дня, а не его конец). Кроме
того, чтобы считывать значение системного таймера, нужно иметь возможность
сначала это значение установить. Возможность установить часы дает вызов
s t ime, доступный только суперпользователю. Третий вызов - u t ime, он позво­
ляет владельцу файла изменить значение времени, записанное в индексном узле
файла. У этого вызова довольно ограниченная область применения, но некото­
рым программам он нужен, например, программа t ou c h устанавливает время
в индексном узле файла в соответствие с текущим временем.
1 . 5. Структура о п ерационной системы 65

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

1 . 5 . Стру кту ра опера ционной системы


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

1 . 5 . 1 . Монол итн ые систе м ы


В общем случае организация монолитной системы представляет собой «большой
хаос� . То есть структура как таковая отсутствует. Операционная система пишет­
ся в виде набора процедур, каждая из которых может вызывать другие, когда ей
это нужно. При использовании такой техники каждая процедура системы имеет
строго определенный интерфейс в терминах параметров и результатов и каждая
имеет возможность вызвать любую другую процедуру для выполнения некото­
рой необходимой для нее работы.
Для построения монолитной системы необходимо скомпилировать все отдель­
ные процедуры, а затем связать их в единый объектный файл с помощью ком­
поновщика. Здесь, по существу, совсем не скрываются детали реализации -
каждая процедура видит любую другую процедуру (в отличие от структуры, со­
держащей модули, в которых большая часть информации является локальной
для модуля, и процедуры модуля можно вызвать только через специально опре­
деленные точки входа).
Однако даже такие монолитные системы могут иметь некоторую структуру. При
системных вызовах, поддерживаемых операционной системой, параметры по­
мещаются в строго определенные места - регистры или стек, после чего выпол­
няется специальная инструкция перехвата, известная как вызов ядра, или вызов
супервизора.
Эта инструкция переключает машину из режима пользователя в режим ядра
и передает управление операционной системе. У большинства процессоров
есть два режима работы: режим ядра, предназначенный для ОС, и пользова­
тельский режим, в котором запрещен ввод-вывод и некоторые другие инст­
рукции.
66 Глава 1 . Введение

Теперь самое время посмотреть на то, как выполняются системные вызовы.


Вспомним, что вызов read выглядит следующим образом:
c ount = read ( f d , bu f f e r , nby t e s ) ;

Перед вызовом библиотечной процедуры re ad, фактически осуществляющей


системный вызов read, вызывающая программа сначала помещает параметры
в стек (шаги 1-3 на рис. 1 . 1 2). В силу исторических причин компиляторы язы­
ков С и С++ размещают параметры в обратном порядке (дело в том, что при пере­
даче параметров функции p r i nt f первой в стеке должна располагаться формат­
ная строка). Первый и третий параметры передаются по значению, а второй - по
ссылке. Это означает, что процедура read получает адрес буфера (указываемый
знаком &), а не его содержимое. Далее (на шаге 4) осуществляется фактический
вызов библиотечной процедуры. Данная инструкция ничем не отличается от
обычных вызовов процедур.

Адрес

)
OxFFFFFFFF
Возврат в вызвавший процесс Библиотечная
Переход в режим ядра процедура
Помещение кода для чтения в регистр read

Пространство
пользователя
3
2
Инкремент SP
Call read
Push fd
Push пbuffer
11
] Пользовательская
программа,
вызывающая read
1 Push &bytes

Пространство ядра
(операционная система)
Jl
о
Рис. 1 . 1 2 . Выполнение системного вызова read (fd , buffer, п bytes) за 1 1 шагов
Библиотечная процедура, возможно, написанная на языке ассемблера, обычно по­
мещает номер системного вызова туда, куда требует операционная система (напри­
мер, в регистр, как показано на шаге 5). Затем процедура исполняет инструкцию
t r ap, чтобы переключиться из пользовательского режима в режим ядра и начать
исполнение с определенного адреса внутри ядра (шаг 6). Запущенный код ядра
анализирует номер системного вызова и выбирает для него соответствующий об­
работчик, как правило, с помощью таблицы указателей на обработчики систем­
ных вызовов, индексируемой по номеру системного вызова (шаг 7). Затем вы­
бранный обработчик запускается (шаг 8). По завершении его работы управление
может быть возвращено библиотечной процедуре и передано инструкции, сле­
дующей за t rap (шаг 9). Процедура, в свою очередь, возвращает управление
программе пользователя так же, как любая другая процедура (шаг 10).
1 .5. Структура о п ерационной системы 67

В завершение программа пользователя должна очистить стек: это действие все­


гда выполняется после возврата из процедуры (шаг 1 1). Предполагая, что стек
растет сверху вниз, как это обычно бывает, скомпилированный код увеличивает
указатель стека (Stack Pointer, SP) ровно настолько, чтобы удалить из него пара­
метры, помещенные перед вызовом r ead. После этого программа может смело
приступать к выполнению следующих действий.
Описывая шаг 9, мы намеренно сказали, что управление •может быть возвраще­
но библиотечной процедуре•. Дело в том, что системный вызов способен блоки­
ровать выполнение вызвавшего процесса. Например, если вызов должен считать
ввод с клавиатуры, но пользователь ничего не вводит, вызвавший процесс под­
лежит блокированию. В этом случае операционная система попытается найти
процесс, который можно выполнить в образовавшейся паузе. Когда ожидаемый
ввод, наконец, произойдет, система вернется к обработке блокированного про­
цесса и выполнит шаги 9- 1 1.
Такая организация операционной системы предполагает следующую структуру:
+ Главная программа, которая вызывает требуемую служебную процедуру.
+ Набор служебных процедур, выполняющих системные вызовы.
+ Набор утилит, обслуживающих служебные процедуры.
В этой модели для каждого системного вызова имеется одна служебная процеду­
ра. Утилиты выполняют функции, которые нужны нескольким служебным про­
цедурам. Деление процедур на три уровня иллюстрирует рис. 1 . 13.

Главная
процедура

Сервисные
процедуры

Утилиты
Рис. 1 . 1 3 . Простая структурная модель монолитной системы

1 . 5 . 2 . М ногоуровневые системы
Обобщением этого подхода является организация операционной системы в виде
иерархии уровней. Первой системой, построенной таким образом, была система
ТНЕ, созданная Э. Дейкстрой ( Е. W. Dijkstra) и его студентами в 1 968 году. Она
была простой пакетной системой для голландского компьютера Electrologica Х8,
память которого состояла из 32 К 27-разрядных слов.
68 Глава 1 . Введение

Система включала 6 уровней, как показано в табл. 1 .2 . Уровень О отвечал за


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

Табли ца 1 . 2 . Структура операционной системы ТНЕ


Уровень Функция
5 Оператор
4 Пользовательские программы
з Управление вводом-выводом
2 Взаимодействие оператор-процесс
1 Управление памятью и барабаном
о Выделение процессора и многозадачность
Уровень 1 управлял памятью. Он выделял процессам пространство в оператив­
ной памяти и на магнитном барабане объемом 5 1 2 К слов для тех частей процес­
сов (страниц), которые не помещались в оперативной памяти. Процессы более
высоких уровней не заботились о том, находятся ли они в данный момент в па­
мяти или на барабане. Программы уровня 1 обеспечивали попадание страниц
в оперативную память по мере необходимости.
Уровень 2 управлял взаимодействием между консолью оператора и процессами.
Таким образом, все процессы выше этого уровня имели собственную консоль
оператора. Уровень 3 управлял устройствами ввода-вывода и буферизовал пото­
ки информации к ним и от них. Любой процесс выше уровня 3 вместо того, что­
бы работать с конкретными устройствами, учитывая разнообразные нюансы их
конструкции, мог обращаться к абстрактным устройствам ввода-вывода, обла­
дающим удобными для пользователя характеристиками. На уровне 4 работали
пользовательские программы, которым не надо было заботиться ни о процессах,
ни о памяти, ни о консоли, ни об управлении устройствами ввода-вывода. Про­
цесс системного оператора размещался на уровне 5.
Дальнейшее обобщение многоуровневой концепции бьmо сделано в операцион­
ной системе MULТICS. В ней уровни представляли собой серию концентриче­
ских колец, где внутренние кольца являлись более привилегированными, чем
внешние. Когда процедура внешнего кольца хотела вызвать процедуру внут­
реннего, она должна была выполнить эквивалент системного вызова, то есть
команду t r ap, параметры которой тщательно проверяются перед тем, как вы­
полняется вызов. Хотя операционная система MULTICS являлась частью адрес­
ного пространства каждого пользовательского процесса, аппаратура обеспечива­
ла защиту данных на уровне сегментов памяти, разрешая или запрещая доступ
к отдельным процедурам (в действительности - к сегментам памяти) для записи,
чтения или выполнения.
1 . 5. Структура о пера ционной системы 69

Стоит отметить, что в системе ТНЕ многоуровневая схема представляла собой


исключительно конструктивное решение и все части системы были, в конечном
счете, связаны в один объектный файл, а в M U LTICS механизм разделения
колец действовал во время исполнения на аппаратном уровне. Преимущество
подхода MULTICS заключается в том, что его можно расширить и на структуру
пользовательских подсистем. Например, профессор может написать программу
для тестирования и оценки студенческих программ и запустить ее в кольце п,
в то время как студенческие программы будут работать в кольце п + 1 , так что
они не смогут изменить свои оценки. Аппаратное обеспечение Pentium поддер­
живает кольцевую структуру MULTI CS, однако в настоящий момент ее не ис­
пользует ни одна из распространенных операционных систем.

1 . 5 . 3 . В и ртуал ьные маш и н ы


Исходная версия O S/360 была системой исключительно пакетной обработки.
Однако множество пользователей OS/360 желали работать в системе с разделени­
ем времени, поэтому различные группы программистов как в самой корпорации
IBM, так и вне ее решили написать для этой машины системы разделения времени.
Официальная система разделения времени от IBM, которая называлась TSS/360,
поздно вышла в свет и оказалась настолько громоздкой и медленной, что на нее
перешли немногие. В конечном счете от нее отказались, но уже после того, как ее
разработка потребовала около 50 млн долларов [ 1 06]. Группа из научного цен­
тра I B M в Кембридже, штат Массачусетс, разработала в корне отличающуюся от
нее систему, которую компания I B M в результате приняла как законченный
продукт. Сейчас она широко используется на еще оставшихся мэйнфреймах.
Эта система, в оригинале называвшаяся СР /CMS, а позже переименованная в VM/
370VM/370, была основана на следующем проницательном наблюдении: систе­
ма разделения времени обеспечивает, во-первых, многозадачность, во-вторых,
расширенную машину с более удобным интерфейсом, чем тот, что предоставля­
ется оборудованием напрямую. Система VM/370 основана на полном разделе­
нии этих двух функций.
Сердце системы, называемое монитором виртуальной машины, работает с обору­
дованием и обеспечивает многозадачность, предоставляя верхнему уровню не
одну, а несколько виртуальных машин, как показано на рис. 1 . 1 4 . Но, в отли­
чие от всех других операционных систем, эти виртуальные машины не являются
расширенными. Они не поддерживают файлы и прочие удобства, а предоставля­
ют точные аппаратные копии, включая режимы ядра и пользователя, ввод-вывод
данных, прерывания и все остальное, характерное для реального компьютера.
Поскольку каждая виртуальная машина идентична настоящему оборудованию,
на каждой из них может работать любая операционная система, которая запуска­
ется прямо на аппаратуре. На разных виртуальных машинах могут (а зачастую
так и происходит) функционировать различные операционные системы. На не­
которых из них для обработки пакетов и транзакций работают потомки OS/360,
а на других для интерактивного разделения времени пользователей работает
70 Глава 1 . Введение

однопользовательская интерактивная система CMS ( Conversational Monitor Sys­


tem - система диалоговой обработки).

Виртуальные 370-е

Системные вызовы
Команды ввода-вывода CMS
,__..,._
..,. ��--�����....����---1
..
Прерывания
Прерывания VM/370
«Голое» оборудование 370
Рис. 1 . 1 4. Структура VM/370 с системой CMS

Когда программа операционной системы CMS выполняет системный вызов, он


перехватывается операционной системой на собственной виртуальной машине,
а не на VM/370, как произошло бы, если бы он работал на реальной машине вме­
сто виртуальной. Затем CMS выдает обычные команды ввода-вывода для чтения
своего виртуального диска или другие команды, которые могут понадобиться
системе для выполнения вызова. Эти команды ввода-вывода перехватываются
ОС VM/370, которая выполняет их в рамках моделирования реального оборудо­
вания. При полном разделении функций многозадачности и предоставления рас­
ширенной машины каждая часть может быть намного проще, гибче и удобней
для обслуживания.
Идея виртуальной машины очень часто используется в наши дни, но в несколько
другом контексте: для работы на Pentium (или на других 32-разрядных процессо­
рах от Intel) старых программ, написанных для MS-DOS. При разработке компь­
ютера Pentium и его программного обеспечения обе компании, Intel и Microsoft,
понимали, что возникнет острая потребность в поддержании работы старых про­
грамм на новом оборудовании. Поэтому корпорация Intel создала на процессоре
Pentium режим виртуального процессора 8086. В этом режиме машина действует
как 8086 (с точки зрения программного обеспечения она идентична 8088), вклю­
чая 1 6-разрядную адресацию памяти с ограничением объема памяти в 1 Мбайт.
Такой режим используется системой Windows и другими операционными сис­
темами для запуска программ MS-DOS. Программы запускаются в режиме вир­
туального процессора 8086. Пока они выполняют обычные команды, эти про­
граммы работают напрямую с оборудованием. Но когда программа пытается
обратиться по прерыванию к операционной системе, чтобы сделать системный
вызов, или пытается напрямую осуществить ввод-вывод данных, происходит
прерывание с переключением на монитор виртуальной машины.
Возможны два варианта устройства. Первый: сама система MS-D O S загружена
в адресное пространство виртуальной машины 8086, так что монитор виртуаль­
ной машины только отсылает прерывания назад к MS- D O S , как это происхо­
дит на реальной машине 8086. Когда затем MS- DOS пытается самостоятельно
осуществить ввод-вывод, операция перехватывается и выполняется монитором
виртуальной машины.
1 . 5 . Структура операционной системы 71

В другом варианте монитор виртуальной машины перехватывает первое преры­


вание и сам выполняет ввод-вывод, так как он знает все системные вызовы MS­
DOS и имеет представление о том, что должно делать каждое прерывание. Этот
вариант не столь безупречен, как первый, потому что, в отличие от первого вари­
анта, он корректно моделирует только MS-DOS и никакие другие операционные
системы. С другой стороны, он намного быстрее работает, так как не требует за­
пуска MS- DOS для выполнения ввода-вывода. Существует еще один недостаток
фактического запуска MS-DOS в режиме виртуальной машины 8086: MS-DOS
очень часто оперирует флагом разрешения/запрещения прерываний, а модели­
рование этого требует больших затрат.
Стоит отметить, что ни один из двух описанных методов в действительности не
обеспечивает то, чем бьmа система VM/370, потому что смоделированная машина
представляет собой только машину 8086, а не полноценный процессор Pentium.
В системе VM/370 можно бьmо запустить на виртуальной машине саму систему
VM/370. Даже для самых старых версий Windows необходим как минимум про­
цессор 286, а, значит, их невозможно запустить на виртуальной машине 8086.
Некоторые реализации виртуальных машин предлагаются рынку как коммерче­
ские. Для компаний, предоставляющих услуги веб-хостинга, экономически вы­
годнее использовать несколько виртуальных машин на одном мощном сервере
(возможно, многопроцессорном), нежели несколько компьютеров, обслуживаю­
щих отдельные сайты. Именно для такого применения предназначены машины
VMWare и Microsoft Virtual РС. В качестве имитируемых дисков для гостевых
систем (guest systems) эти программы используют большие файлы на физиче­
ской системе. Для эффективности они анализируют двоичный код программ
гостевой системы и разрешают выполнение безопасного кода непосредственно
на физическом аппаратном обеспечении, перехватывая инструкции, осуществ­
ляющие системные вызовы. Подобные системы также полезны в сфере обучения.
Например, студенты, изучающие О С MINIX 3, могут работать с ней как с госте­
вой операционной системой при помощи виртуальной машины VMWare на фи­
зическом компьютере с Windows, Linux или UNIX без риска повредить другое
установленное программное обеспечение. Многие преподаватели с опаской отно­
сятся к совмещению своих занятий с курсом операционных систем на одних и тех
же компьютерах, поскольку ошибки, совершаемые студентами при работе с О С,
способны повредить или вовсе уничтожить данные на дисках.
Еще одна область применения виртуальных машин, хотя и несколько специ­
фичная, - это исполнение Jаvа-программ. Вместе с языком Java компания Sun
Microsystems изобрела виртуальную машину (то есть компьютерную архитекту­
ру) ]VM (Java Virtual Machine - виртуальная машина Java). Компилятор Java
создает код для JVM, который, как правило, исполняется программным JVM -
интерпретатором. Преимущество такого подхода заключается в том, что JVМ­
код может быть передан через Интернет и исполнен на любом компьютере, осна­
щенном JVМ-интерпретатором. Двоичный код, созданный компилятором, к приме­
ру, для SPARC или Pentium, нельзя использовать где угодно с такой же просто­
той. Разумеется, фирма Sun могла бы сначала предложить компилятор двоичного
72 Глава 1 . Введение

кода для SP ARC, а затем его интерпретатор, однако интерпретировать JVM -ар­
хитектуру значительно легче. У JVM есть и другое важное преимущество: если
интерпретатор реализован корректно (что отнюдь не является нетривиальным),
входящие JVМ-программы можно проверять на безопасность и затем испол­
нять в безопасной среде, чтобы избежать похищения дю;1ных и деструктивных
последствий.

1 . 5 . 4 . Экзоядра
В системе VM/370 каждый процесс пользователя получает точную копию настоя­
щей машины. На Pentium, в режиме виртуальной машины 8086, каждый пользо­
вательский процесс получает точную копию другой машины. Шагнув несколько
дальше, исследователи из Массачусетсского технологического института изобре­
ли систему, которая обеспечивает каждого пользователя абсолютной копией ре­
ального компьютера, но. со своим подмножеством ресурсов [43, 75]. Например,
одна виртуальная машина может получить блоки на диске с номерами от О до
1 023, следующая - от 1 024 до 2047 и т. д.
На нижнем уровне в режиме ядра работает программа, которая называется экзо­
ядро (exokernel). В ее задачу входит распределение ресурсов для виртуальных
машин, а после этого проверка их использования (то есть отслеживание попыток
машин задействовать чужой ресурс). Каждая виртуальная машина на уровне
пользователя может работать с собственной операционной системой, как на
VM/370 или виртуальных машинах 8086 для Pentium, с той разницей, что каж­
дая машина ограничена набором ресурсов, которые она запросила и которые ей
бьши предоставлены.
Преимущество схемы экзоядра заключается в том, что она позволяет обойтись
без уровня отображения. При других методах работы каждая виртуальная маши­
на считает, что она использует собственный диск с нумерацией блоков от О до
некоторого максимума. Поэтому монитор виртуальной машины должен поддер­
живать таблицы преобразования адресов на диске (и всех других ресурсов). Не­
обходимость преобразования отпадает при наличии экзоядра, которому нужно
только хранить запись о том, какой виртуальной машине выделен данный ре­
сурс. Подобный подход имеет еще одно преимущество: он отделяет многозадач­
ность (в экзоядре) от операционной системы пользователя (в пользовательском
пространстве) с меньшими затратами, так как для этого ему необходимо всего
лишь не допускать вмешательства одной виртуальной машины в работу другой.

1 . 5 . 5 . Модел ь клиент-сервер
Система VM/370 сильно выигрывает в простоте благодаря переносу значитель­
ной части кода (обеспечивающего расширенную машину) традиционной опера­
ционной системы на верхний уровень, в систему CMS. Однако VM/370 и при
этом останется сложной комплексной программой, потому что моделирование
1 . 5 . Структура о п ерационной системы 73

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

Клиентский Клиентский Сервер Сервер


процесс процесс процессов терминала
Сервер Сервер
файлов памяти
} Режим
пользователя
Микроядро
} Режим ядра
Клиент посылает сообщение
серверу и, таким образом,
получает выполнение операции
Ри с . 1 . 1 5 . Модель клиент-сервер

Другое преимущество модели клиент-сервер заключается в ее простой адапта­


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

Машина 1 Машина 2 Машина 3 Машина 4


Клиент Сервер файлов Сервер процессов Сервер терминала
Ядро Ядро Ядро Ядро

Сеть
Сообщение от клиента серверу
Рис . 1 . 1 6 . Модель клиент-сервер в распределенной системе
74 Глава 1 . Введение

Рассказанная история о ядре, управляющем передачей сообщений от клиентов


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

1 . 6 . Кр атки й обзор остальны х гл ав


Типичные операционные системы состоят из четырех основных компонентов,
обеспечивающих управление процессами, устройствами ввода-вывода, памятью
и файлами. MINIX 3 в этом отношении не является исключением. Следующие
четыре главы будут посвящены указанным четырем компонентам, по одной гла­
ве на каждый. В главе 6 содержится перечень рекомендуемой литературы и биб­
лиографии.
Главы о процессах, вводе-выводе, управлении памятью и файловых системах име­
ют одну и ту же общую структуру. Сначала излагаются общие принципы. Затем
идет обзор соответствующего элемента MINIX 3 (эта информация применима
и к UNIX). Наконец, подробно рассматривается реализация в MINIX 3. Раздел,
посвященный реализации, можно без потери целостности изложения пропустить
или коснуться вскользь. Те же читатели, которые заинтересованы в изучении ра­
боты реальной ОС ( MINIX 3), должны прочитать все разделы.
Во п росы и задания 75

Резюме
Операционную систему можно рассматривать с двух точек зрения: как менеджер
ресурсов и как расширенную машину. Как менеджер ресурсов операционная
система рационально управляет различными частями системы. С точки зрения
расширенной машины, работа операционной системы состоит в предоставлении
пользователям виртуальной машины, более удобной, чем настоящий компьютер.
Операционные системы имеют достаточно долгую историю развития, которая на­
чинается с тех дней, когда операционные системы заменили оператора, и про­
должается до современных многозадачных систем.
Сердцем любой операционной системы является набор системных вызовов, ко­
торые она может обработать. Они говорят о том, что реально делает операцион­
ная система. Мы рассмотрели шесть групп системных вызовов для MINIX 3.
Первая группа обеспечивает создание и завершение процессов. Вторая группа
предназначена для работы с сигналами. Третья - для чтения и записи файлов.
Четвертая нужна для управления каталогами. Пятая включила в себя управле­
ние защитой, а шестая - управление временем.
Операционные системы могут быть структурированы по-разному. В наиболее
общем случае структурирование может быть таким: монолитные системы, иерар­
хические многоуровневые системы, виртуальные машины, экзоядра, модель кли­
ент-сервер.

В оп рос ы и з адан ия
1 . Каковы две главные функции операционной системы?
2. В чем различие между режимом ядра и пользовательским режимом? Почему
это различие представляет важность для операционной системы?
3. Что такое многозадачность?
4. Что такое подкачка? Как вы считаете, будут ли передовые персональные ком­
пьютеры будущего поддерживать подкачку данных в качестве стандартной
функции?
5. На ранних компьютерах чтение или запись каждого байта данных управля­
лись напрямую центральным процессором (то есть тогда не было прямого
доступа к памяти). Какой смысл имеет это понятие для многозадачности?
6. Почему системы разделения времени не были широко распространены на
компьютерах второго поколения?
7. Какая из следующих команд должна быть разрешена только в режиме ядра:
1 ) отключение всех прерываний;
2) чтение счетчика даты/времени;
3) изменения счетчика даты/времени;
4) изменение схемы распределения памяти.
76 Глава 1 . Введе н ие

8. Перечислите основные различия между операционной системой для персо­


нального компьютера и для мэйнфрейма.
9. Приведите причину, по которой фирменная операционная система с закры­
тым исходным кодом (к примеру, Windows) должна быть лучше по качеству
по сравнению с операционной системой, имеющей открытый исходный код
(например, Linux). Приведите причину, по которой, напротив, операционная
система с открытым исходным кодом должна быть лучше по качеству, чем
фирменная операционная система с закрытым исходным кодом.
10. У файла в MINIX идентификатор владельца равен 12 и идентификатор груп­
пы равен 1. Файлу присвоены следующие разрешения: rwxr - x - - . К этому
файлу пытается обратиться другой пользователь, у которого идентификатор
пользователя равен 6, а идентификатор группы - 1. Что произойдет?
1 1 . Как в свете того, что существование суперпользователя может привести к мно­
жеству проблем безопасности, объяснить сам факт существования концепции
суперпользователя?
12. Все версии UNIX поддерживают именование файлов с использованием двух
типов путей - абсолютных (относительно корня) и относительных (относи­
тельно рабочего каталога). Можно ли отказаться от одного из типов, сохра­
нив лишь другой? Если да, какой из них следует сохранить?
13. Почему таблица процессов необходима в системах разделения времени? Нуж­
на ли она в системах, где в каждый момент времени существует единствен­
ный процесс, занимающий все ресурсы компьютера до окончания своего вы­
полнения?
14. В чем заключается существенная разница между блочным специальным фай­
лом и символьным специальным файлом?
15. Что случится, если в MINIX 3 пользователь 2 создаст ссылку на файл, кото­
рым владеет пользователь 1, затем пользователь 1 удалит файл, и, наконец,
пользователь 2 попытается прочитать файл?
1 6. Являются ли каналы необходимой функцией операционной системы? Какая
основная функциональность утрачивается при отсутствии каналов?
17. Современные потребительские товары, такие как цифровые камеры и стерео­
системы, зачастую оснащены дисплеем, с помощью которого можно вводить
команды и просматривать результаты их выполнения. Обычно в такие това­
ры встроена простейшая операционная система. Какой области программного
обеспечения персонального компьютера соответствует обработка команд че­
рез дисплей стереосистемы и камеры?
18. В операционной системе Windows не поддерживается системный вызов fork,
однако это не лишает ее возможности создания новых процессов. Сформули­
руйте научную догадку о том, какова семантика системного вызова Windows,
создающего новый процесс.
19. Почему системный вызов chroot разрешено выполнять только суперпользо­
вателю (подсказка: не забывайте о безопасности)?
Во просы и задания 77

20. Рассмотрите список системных вызовов, приведенный в табл. 1 . 1 . Какие,


по вашему мнению, вызовы используются наиболее часто? Поясните ответ.
2 1 . Предположим, что компьютер исполняет 1 млрд инструкций в секунду, а сис­
темный вызов включает 1 ООО инструкций, считая прерывание и переключе­
ние контекста. Сколько системных вызовов в секунду способен обработать
компьютер при условии, что половина ресурсов процессора тратится на ис­
полнение кода приложений?
22. Мы рассматривали системный вызов mknod, но не рассматривали вызов rmnod.
Означает ли это, что вы должны очень вдумчиво подходить к созданию узлов
подобным образом, поскольку нет способа их удаления?
23. Зачем в MINIX 3 все время работает фоновая программа upda t e ?
2 4 . Имеет л и смысл игнорировать сигнал s i ga l arm?
25. Модель клиент-сервер популярна в распределенных системах. Может ли она
использоваться в системах из одного компьютера?
26. Первые версии процессора Pentium не могли поддерживать монитор вирту­
альной машины. Какая основополагающая характеристика машины позволя­
ет виртуализировать ее?
27. Напишите программу (или набор программ), чтобы протестировать все сис­
темные вызовы MINIX 3. Произведите каждый вызов с разными параметрами,
в том числе и с некорректными, чтобы увидеть реакцию системы на ошибки.
28. Напишите оболочку, напоминающую показанную в листинге 1 . 1 , но достаточ­
но работоспособную, чтобы ее можно было протестировать. Желательно так­
же наделить ее некоторыми дополнительными функциями, включая перена­
правление ввода и вывода, поддержку каналов, обработку фоновых заданий.
Глава 2
Пр о ц е ссы

С этой главы мы начнем детальное изучение устройства и работы операцион­


ных систем, в основном MINIX 3. Основополагающей концепцией для любой
операционной системы является процесс абстракция запущенной программы.
-

Все остальное базируется на концепции процесса, поэтому представляется край­


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

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

2 . 1 . 1 . Модел ь процессов
В модели процесса все функционирующее на компьютере программное обеспе­
чение, иногда включая собственно операционную систему, организовано в виде
набора логическ:и упорядоченных процессов, или, для краткости, просто процессов.
Процессом является выполняемая программа, вместе с текущими значениями
счетчика команд, регистров и переменных. С позиций данной абстрактной модели
у каждого процесса есть собственный виртуальный центральный процессор.
2. 1 . З н акомство с пр оцессами 79

На самом деле, разумеется, реальный процессор переключается с процесса на про­


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

Один счетчик команд


Четыре счетчика команд
д Переключение 8
�----1 между ф
5 о
В процессами
1----�
а.
i:: с

с в

А
D Время --.
а б в

Иллюстрация многозадачности:
Рис. 2 . 1 . а - четыре программы в многозадачном.режиме;
б- принципиальная модель четырех независимых логически упорядоченных процессов;
в в каждый момент времени активна только одна программа
-

Поскольку процессор переключается между программами, скорость, с которой


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

Различие между процессом и программой трудноуловимо, но тем не менее оно


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

2 . 1 . 2 . Создание процессов
Операционной системе нужен способ, позволяющий удостовериться в существо­
вании всех обслуживаемых процессов. В простейших системах, а также в систе­
мах, разработанных для выполнения единственного приложения (таких как кон­
троллер микроволновой печи), нетрудно реализовать ситуацию, в которой все
требуемые процессы уже присутствуют в системе при ее запуске. Однако в мно­
гоцелевых системах требуется способ создания и завершения процессов по мере
необходимости. Сейчас мы рассмотрим несколько связанных с этим вопросов.
Существуют четыре основных события, приводящие к созданию процессов:
1 . Инициализация системы.
2. Исполнение запущенным процессом системного вызова создания процесса.
3. Запрос пользователя на создание процесса.
4. Инициализация пакетного задания.
При загрузке операционной системы зачастую создаются несколько процессов.
Некоторые из них являются приоритетными, то есть процессами, взаимодейст­
вующими с пользователями (людьми) и выполняющими для них определенную
работу. Другие процессы являются фоновыми: они не связаны с конкретными
2 . 1 . З н акомство с п роцессами 81

пользователями, однако имеют определенное функциональное назначение. На­


пример, фоновый процесс может принимать входящие запросы на открытие веб­
страниц, размещенных на компьютере, �просыпаясь� каждый раз при получении
запроса и обслуживая его. Процессы, выполняемые в фоновом режиме и осуще­
ствляющие определенную деятельность, такую как обслуживание веб-страниц,
печать и т. д., часто называют демона.ми. В больших системах обычно работают
десятки демонов. В MINIX 3 список выполняемых процессов можно получить
при помощи программы p s .
Процессы могут создаваться н е только при загрузке операционной системы, но
и во время ее работы. Зачастую запущенный процесс создает один или несколь­
ко новых процессов, которые помогают ему выполнять свои функции. Создание
новых процессов особенно полезно в случаях, когда работу можно представить
как совокупность взаимодействующих, но в остальном независимых друг от дру­
га процессов. Так, при компиляции большой программы утилита make сначала
вызывает компилятор языка С, чтобы преобразовать исходные файлы в объект­
ный код, а затем обращается к программе ins t a l l , чтобы скопировать программу
в целевой каталог, задать владельца, разрешения и прочие атрибуты. В MINIX 3
сам компилятор языка С представляет собой набор отдельных совместно рабо­
тающих программ: препроцессор, синтаксический анализатор, генератор кода на
языке ассемблера, ассемблер и редактор связей.
В интерактивных системах пользователи могут запустить программу путем вво­
да команды. В MINIX 3 виртуальные консоли позволяют пользователю запус­
тить программу (скажем, компилятор), затем переключиться на другую консоль
и запустить еще одну программу, например редактор документов, в то время как
компилятор выполняет свою работу.
Последнее событие, приводящее к созданию процессов, поддерживается только
в системах пакетной обработки, устанавливаемых на больших мэйнфреймах.
В таких системах пользователи могут вводить в систему пакетные задания (в том
числе удаленно). Когда операционная система обнаруживает, что для запуска
еще одного задания достаточно ресурсов, она создает новый процесс и запускает
задание, извлекаемое из входной очереди.
Во всех описанных случаях новый процесс создается путем выполнения соответ­
ствующего системного вызова существующим процессом. В качестве последнего
может выступать пользовательский процесс, системный процесс, активизируе­
мый клавиатурой или мышью, а также менеджер пакетной обработки. Процесс
исполняет системный вызов, запрашивающий у операционной системы создание
нового процесса, и указывает (прямо или косвенно), какая программа должна
быть запущена в этом процессе.
Для создания нового процесса в MINIX 3 существует единственный системный
вызов f o rk. Он создает точную копию вызывающего процесса. По завершении
вызова два процесса, родительский и дочерний, имеют одинаковые образы памя­
ти, строки окружения и открытые файлы. Как правило, затем дочерний процесс
исполняет вызов execve или аналогичный системный вызов, чтобы изменить
свой образ памяти и запустить новую программу. К примеру, когда пользователь
82 Глава 2. Процессы

вводит в оболочку команду s o r t , оболочка создает дочерний процесс, который


и исполняет команду. Смысл такого двухэтапного действия заключается в том,
что дочерний процесс получает возможность манипулировать своими дескрип­
торами файлов между вызовами f o rk и е х е с , чтобы перенаправить стандарт­
ные потоки ввода, вывода и ошибок.
Как в UNIX, так и в MINIX 3 родительский и дочерний процессы имеют собст­
венные адресные пространства. Если какой-либо процесс изменит слово в своем
адресном пространстве, это не затронет другой процесс. Адресное пространство
дочернего процесса является копией адресного пространства родителя, однако не
совпадает с ним; у двух процессов нет общей памяти для записи (как и некоторые
реализации UNIX, MINIX 3 поддерживает возможность использования процес­
сами общего кода, поскольку его невозможно модифицировать). Тем не менее
процесс-потомок может совместно с родителем использовать другие ресурсы, на­
пример открытые файлы.

2 . 1 3 Завершение п роцессов
. .

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


нако ничто не вечно, и это в полной мере справедливо в отношении процессов.
Рано или поздно процесс завершается одним из следующих четырех способов:
1. Нормальное завершение (добровольное).
2. Завершение вследствие ошибки (добровольное).
3. Завершение вследствие фатальной ошибки (принудительное).
4. Уничтожение другим процессом (принудительное).
Большинство процессов завершаются по причине окончания своей работы. После
компиляции переданной ему программы компилятор исполняет системный вызов,
чтобы уведомить операционную систему о том, что работа завершена. В операци­
онной системе MINIX 3 таким вызовом является exi t. Программы с экранным
интерфейсом также поддерживают внешнее завершение. Например, все редакто­
ры имеют комбинацию клавиш, нажатие которой пользователем приводит к со­
хранению рабочего файла, удалению временных файлов и завершению работы.
Вторая причина завершения процесса - обнаружение неисправимой ошибки.
Например:
се f oo . c

Если пользователь введет эту команду, но файла foo. c не существует, компиля­


тор просто закончит работу.
Третья причина завершения - ошибка, вызванная процессом, например, из-за не­
правильно написанной программы. Возможно, программа содержит недопустимую
инструкцию, ссьmку на несуществующую область памяти или пытается выпол­
нить деление на ноль. В MINIX 3 процесс может сообщить операционной системе
о намерении обрабатывать некоторые ошибки самостоятельно. В этом случае при
появлении ошибки процесс получает сигнал ( прерывается), а не завершается.
2. 1 . Знакомство с п роцессами 83

Четвертая причина завершения процесса - исполнение одним процессом сис­


темного вызова, требующего у операционной системы завершения другого про­
цесса. В ОС MINIX 3 таким вызовом является ki l l . Разумеется, запрашиваю­
щий процесс должен иметь соответствующие права. В некоторых операционных
системах при завершении процесса, независимо от причины, все созданные им
процессы также подлежать немедленному завершению. Это замечание не каса­
ется MINIX 3.

2. 1 4
. . Иерархии п роцессов
В некоторых системах после порождения одного процесса другим между дочер­
ним и родительским процессами продолжает существовать определенная связь.
Дочерний процесс может также создавать новые процессы, тем самым порождая
иерархию. В отличие от растений и животных, размножающихся половым путем
и имеющих двух родителей, процессы имеют единственного родителя. В то же
время число дочерних процессов может быть любым - О, 1, 2 или более.
В операционной системе MINIX 3 процесс, его детей и прочих потомков можно
объединить в группу. Сигнал, посланный пользователем с клавиатуры, может
быть передан всем членам группы процессов, в данный момент связанной с клавиа­
турой (как правило, это все процессы, созданные в текущем окне). Это - сигналь­
ная зависимость. Каждый член группы процессов может по-своему отреагировать
на переданный группе сигнал: перехватить, проигнорировать или выполнить
действие по умолчанию (завершиться).
В качестве простого примера использования деревьев процессов рассмотрим ини­
циализацию операционной системы MINIX 3. В ее загрузочном образе имеются
два специальных процесса - сервер реинкарнации (reincarnation server) и i n i t .
В функции сервера реинкарнации входит запуск и перезапуск драйверов и дру­
гих серверов. Первое, что он делает, - блон:ируется и ожидает сообщения, указы­
вающему ему, что нужно создать.
Процесс i n i t запускает сценарий / e t c / rc, с помощью которого передает сер­
веру реинкарнации команды запуска драйверов и серверов, отсутствующих в за­
грузочном образе. В результате все драйверы и серверы, запущенные подобным
образом, являются потомками сервера реинкарнации. Это означает, что если кто­
либо из них завершит выполнение, сервер реинкарнации получит об этом уве­
домление и сможет перезапустить (реинкарнировать) драйвер или сервер. Такой
механизм позволяет MINIX 3 справляться с авариями драйверов и серверов, по­
скольку в этом случае перезапуск осуществляется автоматически. На практике
переустановить драйвер значительно проще, чем сервер, поскольку влияние драй­
вера на систему меньше. Мы не утверждаем, что наше решение работает идеаль­
но, и ведем работу над его улучшением.
Далее процесс i n i t считывает конфигурационный файл / e t c / t tyt ab, чтобы
получить информацию о существующих реальных и виртуальных терминалах.
Для каждого терминала ini t создает новый процесс get ty с помощью системно­
го вызова f o rk, отображает приглашение для входа в систему и ожидает ввода.
84 Глава 2. Процессы

После ввода имени ge t ty запускает системным вызовом ехес процесс l o g i n,


передав ему имя в качестве аргумента. Если вход пользователя в систему оказал­
ся удачным, l o g i n также при помощи вызова е х е с запускает оболочку поль­
зователя. Таким образом, оболочка является дочерним процессом по отношению
к i n i t. Команды пользователя порождают дочерние процессы для оболочки,
являющиеся �внуками� i n i t. Описанная последовательность событий демон­
стрирует использование деревьев процессов. Коды сервера реинкарнации, про­
цесса ini t и оболочки не представлены в данной книге, поскольку продолжать
обсуждение до бесконечности невозможно. Тем не менее теперь у вас есть базо­
вое представление о концепции.

2 . 1 . 5 . Состоя ния п роцессов


Несмотря на то что процесс является независимым объектом со своим счетчи­
ком команд, регистрами, стеком, открытыми файлами, аварийными сигналами
и прочими атрибутами внутреннего состояния, существует необходимость его
взаимодействия и синхронизации с другими процессами. Например, выходные
данные одного процесса могут служить входными данными для другого процес­
са. В этом случае необходимо обеспечить передачу данных между процессами.
Например:
cat chapt e r l chap t e r 2 chap t e r З 1 grep t r e e

В этой команде оболочки первый процесс cat объединяет и распечатывает три


файла. Второй процесс grep отбирает все строки, содержащие слово t r e e . В за­
висимости от относительных скоростей процессов (скорости зависят от относи­
тельной сложности программ и процессорного времени, предоставляемого каж­
дому процессу) может получиться, что процесс grep уже готов к запуску, но
входных данных для него еще нет. В этом случае процесс блокируется до поступ­
ления входных данных.
Процесс блокируется потому, что с точки зрения логики он не в состоянии продол­
жать свою работу (обычно это связано с отсутствием входных данных, ожидае­
мых процессом). Также возможна ситуация, когда активный и работоспособный
процесс останавливается, так как операционная система решила предоставить
на время процессор другому процессу. Эти ситуации принципиально различны.
В первом случае приостановка выполнения является внутренней проблемой
(например, невозможно обработать командную строку пользователя до того, как
она введена). Во втором случае проблема является технической (нехватка про­
цессоров для каждого процесса). На рис. 2.2 представлена диаграмма состояний,
показывающая три возможных состояния процесса:
1. Выполнение (процесс использует процессор).
2. Готовность (процесс временно приостановлен, чтобы позволить выполняться
другому процессу).
3. Блокировка (процесс не может быть запущен прежде, чем произойдет некое
внешнее событие) .
2 . 1 . Знакомство с п роцессами 85

1 . Процесс блокируется, ожидая входных данных


2. Планировщик выбирает другой процесс
3. Планировщик выбирает этот процесс
4. Доступны входные данные
Рис. 2.2. Процесс может находиться в состоянии выполнения, готовности или блокировки.
Стрелками показаны возможные переходы между состояниями
С позиций логики первые два состояния схожи. В обоих вариантах процесс мо­
жет быть запущен, только во втором случае процессор недоступен. Третье со­
стояние отличается тем, что запустить процесс невозможно, какой бы ни была
загруженность процессора.
Как показано на рисунке, между этими тремя состояниями допустимы четыре
перехода. Переход 1 происходит, когда процесс обнаруживает, что продолжение
работы невозможно. В некоторых системах процесс должен выполнить систем­
ный запрос, например Ы о с k или pau s e, чтобы оказаться в состоянии блоки­
ровки. В других системах, например в MINIX 3, процесс автоматически блоки­
руется, если при считывании из канала или специального файла (предположим,
терминала) входные данные не обнаружены.
Переходы 2 и З вызываются частью операционной системы, называемой пла­
нировщиком процессов, так что сами процессы даже не знают о существовании
этих переходов. Переход 2 происходит, когда планировщик решает, что пора
предоставить процессор следующему процессу. Переход З имеет место, когда
все остальные процессы уже исчерпали предоставленное им время, и процессор
снова возвращается к первому процессу. Вопрос планирования (когда следует
запустить очередной процесс и на какое время) сам по себе достаточно важен.
Было разработано множество алгоритмов, призванных сбалансировать требо­
вания по эффективности системы в целом и каждого процесса в отдельно­
сти. Мы рассмотрим планирование и изучим некоторые его алгоритмы позднее
в этой главе.
Переход 4 происходит с появлением внешнего события, ожидавшегося процес­
сом (например, поступления входных данных). Если в этот момент не запущен
какой-либо другой процесс, происходит переход З, и процесс запускается. В про­
тивном случае процессу придется некоторое время находиться в состоянии го­
товности, пока не освободится процессор.
Модель процессов упрощает представление о внутреннем поведении системы.
Некоторые процессы запускают программы, выполняющие команды, введенные
с клавиатуры пользователем. Другие процессы являются частью системы и об­
служивают такие задания, как выполнение запросов файловой подсистемы,
управление запуском диска или магнитного накопителя. В случае дискового пре­
рывания система может остановить текущий процесс и запустить дисковый про­
цесс, который был заблокирован в ожидании этого прерывания. Слово �может�
использовано потому, что выполнение или невыполнение системой указанных
86 Глава 2. Процессы

действий зависит от приоритета текущего процесса по отношению к приоритету


дискового драйвера. Тем не менее важнее здесь то, что вместо прерываний мы
можем представлять себе дисковые процессы, процессы пользователя, термина­
ла и т. п., блокирующиеся на время ожидания событий. Когда событие происхо­
дит (информация считывается с диска или клавиатуры), блокировка снимается,
после чего процесс может быть запущен.
Рассмотренный подход описывается моделью, представленной на рис. 2.3. Ниж­
ний уровень операционной системы - это планировщик, выше него - все мно­
жество процессов. За обработку прерываний и детали, связанные с остановкой
и запуском процессов, отвечает планировщик, который является, по сути, совсем
небольшой программой. Вся остальная часть операционной системы упорядо­
чена в виде набора процессов. Модель, изображенная на рисунке, используется
в операционной системе MINIX 3. Разумеется, нижний уровень состоит не толь­
ко из планировщика: он также обеспечивает обработку прерываний и взаимо­
действие между процессами. Тем не менее представленная модель �в первом
приближении� адекватно демонстрирует основную структуру.

Процессы
о 1
. . .
п-2 n-1

Планировщик
Рис. 2 . 3 . Нижний уровень операционной системы отвечает за прерывания
и планирование. Выше расположены логически упорядоченные процессы

2 . 1 . 6 . Реал изация п роцессов


Для реализации модели процессов операционная система поддерживает таб­
лицу (массив структур), называемую таблицей процессов, в которой для каждого
процесса имеется по одной записи. ( Некоторые авторы называют эти записи
блоками управления процессом.) Записи таблицы содержат информацию о со­
стоянии процесса, счетчике команд, указателе стека, распределении памяти,
состоянии открытых файлов, использовании и распределении ресурсов, ава­
рийных и прочих сигналах, а также всю остальную информацию, которую не­
обходимо сохранять при переключении из состояния выполнения в состояние
zотовности, чтобы позже процесс мог быть запущен снова так, как будто он ни­
когда не останавливался.
В MINIX 3 за взаимодействие между процессами, управление памятью и фай­
лами ответственны различные модули из состава системы, поэтому таблица
разбита на разделы, где каждый модуль поддерживает относящиеся к нему
поля. В табл. 2 . 1 представлены некоторые наиболее важные поля типичной
системы. К теме данной главы относятся только поля первой колонки; осталь­
ные колонки лишь демонстрируют информацию, используемую в других об­
ластях системы.
2. 1 . Знакомство с п роцессами 87

Некоторые поля типовой записи таблицы процессов MINIX З. Поля


Таблица 2 . 1 .
сгруппированы для ядра, модулей управления процессами и файловой системой
Ядр о Управление процессами Управлен ие файлами
Регистры Указатель на текстовый сегмент Маска UMASK
Счетчик команд Указатель на сегмент данных Корневой каталог
Слово состояния Указатель на сегмент стека Рабочий каталог
программы Статус завершения Дескрипторы файлов
Указатель стека Состояние сигналов Реальный идентификатор
Состояние процесса Идентификатор процесса пользователя
Текущий приоритет Родительский процесс Эффективный
Максимальный приоритет Группа процесса идентификатор
Оставшееся число тактов пользователя
Процессорное время дочернего Реальный идентификатор
Размер кванта процесса группы
Использованное Реальный идентификатор Эффективный
процессорное время пользователя (UID) идентификатор группы
Указатели очереди Эффективный идентификатор Управление
сообщений пользователя Область сохранения для
Биты действующих Реальный идентификатор группы (GID) чтения/записи
сигналов Эффективный идентификатор группы Параметры системного
Различные флаги Файловая информация о совместном вызова
Имя процесса использовании текста Различные битовые флаги
Битовые маски для сигналов
Различные битовые флаги
Имя процесса
Теперь, после знакомства с таблицей процессов, настало время сказать несколь­
ко слов о том, как создается иллюзия параллельного исполнения процессов на
машине с одним процессором и несколькими устройствами ввода-вывода. Затем
мы познакомимся с работой планировщика в MINIX 3 (см. рис. 2.3), но все ска­
занное относится и к большинству других современных ОС. С каждым классом
устройств ввода-вывода (гибкий диск, жесткий диск, таймер, терминал) связана
структура данных, называемая таблицей дескрипторов прерываний. Самой важ­
ной частью каждой записи этой таблицы является вектор прерываний. Вектор
прерываний содержит адрес процедуры обработки прерываний. Представьте, что
в момент дискового прерывания работал пользовательский процесс 23. Содер­
жимое счетчика команд процесса, слово состояния программы и , возможно, один
или несколько регистров записываются в (текущий) стек аппаратными средства­
ми. Затем происходит переход по адресу, указанному в векторе прерывания дис­
ка. Вот и все, что делают аппаратные средства. С этого момента вся остальная
обработка прерывания производится программно, обычно стандартной процеду­
рой-обработчиком.
Обработка любого прерывания начинается с сохранения регистров, часто в блоке
управления текущим процессом в таблице процессов. Затем информация, поме­
щенная в стек прерыванием, удаляется, и указатель стека переставляется на вре­
менный стек, используемый программой обработки процесса. Такие действия,
88 Глава 2. П роцессы

как сохранение регистров и установка указателя стека, невозможно даже выра­


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

2 . 1 . 7. П рограммные потоки
В традиционных операционных системах у каждого процесса есть адресное
пространство и один поток управления. В сущности, это почти полностью оп­
ределяет процесс. Тем не менее нередко возникают ситуации, в которых же­
лательно иметь несколько потоков управления, квазипараллельно выполняю­
щихся в одном адресном пространстве так, как будто они представляют собой
отдельные процессы (за исключением общности адресного пространства). Такие
2 . 1 . Знакомство с п роцессами 89

потоки управления обычно называются программ ными потоками, или легковес­


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

Процесс 1 Процесс 1 Процесс 1 Процесс


1

Пространство
пользователя
Поток Поток
Пространство Ядро Ядро
ядра
а б
Рис. 2 . 4 . Модель программных потоков: атри процесса с собственным программным
-

потоком каждый; б один процесс с тремя программными потоками


-

В качестве примера приложения, рассчитанного на многопоточность, рассмот­


рим веб-браузер. Неб-страницы зачастую содержат множество картинок неболь­
шого размера. Для воспроизведения каждой картинки браузер должен устано­
вить отдельное соединение с сайтом, которому принадлежит страница, и послать
запрос на ее получение. Установка и разрыв соединений отнимают много вре­
мени. При поддержке браузером многопоточности можно загружать несколько
90 Глава 2 . Процессы

картинок одновременно, что значительно ускоряет загрузку страницы, посколь­


ку при небольшом размере большинства изображений установка соединений от­
нимает больше времени, чем передача данных.
Когда в одном адресном пространстве имеется несколько программных пото­
ков, то некоторые из полей, показанных в табл. 2 . 1 , относятся уже не к процессам,
а к отдельным программным потокам. Поэтому необходима дополнительная таб­
лица, каждая запись в которой будет описывать отдельный поток. Среди прочих
в этой таблице должны быть поля для счетчика команд, регистров и состояния
потока. Счетчик команд нужен потому, что программные потоки, как и про ­
цессы, могут приостанавливаться и возобновлять свою работу. Поля регистров
необходимы по той причине, что значения регистров приостановленного про­
граммного потока необходимо сохранять. Наконец, программные потоки, как
и процессы, могут находиться в состоянии выполиения, zотовности и блокировки.
В табл. 2.2 перечислены некоторые поля, относящиеся к процессам и программ­
ным потокам.

В первой колонке перечислены поля, совместно используемые всеми


Табл ица 2 . 2 .
программными потоками процесса, а во второй колонке - поля, относящиеся
к отдельным программным потокам
Процесс Программный поток
Адресное пространство Счетчик команд
Глобальные переменные Регистры
Открытые файлы Стек
Дочерние процессы Состояние
Действующие аварийные сигналы
Сигналы и их обработчики
Учетная информация
Иногда операционная система не заботится о программных потоках. Другими
словами, управление программными потоками происходит целиком в пользова­
тельском режиме. Например, когда программный поток блокируется, то, перед
тем как остановиться, он решает, какой программный поток должен выполнять­
ся следующим, и запускает его. Существуют и широко используются несколько
библиотек для поддержки пользовательских программных потоков, в том числе
пакеты P- Тhreads от POSIX и C-Threads от Mach.
В других случаях ОС учитывает существование множества потоков, и когда
один программный поток переходит в состояние блокировки, система выбирает
для запуска следующий поток в том же самом процессе или в другом. Чтобы
поддерживать такую функциональность, ядро системы должно хранить таблицу
всех программных потоков в системе, наподобие таблицы процессов.
Хотя, на первый взгляд, оба варианта могут показаться равносильными, между
ними есть заметная разница в производительности. Переключение потоков про­
исходит гораздо быстрее, если оно происходит без участия ядра. Этот факт -
серьезный аргумент за пользовательские программные потоки. В то же время,
2 . 1 . Знакомство с п роцессами 91

когда один и з пользовательских программных потоков блокируется системой


(например, он ожидает завершения операции ввода-вывода или должен обрабо­
тать ошибку отсутствия страницы), то блокируется весь процесс, поскольку ядро
не подозревает о существовании нескольких последовательностей команд. Это -
сильный аргумент за потоки, управляемые ядром [ 1 0 ] . Как следствие, использу­
ются оба механизма, существует также множество гибридных схем [ 1 ] .
Хотя программные потоки часто бывают полезными, они существенно усложня­
ют программную модель. Представьте себе системный вызов f ork. Если роди­
тельский процесс состоял из множества программных потоков, должно ли это
свойство распространяться на дочерний процесс? Если нет, то возможно непра­
вильное функционирование процесса, поскольку все программные потоки могут
оказаться необходимыми.
Но что произойдет, если поток родительского процесса будет блокирован вызо­
вом read с клавиатуры, а у дочернего процесса столько же программных потоков,
сколько у родительского? Будут ли в этом случае блокированы два программных
потока - один из родительского процесса, другой из дочернего? И если с кла­
виатуры поступит строка, получат ли оба программных потока ее копию? Или
только один - тогда какой? Та же проблема возникает при работе с открытыми
сетевыми соединениями.
Другой класс проблем связан с тем, что программные потоки совместно использу­
ют большое количество структур данных. Что произойдет, если один программ­
ный поток закроет файл в то время, когда другой считывает из него данные?
Представьте себе, что одному программному потоку перестало хватать памяти, и он
просит выделить дополнительную. На полпути происходит переключение потоков,
и уже новый программный поток обнаруживает, что ему не хватает памяти, и за­
прашивает ее для себя. В этой ситуации память может быть выделена дважды.
Еще одна проблема связана с сообщениями об ошибках. В UNIX после систем­
ного вызова система помещает информацию о состоянии операции в глобальную
переменную errno. А что произойдет, если сразу после того, как первый про­
граммный поток сделает системный вызов, управление будет передано другому
потоку, который также сделает системный вызов и затрет значение глобальной
переменной?
Теперь рассмотрим сигналы. Одни из них замыкаются на программные потоки,
тогда как другие - нет. Например, если программный поток выполняет запрос
a l arm, результирующий сигнал по логике должен вернуться к этому программ­
ному потоку. Однако когда программные потоки реализованы в пространстве
пользователя, ядро ничего не знает об их существовании и вряд ли направит сиг­
нал по назначению. Ситуация еще больше усложняется, если у процесса может
быть только один необработанный аварийный сигнал, а несколько программных
потоков выполняют запрос a l arrn независимо друг от друга.
Другие сигналы, такие как прерывания с клавиатуры, не связаны с программ­
ными потоками. Кто должен их перехватывать? Один специально выделенный
для этого программный поток? Все программные потоки? Просто очередной
92 Глава 2 . Процессы

программный поток? Что случится, если один программный поток изменит об�
работчик сигнала, не предупредив об этом остальные программные потоки?
Последняя проблема, порождаемая программными потоками, - управление сте­
ками. Во многих системах при переполнении стека процесса ядро автоматически
увеличивает его. Если у процесса несколько программных потоков, стеков тоже
должно быть несколько. Если ядро не знает о существовании этих стеков, оно не
может их автоматически наращивать при переполнении. Ядро может даже не
связать ошибки памяти с переполнением стеков.
Разумеется, эти проблемы преодолимы, но на их примере хорошо видно, что
введение программных потоков в существующую систему без тщательной и про­
думанной реконструкции всей системы не имеет смысла. По крайней мере, при­
дется изменить семантику системных запросов и переписать библиотеки. И ре­
зультат ваших трудов должен быть совместим с существующими программами
для процессов с одним программным потоком. Дополнительную информацию
о программных потоках см. в [56, 83] .

2 . 2 . В заи моде йстви е м ежду п ро цессам и


Процессам часто бывает необходимо взаимодействовать между собой. Напри­
мер, в конвейере ядра выходные данные первого процесса должны передаваться
второму процессу и далее по цепочке. Поэтому на первый план выходит задача
правильно организовать взаимодействие между процессами, по возможности не
используя прерывания. В этом разделе мы рассмотрим некоторые аспекты взаи­
модействия между процессами (InterProcess Communication, IPC).
Если не вдаваться в подробности, проблема имеет три аспекта. О первом мы уже
упомянули - это передача информации от одного процесса к другому. Второй
связан с контролем над деятельностью процессов: как гарантировать, что два
процесса не �пересекутся� в критических ситуациях (представьте себе два про­
цесса, каждый из которых пытается завладеть последним мегабайтом памяти).
Третий касается согласования действий процессов: если процесс А отвечает за
поставку данных, а процесс В за их вывод на печать, то процесс В должен ждать,
не начиная печатать, пока не поступят данные от процесса А. Все три аспекта
рассматриваются в этом разделе.
Нельзя не упомянуть о том, что два из перечисленных аспектов проблемы взаимо­
действия в равной степени относятся к программным потокам. Первый - пере­
дача информации - не представляет большой сложности, если все программные
потоки находятся в едином адресном пространстве (в случае если программные
потоки принадлежат разным адресным пространствам, их взаимодействие попа­
дает в категорию взаимодействия между процессами). Тем не менее обеспечение
�непересекаемости� и согласованности действий программных потоков столь же
актуально: те же проблемы, те же решения. Далее мы будем вести рассуждения
применительно к процессам, однако эти рассуждения в равной степени можно
отнести к программным потокам.
2 . 2 . Взаимодействие между п роцессами 93

2 . 2 . 1 . Гонки
В некоторых операционных системах процессы, работающие совместно, сообща
испол:ьзуют некое общее хранилище данных. Каждый из процессов может счи­
тывать что-либо из общего хранилища данных и записывать туда информацию.
Это хранилище представляет собой область в основной памяти (возможно, в струк­
туре данных ядра) или файл общего доступа. Местоположение разделяемой па­
мяти не влияет на суть взаимодействия и возникающие проблемы. Рассмотрим
взаимодействие между процессами на простом, но очень распространенном при­
мере системы буферизации (спулере) печати. Если процессу требуется вывести
на печать файл, он помещает имя файла в специальный катало� спулера. Другой
процесс, демон печати, периодически проверяет наличие отправленных на пе­
чать файлов, печатает их и удаляет их имена из каталога.
Представьте, что каталог спулера состоит из большого числа сегментов, прону­
мерованных последовательно (О, 1 , 2, ".), в каждом их которых может храниться
имя файла. Также есть две совместно используемые переменные: out, указываю­
щая на следующий файл для печати, и i n, указывающая на следующий свобод­
ный сегмент. Эти две переменные можно хранить в одном файле (состоящем из
двух слов), доступном всем процессам. Пусть в данный момент сегменты с О по 3
пусты (соответствующие файлы уже напечатаны), а сегменты с 4 по 6 заняты
(файлы ждут своей очереди на печать). Пусть более или менее одновременно
процессы А и В решают поставить файл в очередь на печать. Описанную ситуа­
цию схематически иллюстрирует рис. 2.5.

Директория
спулера
.

4 аЬс out = 4

�:
prog.c
ргоg.п
7 iп = 7

� .

Рис. 2 . 5 . Два процесса хотят одновременно получить доступ к совместно


используемой памяти
В соответствии с законом Мерфи (который звучит примерно так: �Если что-то
плохое может случиться, оно непременно случится»), возможна следующая си­
туация. Процесс А считывает значение (7) переменной in и сохраняет его в ло­
кальной переменной next_f r e e_s l o t . После этого происходит прерывание по
таймеру, и процессор переключается на процесс В. Процесс В, в свою очередь,
считывает значение переменной in (опять 7) и сохраняет его в своей локальной
94 Глава 2. П роцессы

переменной next_ f r e e_s l o t . В данный момент оба процесса считают, что сле­
дующий свободный сегмент - седьмой. Процесс В сохраняет в каталоге спулера
имя файла и заменяет значение in на 8, затем продолжает заниматься своими
задачами, не связанными с печатью.
Наконец, управление переходит к процессу А, и он продолжает с того места, на
котором остановился. Он обращается к переменной next_ f r e e_ s l o t , считыва­
ет ее значение и записывает в сегмент 7 имя файла (разумеется, удаляя при этом
имя файла, помещенное туда процессом В). Затем он заменяет значение in на 8
(next_f ree_s l o t + 1 8). Структура каталога спулера не нарушена, поэтому
=

демон печати не заподозрит ничего плохого, но файл процесса В не будет напе­


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

2 . 2 . 2 . Критические секци и
Как избежать гонок? Основным способом предотвращения проблем в этой и лю­
бой другой ситуации, связанной с конкурентным использованием памяти, фай­
лов и чего-либо еще, является запрет одновременной записи и чтения данных
более чем одним процессом. Говоря иными словами, необходимо взаимное ис­
'КЛЮЧение. То есть в тот момент, когда один процесс использует общие данные,
другому процессу это должно быть запрещено. Проблема, описанная в предыду­
щем разделе, вознин:ла из-за того, что процесс В начал работу с одной из общих
переменных до того, как процесс А ее закончил. Выбор подходящей простейшей
операции, реализующей взаимное исключение, является серьезным моментом
разработки операционной системы, и сейчас мы рассмотрим его подробно.
Проблему исключения условий гонок можно сформулировать на абстрактном
уровне. Некоторый промежуток времени (слот) процесс занят внутренними рас­
четами и другими задачами, не приводящими к условиям гонок. В другие момен­
ты времени процесс обращается к совместно используемым файлам или памяти.
Часть программы, в которой происходит обращение к общей памяти, называется
критической секцией. Если нам удастся избежать одновременного нахождения
двух процессов в критических секциях, мы сможем избежать гонок.
Несмотря на то что поставленное требование исключает гонки, его недостаточно
для правильной совместной работы параллельных процессов и эффективного ис­
пользования общих данных. Для этого необходимо выполнение четырех условий.
1 . Два процесса не должны одновременно находиться в критических секциях.
2. Нельзя делать никаких предположений относительно скорости или количест­
ве процессоров.
2 . 2 . Взаимодействие между процессам и 95

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


другие процессы.
4. Недопустима ситуация, в которой процесс бесконечно ожидает попадания
в критическую секцию.
Нужное нам поведение иллюстрирует рис. 2.6. Процесс А входит в свою крити­
ческую секцию в момент времени Т1 • Чуть позднее, в момент времени Т2 , про­
цесс В также пытается войти в свою критическую секцию, однако попытка окан­
чивается неудачей, поскольку один процесс уже находится в критической сек­
ции, а мы не позволяем это делать двум и более процессам одновременно. Таким
образом, процесс В временно приостанавливается до тех пор, пока А не покинет
критическую секцию (момент Т3 ). Далее процесс В немедленно войдет в свою
критическую секцию и выйдет из нее в момент времени Т4, после чего мы вернем­
ся в исходную ситуацию, когда оба процесса находятся вне критических секций.

А входит в критическую область А покидает критическую область


Процесс А \ /
11 В пытается войти 11 В входит
-- lll Ba!!lll!llli'lll
2ii'liil llllii! l
1 покидает
F-
iiiliilil'lli!lllil ----- +-----

11 в критическую 11 в критическую 111 Вкритическую


область IJIобласть 1 область
:
Процесс В ---.--____, \ �1!!!11! -1
· · " " · · · · · · · · · · · · · · · · · !!Щ м 11

:1 В блокирован :1 :1
Т2 Тз Т4
Время •
Рис. 2 . 6 . Взаимное исключение с использованием критических секций

2 . 2 . З . Взаим ное исключение


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

Запрет на прерывания
Самое простое решение состоит в запрете всех прерываний при входе процесса
в критическую секцию и разрешение прерываний по выходу из нее. Если преры­
вания запрещены, невозможно прерывание по таймеру. Запрет на прерывания
исключает передачу процессора другому процессу. Таким образом, запретив пре­
рывания, процесс может спокойно считывать и сохранять общие данные, не опа­
саясь вмешательства конкурентов.
И все же было бы неразумно давать пользовательскому процессу полномочия
для запрета прерываний. Представьте себе, что процесс отключил все прерывания
96 Глава 2 . Процессы

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


на этом может закончить свое существование. К тому же, в многопроцессорной
системе запрет прерываний влияет только на тот процессор, который выполняет
инструкцию di s aЫ e . Остальные процессоры продолжат работу и сохранят дос­
туп к общим данным.
В то же время для ядра блокировка прерываний для некоторых команд может
быть полезной при работе с переменными или списками. Возникновение прерыва­
ния в момент, когда, например, список готовых процессов находится в неопреде­
ленном состоянии, могло бы привести к условиям гонок. Итак, запрет прерыва­
ний бывает полезным в самой операционной системе, но для пользовательских
процессов это решение в качестве механизма взаимного исключения неприемлемо.

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

Строгое чередование
Третий метод реализации взаимного исключения иллюстрирует листинг. 2 . 1 .
Этот фрагмент программного кода, как и многие другие в данной книге, написан
на С. Язык С был выбран, поскольку практически все существующие операцион­
ные системы написаны на С (или С++), а не на языках, подобных Jаvа. Язык С
обладает всеми необходимыми свойствами для написания операционных систем,
это - мощный, эффективный и предсказуемый язык программирования. А язык
Java, например, не является предсказуемым, поскольку у программы, написан­
ной на нем, может в критический момент закончиться свободная память, и она
вызовет �сборщик мусора� в исключительно неподходящее время. В случае С
это невозможно, поскольку в С процедура сборки мусора в принципе отсутствует.
2 . 2 . Взаимодействие между п роцессами 97

Сравнительный анализ С, С++, Java и четырех других языков программирова­


ния представлен в [97 ] .
Предлагаемое решение проблемы критической секции (для обоих процессов
Листи нг 2 . 1 .
необходимо удостовериться в наличии точки с запятой после цикла while)
!* Проце с с 1 * /
wh i l e ( TRUE ) {
wh i l e ( t urn ! = O ) ! * ожидание * / ;
c r i t i c a l _r e g i on ( ) ;
turn= l ;
nonc r i t i c a l_re g i on ( ) ;

/ * Проце с с 2 * /
wh i l e ( TRUE ) {
wh i l e ( t urn ! = l ) ! * ожидание * / ;
c r i t i c al_r e g i on ( ) ;
t u rn= O ;
nonc r i t i c a l_re g i on ( ) ;

В листинге 2. 1 целая переменная turn, изначально равная О, фиксирует, чья оче­


редь входить в критическую секцию. Вначале процесс О проверяет значение turn,
считывает О и входит в критическую секцию. Процесс 1 также проверяет значе­
ние turn, считывает О и после этого в цикле непрерывно проверяет, когда же
значение turn будет равно 1 . Постоянная проверка значения переменной в ожи­
дании некоторого значения называется активным ожиданием. Однако подобного
подхода следует избегать, поскольку он является причиной бесцельного расхо­
дования ресурсов процессора. Активное ожидание может быть приемлемым толь­
ко в случае, когда есть уверенность в коротком времени ожидания.
Когда процесс О покидает критическую секцию, он изменяет значение turn на 1 ,
позволяя процессу 1 завершить цикл. Предположим, что процесс 1 быстро поки­
дает свою критическую секцию, так что оба процесса находятся в обычном со­
стоянии, и значение t urn равно О. После этого процесс О быстро выполняет весь
цикл, выходит из критической секции и устанавливает значение turn равным 1 .
В итоге в этот момент значение turn оказывается равным 1 , и оба процесса на­
ходятся вне критической секции.
Неожиданно процесс О завершает работу вне критической секции и возвращается
к началу цикла. Но на большее он не способен, поскольку значение t urn равно 1 ,
и процесс 1 находится вне критической секции. Процесс О �зависает� в своем
цикле whi l e, ожидая, пока процесс 1 изменит значение turn на О. Получается,
что метод поочередного доступа к критической секции не слишком удачен, если
один процесс существенно медленнее другого.
Эта ситуация нарушает третье из сформулированных нами условий: один про­
цесс блокируется другим, не находящимся в критической секции. Возвратимся
к примеру с каталогом спулера: если заменить критическую секцию процедурой
считывания из каталога и записи в каталог, процесс О не сможет послать файл на
печать, поскольку процесс 1 занят чем-то другим.
98 Глава 2. Процессы

Фактически этот метод требует, чтобы два процесса попадали в критические


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

Алгоритм Петерсона
Датский математик Деккер (Т. Dekker) был первым, кто разработал программ­
ное решение проблемы взаимного исключения, не требующее строгого чередова­
ния. Подробное изложение алгоритма можно найти в [38].
В 198 1 году Петерсон ( G. L. Peterson) придумал существенно более простой
алгоритм взаимного исключения. С этого момента вариант Деккера считается
устаревшим. Алгоритм Петерсона, представленный в листинге 2.2, состоит из
двух процедур, написанных на языке С, соответствующем стандарту ANSI, что
предполагает необходимость прототипов для всех определяемых и используе­
мых функций. В целях экономии места мы не будем приводить прототипы для
этого и последующих примеров.
Листинг 2.2. Решение Петерсона для взаимного исключения
# d e f ine FAL S E О
# d e f ine TRUE 1
# de f ine N 2 /* Количе с т в о проце с с о в * /
int turn ; /* Чья с ейчас очередь ? * /
int i n t e r e s t ed [ N ] ; /* В с е переменные и з начально
/* ра вны О ( FAL S E ) * /

vo i d ent e r_re g i on ( i nt proc e s s ) ; / * Проце с с О или 1 * /


{
int o t he r ; / * Номер второго проце с с а * /
other = 1 - pro c e s s ; / * " Противоположный " проце с с * /
i n t e r e s t e d [ proc e s s ] = TRUE ; / * Индикатор инт е р е с а * /
turn = pro c e s s ; / * Установка флага * /
whi l e ( turn = = proc e s s & & i n t e r e s t ed [ o t he r ] = = TRUE ) / * Пу стой цикл * / ;

vo i d l e ave_reg ion ( i nt proc e s s ) / * Проце с с , покидающий


/ * критиче с кую с е кцию * /

i n t e re s t ed [ pro c e s s ] FAL S E ; / * Индикатор выхода и з


/ * критиче с кой с е кции * /

Прежде чем обратиться к общим переменным (то есть перед тем, как войти в кри­
тическую секцию), процесс вызывает процедуру ent er_r e g i on со своим но­
мером (О или 1 ) в качестве аргумента. Поэтому процессу при необходимости
приходится ждать, прежде чем входить в критическую секцию. После выхода из
критической секции процесс вызывает процедуру l e ave_reg i on, чтобы обо­
значить свой выход и тем самым разрешить другому процессу вход в критиче­
скую секцию.
2 . 2 . Взаимодействие между процессами 99

Рассмотрим алгоритм более подробно. Изначально оба процесса находятся вне


критических секций. Процесс О вызывает процедуру ent er_regi on, задает эле­
менты массива и устанавливает переменную turn равной О. Поскольку про­
цесс 1 не заинтересован в попадании в критическую секцию, происходит возврат
из процедуры. Теперь, если процесс 1 вызовет процедуру ent er_regi on, ему
придется подождать, пока i nt e re s t ed [ О ] примет значение FALSE, а это про­
изойдет только в тот момент, когда процесс О вызовет процедуру l eave_region,
покидая критическую секцию.
Представьте, что оба процесса вызвали процедуру ent er_reg ion практически
одновременно. Оба запомнят свои номера в turn. Но сохранится номер того про­
цесса, который был вторым, а предыдущий номер будет утерян. Предположим,
что вторым был процесс 1 , отсюда значение turn равно 1 . Когда оба процесса
дойдут до конструкции wh i l e, процесс О войдет в критическую секцию, а про­
цесс 1 останется в цикле и будет ждать, пока процесс О выйдет из нее.

Команда TSL
Рассмотрим решение, поддерживаемое аппаратно. Многие компьютеры, особен­
но разработанные с расчетом на несколько процессоров, имеют в составе коман­
ду T S L (Test and Set Lock - проверить и заблокировать):
TSL RX , LOCK

Эта команда действует следующим образом. Она считывает содержимое слова


памяти LOCK в регистр RX, а затем сохраняет по адресу LOCK ненулевое значе­
ние. Операция чтения слова и сохранения в него значения гарантированно явля­
ется неделимой - ни один другой процесс не может получить доступ к слову до
окончания исполнения команды. Процессор, исполняющий команду TSL, блоки­
рует для этой цели шину памяти.
Чтобы применить команду TSL , мы воспользуемся общедоступной переменной
LOCK, координирующей доступ к общей памяти. Когда значение LOCK равно О,
любой процесс может установить ее в 1 с помощью TSL, а затем выполнить чте­
ние или запись общей памяти. Закончив действие, процесс снова устанавливает
LOCK в О при помощи обычной команды MOVE.
Итак, как же использовать команду TSL для реализации взаимного исключения?
Решение приведено в листинге 2.3. Здесь представлена подпрограмма из четы­
рех команд, написанная на некотором обобщенном (но типичном) ассемблере.
Первая команда копирует старое значение LOCK в регистр и затем устанавливает
значение переменной в 1 . Потом старое значение сравнивается с нулем. Если оно
ненулевое, значит, блокировка уже была произведена, и проверка начинается
сначала. Рано или поздно значение окажется нулевым (это означает, что про­
цесс, находившийся в критической секции, покинул ее), и подпрограмма вернет
управление в вызвавшую программу, установив блокировку. Сброс блокировки
не представляет собой ничего сложного - просто в переменную LOCK помещает­
ся О. Специальной команды процессора не требуется.
1 00 Глава 2 . Процессы

Листинг 2 . 3 . Вход и выход из критической секции с помощью команды TSL


ent er_r e g i on ;
TSL REG I STER , LOCK /* З начение LOCK копируется в р е г и с тр * /
/* З начение переменной ус танавли вается равным 1 * /
СМР REG I STER , # 0 /* Старое значение LOCK сравнивается с нулем * /
JNE ENTER_REG ION /* Если оно нену л е в о е , з начит , блокировка уже была * /
/* у с тано влена , поэтому цикл * /
RET /* В о з врат в вызывающую про грамму * /
/* Проце с с вошел в критиче с кую с е кцию * /
l e ave_re g i o n ;
MOVE LOCK , # 0 / * С охранение О в переменной LOCK * /
RET / * В о з врат в вызыв ающую программу * /

Одно из решений проблемы критических секций теперь очевидно. Прежде чем


попасть в критическую секцию, процесс вызывает процедуру ent er_r e g i on,
которая выполняет активное ожидание вплоть до снятия блокировки, затем она
устанавливает блокировку и возвращает управление. По выходу из критической
секции процесс вызывает процедуру l e ave_reg i on, помещающую О в перемен­
ную LОСК. Как и во всех остальных решениях проблемы критической секции,
для корректной работы процесс должен вызывать эти процедуры своевременно,
в противном случае обойти взаимное исключение не удастся.

2 . 2 . 4 . П римитивы взаимодействия
между процессам и
Оба решения - Петерсона и с использованием команды T SL - корректны, но
они обладают одним и тем же недостатком: наличием активного ожидания. В сущ­
ности, оба они реализуют следующий алгоритм: перед входом в критическую
секцию процесс проверяет, можно ли это сделать. Если нельзя, процесс ожидает
разрешения на вход в критическую секцию в бесконечном цикле.
Этот алгоритм не только бесцельно расходует ресурсы процессора, но и может
иметь некоторые неожиданные последствия. Рассмотрим два процесса: Н с вы­
соким приоритетом и L с низким приоритетом. Правила планирования в этом
случае таковы, что процесс Н запускается немедленно, как только он оказывает­
ся в состоянии ожидания. В какой-то момент, когда процесс L находится в кри­
тической секции, процесс Н оказывается в состоянии ожидания (например, он
закончил операцию ввода-вывода). Процесс Н попадает в состояние активного
ожидания, но поскольку процессу L при условии работающего процесса Н про­
цессорное время предоставлено быть не может, у процесса L не будет возможно­
сти выйти из критической секции, и процесс Н навсегда останется в цикле. Эту
ситуацию иногда называют проблемой инверсии приоритета.
Теперь рассмотрим некоторые примитивы взаимодействия между процессами,
применяемые вместо циклов ожидания (в которых лишь напрасно расходуется
процессорное время). Эти примитивы блокируют процессы в случае запрета на
вход в критическую секцию. Одной из простейших является пара примитивов
s l eep и wakeup. Примитив s l eep - системный вызов, в результате которого
вызывающий процесс блокируется, пока его не запустит другой процесс. Вызов
2 . 2 . Взаи модействие между процессами 1 01

wakeup имеет один аргумент - идентификатор запускаемого процесса. Также


возможно наличие одного аргумента у обоих вызовов (ожидания и запуска) -
адреса ячейки памяти, предназначенной для их согласования.

П роблема производителя и потребителя


В качестве примера использования этих примитивов рассмотрим проблему про­
изводителя и потребителя, также известную как проблема оzраниченности буфе­
ра. Два процесса совместно используют буфер ограниченного размера. Один из
них, производитель, помещает данные в этот буфер, а другой, потребитель, счи­
тывает их оттуда. ( Можно обобщить задачу на случай т производителей и п по­
требителей, но мы разберем случай с одним производителем и одним потребите­
лем, поскольку это существенно упрощает решение.)
Трудности начинаются в тот момент, когда производитель желает поместить
в буфер очередную порцию данных и обнаруживает, что тот полон. Для произ­
водителя решением является ожидание, пока потребитель полностью или час­
тично не очистит буфер. Аналогично, если потребитель хочет забрать данные
из буфера, а буфер пуст, потребитель переключается в состояние ожидания и вы­
ходит из него, как только производитель положит что-нибудь в буфер и разбу­
дит спящего.
Это решение кажется достаточно простым, но оно приводит к условиям гонок,
как и пример с каталогом спулера. Нам нужна переменная count для отслежи­
вания количества элементов в буфере. Если максимальное число элементов, хра­
нящихся в буфере, равно N, программа производителя должна проверить, не рав­
но ли N значение c ount , прежде чем поместить в буфер следующую порцию
данных. Если значение c ount равно N, производитель переключается в состоя­
ние ожидания; в противном случае он помещает данные в буфер и увеличивает
значение c ount .
Код программы потребителя прост: сначала проверить, не равно ли значение count
нулю. Если равно, то уйти в состояние ожидания; иначе забрать порцию дс;�нных
из буфера и уменьшить значение c ount . Каждый из процессов также должен
проверять, не следует ли активизировать другой процесс, и в случае необходимо­
сти делать это. Программы обоих процессов представлены в листинге 2.4.
Для описания на языке С системных вызовов s l eep и wakeup мы представили
их в виде вызовов библиотечных процедур. В стандартной библиотеке С их нет,
но они будут доступны в любой системе, в которой поддерживаются такие сис­
темные вызовы. Процедуры i n s e rt_i t ern и rernove_i t ern помещают элементы
в буфер и извлекают их оттуда.
Теперь давайте вернемся к условиям гонок. Их возникновение вероятно, по­
скольку доступ к переменной count не ограничен. Может возникнуть следую­
щая ситуация: буфер пуст, и потребитель только что считал значение перемен­
ной c ount , чтобы проверить, равно ли оно нулю. В этот момент планировщик
передал управление производителю, производитель поместил элемент в буфер
и увеличил значение c ount , убедившись, что теперь оно стало равно 1 . Зная, что
1 02 Глава 2. Процессы

ранее значение бьшо равно О, а потребитель находился в состоянии ожидания,


производитель активизирует потребителя вызовом wakeup.
Листинг 2 . 4 . Проблема производителя и потребителя с неустранимыми условиями гонок
#de f i ne N 1 0 0 ! * Максимальное количе с т в о элементов
! * в буфере * /
int c ount = О; / * Текущее колич е с т в о элементов в буф ере * /

void produc e r ( vo i d )
{
int i t em ;

whi l e ( TRUE ) / * По вторять непрерывно * /


i t em = produc e_i t em ( } ; / * Сф ормировать следующий элемент * /
i f ( c ount = = N ) s l e ep ( ) ; / * Если буфер полон , уйти в с о с тояние
! * ожидания * /
i n s e rt_i t em ( i t em ) ; ! * Помес тить элемент в буфер * /
c ount = c ount + 1 ; / * Ув еличить количе с т в о элементов в буфере * /
if ( c ount == 1) wakeup ( c onsumer ) ; / * Был ли буфер пус т ? * /

vo i d consumer ( vo i d )

int i t em ;
wh i l e ( TRUE ) / * Повторять непрерывно * /
if ( c ount О) s l eep ( ) ; / * Если буфер пус т , уйти в с о с тояние
! * ожидания * /
i t em = remove_i t em ( ) ; ! * З абрать элемент и з буфера * /
c ount = c ount - 1 ; / * Уменьшить сче тчик элементов в буфере * /
i f ( c ount = = N - 1 ) wakeup ( produ c e r ) ; / * Был ли буфер полон? * /
c onsume_i t em ( i t e m ) ; ! * Отправить элемент на печать * /

Но потребитель не бьш в состоянии ожидания, следовательно, сигнал активиза­


ции пропал впустую. Когда управление перейдет к потребителю, он вернется
к считанному когда-то значению count , обнаружит, что оно равно О, и уйдет в со­
стояние ожидания. Рано или поздно производитель наполнит буфер и также пе­
рейдет в состояние ожидания. Оба процесса так и будут ждать.
Суть проблемы в данном случае состоит в том, что сигнал активизации, поступив­
ший процессу, не находящемуся в состоянии ожидания, уходит в никуда. Если
бы не это, проблемы бы не было. Быстрым решением может быть введение бита
ожидания аюпшшзации. Если сигнал активизации послан процессу, не находящему­
ся в состоянии ожидания, этот бит устанавливается. Позже, когда процесс пытается
перейти в состояние ожидания, бит ожидания активизации сбрасывается, но про­
цесс остается активным. Этот бит исполняет роль копилки сигналов активизации.
Хотя введение бита ожидания запуска в нашем примере спасло положение,
легко представить ситуацию с несколькими процессами, в которой одного бита
окажется недостаточно. Мы можем добавить еще один бит, или 8, или 32, но это
не решит проблему.
2.2. Взаи модействие между п роцессами 1 03

2 . 2 . 5 . Семафоры
В 1965 году Дейкстра (Е. W. Dijkstra) показал, как использовать целую перемен­
ную для подсчета сигналов запуска, сохраненных на будущее. Им был предло­
жен новый тип переменных, так называемых семафоров, значение которых мо­
жет быть нулем (в случае отсутствия сохраненных сигналов активизации) или
некоторым положu:тельным числом, соответствующим количеству отложенных
сигналов.
Дейкстра предложил две операции, down и up (обобщения примитивов s l eep
и wakeup ). Операция down сравнивает значение семафора с нулем. Если значе­
ние семафора больше нуля, операция down уменьшает его (то есть расходует один
из сохраненных сигналов активизации) и просто возвращает управление. Если
значение семафора равно нулю, процедура down не возвращает управление про­
цессу, а процесс переводится в состояние ожидания. Все операции проверки
значения семафора, его изменения и перевода процесса в состояние ожидания
выполняются как единое и неделимое атомарное действие. Тем самым гаран­
тируется, что после начала операции ни один процесс не получит доступа к се­
мафору до окончания или блокирования операции. Атомарность операции чрез­
вычайно важна для разрешения проблемы синхронизации и предотвращения
условий гонок.
Операция up увеличивает значение семафора. Если с этим семафором связаны
один или несколько ожидающих процессов, которые не могут завершить более
раннюю операцию down, один из них выбирается системой (например, случай­
ным образом) и ему разрешается завершить свою операцию down. Таким обра­
зом, после операции up, примененной к семафору, связанному с несколькими
ожидающими процессами, значение семафора так и остается равным О, но число
ожидающих процессов уменьшается на единицу. Операция увеличения значения
семафора и активизации процесса тоже неделима. Ни один процесс не может
быть блокирован во время выполнения операции up, как ни один процесс не мог
быть блокирован во время выполнения операции wakeup в предыдущей модели.
В оригинале Дейкстра употреблял вместо down и up обозначения Р и v соответ­
ственно. Мы не будем в дальнейшем использовать оригинальные обозначе­
ния, поскольку тем, кто не знает голландского языка, эти обозначения ничего не
скажут (да и тем, кто знает язык, говорят немного). Впервые обозначения down
и up появились в языке Алгол 68.

Решение проблемы производителя и потребителя


с помощью семафоров
Как показано в листинге 2.5, проблему потерянных сигналов запуска можно ре­
шить с помощью семафоров. Очень важно, чтобы они были реализованы недели­
мым образом. Стандартным способом является реализация операций down и up
в виде системных вызовов с запретом операционной системой всех прерываний
на период проверки семафора, изменения его значения и возможного перевода
процесса в состояние ожидания. Поскольку для выполнения всех этих действий
1 04 Глава 2. Процессы

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


носит никакого вреда. Если используются несколько процессоров, каждый сема­
фор необходимо защитить переменной блокировки посредством команды t s l ,
чтобы гарантировать одновременное обращение к семафору только одного про­
цессора. Необходимо понимать, что использование команды t s 1 принципиаль­
но отличается от активного ожидания, при котором производитель или потреби­
тель ждут наполнения или опустошения буфера. Операция с семафором занимает
несколько микросекунд, тогда как активное ожидание может затянуться на суще­
ственно больший промежуток времени.
Листинг 2 . 5 . Решение проблемы производителя и потребителя с помощью семафоров
#de f ine N 1 0 0 /* Количе с т в о с е гменто в в буфере * /
typede f int s emapho re ; /* С емафоры - ос обый вид целочисленных
/* переменных * /
s emaphore mu t ex = 1 ; /* Контроль д о с тупа в критиче с кую с е кцию * /
s emaphore e�pty = N ; /* Число пус тых с е гментов буфера * /
s emaphore f u l l = О ; /* Число полных с е гментов буфера * /

vo i d produ c e r ( vo i d )
{
int i t em ;

whi l e ( TRUE ) /* TRUE - конс танта , равная 1 * /


i t em = produce_i t em ( ) ; /* С о здать данные , помеща емые в буфер * /
down ( &empty ) ; /* Уменьшить с четчик пу с тых с е гментов буфера * /
down ( &mut e x ) ; /* Вход в критиче с кую с е кцию * /
ins ert_i t em ( i t em ) ; /* Поме с тить в буфер но вый элемент * /
up ( &mu t e x ) ; /* Выход и з критиче с кой с е кции * /
up ( & fu l l ) ; /* Увеличить с ч е тчик полных с е гментов буфера * /

vo i d c onsumer ( vo i d )

i n t i t em ;
wh i l e ( TRUE ) /* Бес конечный цикл * /
down ( & f u l l ) ; /* Уменьшить чис ло полных с е гментов буфера * /
down ( &mu t ex ) ; /* Вход в критическую с е кцию * /
i t em = r emove_i t em ( ) ; /* Удалить элемент и з буфера * /
up ( &mu t ex ) ; /* Выход и з критичес кой с е кции * /
up ( &empty ) ; /* Уве личить с ч е тчик пус тых с е гментов буфера * /
c onsume_i t em ( i t em ) ; /* Обработка элемента * /

В представленном решении используются три семафора: один для подсчета


заполненных сегментов буфера ( fu l l ), другой для подсчета пустых сегментов
( empty), а третий предназначен для исключения одновременного доступа про­
изводителя и потребителя (mu t ex) к буферу. Значение счетчика f u l 1 изначаль­
но равно нулю, счетчик emp ty равен числу сегментов в буфере, а счетчик mu t ex
равен 1 . Семафоры, исходное значение которых установлено в 1 , предназначен­
ные для исключения одновременного нахождения в критической секции двух
2. 2. Взаимодействие между nроцессами 1 05

процессов, называются двоичными семафорами. Взаимное исключение обеспечи­


вается, если каждый процесс выполняет операцию down перед входом в крити­
ческую секцию и up -после выхода из нее.
Теперь, когда у нас есть примитивы взаимодействия между процессами, вернемся
к очередности обработки прерываний (см. список в конце п. 2 . 1 .6). В системах,
использующих семафоры, естественным способом скрыть прерывание является
связывание с каждым устройством ввода-вывода семафора, изначально равного
нулю. Сразу после включения устройства ввода-вывода управляющий процесс
выполняет операцию down на соответствующем семафоре, тем самым входя в со­
стояние блокировки. В случае прерывания обработчик прерывания выполняет
операцию up на соответствующем семафоре, переводя процесс в состояние го­
товности. В такой модели шаг 5 в алгоритме из пункта 2 . 1 .6 заключается в вы­
полнении операции up на семафоре устройства, чтобы следующим шагом плани­
ровщик смог запустить программу, управляющую устройством. Разумеется, если
в этот момент несколько процессов находятся в состоянии готовности, плани­
ровщик вправе выбрать другой, более значимый процесс. Мы рассмотрим неко­
торые алгоритмы планирования позже в этой главе.
В примере, представленном в листинге 2.5, семафоры используются двояко. Это
различие достаточно ощутимо, чтобы сказать о нем особо. Семафор mut ex пред­
назначен для реализации взаимного исключения, то есть для исключения одно­
временного обращения к буферу и к связанным переменным двух процессов.
Мы рассмотрим взаимное исключение и методы его реализации в следующем
разделе.
Остальные семафоры введены с целью синхронизации. Семафоры fu l l и empty
позволяют удостовериться в том, что происходят (или не происходят) опреде­
ленные последовательности событий. В нашем случае они дают гарантию, что
производитель прекращает работу, когда буфер полон, а потребитель прекраща­
ет ее, когда буфер пуст. Такое применение отличается от взаимного исключения.

2 . 2 . 6 . М ьютексы
Иногда, если не нужно применять семафор как счетчик, используется его упро­
щенный вариант, называемый мъютексом (mutex). Мьютексы способны лишь
обеспечивать взаимное исключение для общего ресурса или фрагмента кода.
Их реализация отличается простотой и эффективностью, что делает мьютексы
исключительно полезными в программных потоках, реализуемых исключитель­
но в пространстве пользователя.
Мьютекс представляет собой переменную, способную находиться в одном из двух
состояний: блокированном и разблокированном. Для хранения такой перемен­
ной достаточно одного бита, хотя на практике мьютекс имеет целочисленный тип:
нулевое значение соответствует разблокированному состоянию, а любое ненуле­
вое - блокированному. Мьютексы управляются двумя процедурами. Когда про­
цессу (или программному потоку) ·необходимо войти в критическую секцию, он
вызывает процедуру mut ex_ l o ck. Если мьютекс в этот момент разблокирован
1 06 Глава 2 . Процессы

(то есть критическая область свободна) , вызов завершается успешно и вызы­


вающий поток получает возможность входа в критическую секцию.
Если же мьютекс находится в блокированном состоянии, вызвавший процесс
блокируется до тех пор, пока процесс, находящийся в критической секции, не за­
вершит работу с ней и не вызовет процедуру mut ex_un l o ck. В случае блокиро­
вания мьютексом нескольких процессов из них случайным образом выбирается
один, которому разрешается вход в критическую секцию.

2 . 2 . 7 . Мониторы
Взаимодействие между процессами с применением семафоров выглядит доволь­
но просто, не правда ли? Но эта простота кажущаяся. Взгляните внимательнее
на порядок выполнения процедур down перед помещением элементов в буфер
или удалением их из буфера в листинге 2.5. Представьте себе, что две процедуры
down в программе производителя поменялись местами, так что значение mu tex
было уменьшено раньше, чем empty. Если буфер был заполнен, производитель
блокируется, сбросив mut ex в О. Соответственно, в следующий раз, когда потре­
битель обратится к буферу, он выполнит операцию down с переменной mut ex,
равной О, и тоже заблокируется. Оба процесса навсегда оказываются заблокиро­
ванными. Эта неприятная ситуация называется взаимной блокировкой (deadlock),
и мы вернемся к ней в главе 3.
Описанная ситуация показывает, с какой аккуратностью нужно обращаться с се­
мафорами. Одна маленькая ошибка, и все останавливается. Это напоминает про­
граммирование на ассемблере, но на самом деле еще сложнее, поскольку такие
ошибки приводят к абсолютно невоспроизводимым и непредсказуемым услови­
ям гонок, взаимным блокировкам и т. п.
Чтобы упростить написание программ, Бринч Хансен (Brinch Hansen) в 1973 го­
ду и Хоар (Hoare) в 1974 году предложили примитив синхронизации более вы­
сокого уровня, называемый монитором. Их предложения несколько отличались
друг от друга, как мы увидим дальше. Монитор - это набор процедур, пере­
менных и других структур данных, объединенных в особый модуль, или пакет.
Процессы могут вызывать процедуры монитора, но у процедур, объявленных вне
монитора, нет прямого доступа к внутренним структурам данных монитора. По­
добное правило, обычное для современных объектно-ориентированных языков,
таких как ] ava, было нестандартным в то время, хотя объекты поддерживались
уже в языке Simula 67. В листинге 2.6 представлен монитор, написанный на во­
ображаемом языке, некоем •местечковом диалекте• - •пиджин• Pascal.
Реализации взаимных исключений способствует важное свойство монитора: при
обращении к монитору в любой момент времени активным может быть только
один процесс. Мониторы являются структурным компонентом языка програм­
мирования, поэтому компилятор знает, что обрабатывать вызовы процедур мони­
тора следует иначе, чем вызовы остальных процедур. Обычно при вызове процеду­
ры монитора первые несколько команд процедуры проверяют, нет ли в мониторе
2 . 2 . Взаимодействие между п роцессами 1 07

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


ждать, в противном случае запрос удовлетворяется.
Л и стинг 2 . 6 . Монитор
mon i t o r examp l e
integer i ;
c ondi t i on с ;

pro c e dure produ c e r ( x ) ;

end ;

proc edu r e c onsumer ( x ) ;

end ;
end moni t o r ;

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


ется мьютекс или двоичный семафор. Поскольку взаимное исключение обеспечи­
вает компилятор, а не программист, вероятность ошибки гораздо меньше. В любом
случае программист, пишущий код монитора, не должен задумываться о том, как
компилятор организует взаимное исключение. Достаточно знать, что обеспечив
попадание в критические секции через процедуры монитора, можно не бояться
нахождения в критических секциях двух процессов одновременно.
Хотя мониторы предоставляют простой механизм реализации взаимного ис­
ключения, этого недостаточно. Необходим также способ блокировки процессов,
которые не могут продолжать свою деятельность. В случае проблемы производи­
теля и потребителя достаточно просто поместить все проверки буфера (пуст -
не пуст) в процедуры монитора, но как процесс заблокируется, обнаружив пол­
ный буфер?
Решение заключается в условных переменных и двух операциях, wa i t и s i gna l .
Когда процедура монитора обнаруживает, что она н е в состоянии продолжать
работу (например, производитель выясняет, что буфер заполнен), она выполняет
операцию wa i t для какой-либо условной переменной, скажем, f u l l . Это при­
водит к блокировке вызывающего процесса и позволяет другому процессу войти
в монитор.
Другой процесс, в нашем примере потребитель может активизировать ожидаю­
щего напарника, например, выполнив операцию s i gnal для той условной пере­
менной, для которой он был заблокирован. Чтобы в мониторе не оказалось двух
активных процессов одновременно, нам необходимо правило, определяющее по­
следствия операции s i gna l . Хоар предложил запуск <1:разбуженного� процесса
и остановку второго. Бринч Хансен придумал другое решение: процесс, выпол­
нивший операцию s i gna l , должен немедленно покинуть монитор. Иными слова­
ми, операция s i gnal выполняется только в самом конце процедуры монитора.
1 08 Глава 2 . Процессы

Мы будем использовать это решение, поскольку оно в принципе проще и к тому


же легче в реализации. Если операция s i gnal выполнена для переменной, с ко­
торой связаны несколько заблокированных процессов, планировщик выбирает
и •оживляет� только один из них.
Кроме этого, существует третье решение, не основывающееся на предположени­
ях Хоара и Хансена: позволить процессу, выполнившему операцию s i gna l ,
продолжать работу и запустить ожидающий процесс только после того, как пер­
вый процесс покинет монитор.
Условные переменные не являются счетчиками. В отличие от семафоров они не
аккумулируют сигналы, чтобы впоследствии воспользоваться ими. Это означает,
что в случае выполнения операции s i gna l для условной переменной, с которой
не связано ни одного блокированного процесса, сигнал будет утерян. Проще го­
воря, операция wa i t должна выполняться прежде, чем s i gna l . Последнее пра­
вило существенно упрощает реализацию. На практике оно не создает проблем,
поскольку отслеживать состояния процессов при необходимости не очень труд­
но. Процесс, который собирается выполнить операцию s i gna l , может оценить
необходимость этого действия по значениям переменных.
В листинге 2.7 представлена схема решения проблемы производителя и потреби­
теля с применением мониторов, написанная на языке •пиджин� Pascal. В данной
ситуации этот суррогат языка удобен своей простотой, а также тем, что он позво­
ляет в точности следовать моделям Хоара и Хансена. В каждый момент времени
активна только одна процедура монитора. Буфер состоит из N сегментов.
Можно подумать, что операции wa i t и s i gna l похожи на s l e ep и wakeup , ко­
торые приводили к неустранимым условиям гонок. Они действительно похожи,
но с одним существенным отличием: неудачи при применении операций s l eep
и wakeup были связаны с тем, что один процесс пытался уйти в состояние ожи­
дания, в то время как другой процесс предпринимал попытки активизировать
его. С мониторами такого произойти не может. Автоматическое достижение
взаимного исключения, реализуемое процедурами монитора, гарантирует: если
производитель, находящийся в мониторе, обнаружит полный буфер и решит вы­
полнить операцию wa i t, можно не опасаться, что планировщик передаст управ­
ление потребителю раньше, чем операция wa i t будет завершена. Потребитель
даже не сумеет попасть в монитор, пока операция wai t не будет выполнена и про­
изводитель не прекратит работу.
Хотя •пиджин� Pascal является вымышленным языком, некоторые •настоящие�
языки программирования поддерживают мониторы, пусть и не всегда в форме
Хоара и Хансена. Одним из таких языков является Jаvа. Jаvа представляет собой
объектно-ориентированный язык, дающий пользователю возможность работать
с программными потоками и поддерживающий группировку методов (процедур)
в классы. Добавление в объявление метода ключевого слова synchron i z e d
гарантирует, что если какой-либо программный поток вызовет этот метод, то ни­
какой другой поток не вызовет любой другой метод этого же класса, также объ­
явленный с ключевым словом synchron i z ed.
2 . 2 . Взаимодействие между п роцессами 1 09

Схема решения проблемы производителя и потребителя


Л и стинг 2. 7.
с применением мониторов
mon i t o r Produ c e rCon sumer
c ondi t i on f u l l , empty ;
i n t e g e r c ount ;

pro c e du r e i n s e r t ( i t em : i n t e g e r ) ;
beg i n
i f c ount = N t hen wa i t ( fu l l ) ;
i n s e rt_i t em ( i t em ) ;
c ount : = c ount + l ;
i f c ount = 1 then s i gna l ( empty )
end ;
func t i on remove : i n t e g e r ;
begin
if c ount = О then wa i t ( empty ) ;
remove = remove_i t em ;
c ount : = c ount-1 ;
i f c ount = N - 1 t hen s i gnal ( f u l l )
end ;
c ount : = О ;
end moni t o r ;

pro c e dure produc er ;


beg i n
whi l e t ru e do
begin
i t em = produc e_i t em ;
Produ c e rCon sume r . i n s e r t ( i t em )
end
end ;

proc edure c o n sumer ;


beg i n
whi l e t ru e do
beg i n
i t em = Produ c e rConsumer . remove ;
c o n s ume_i t em ( i t em )
end
end ;

Между синхронизированными методами J ava и классическими мониторами су­


ществует принципиальное отличие: в J ava отсутствуют условные переменные.
Вместо этого имеются две процедуры, wa i t и no t i fy, представляющие собой
эквиваленты s l е ер и wakeup за одним исключением. Когда эти процедуры ис­
пользуются внутри синхронизированных методов, между ними исключены усло­
вия гонок.
Благодаря автоматизации взаимного исключения при применении мониторов
в программах ошибки встречаются значительно реже, чем при применении сема­
форов. Но и у мониторов есть свои недостатки. Недаром монитор, представлен­
ный в листинге 2.7, написан на �пиджин� Pascal, а не на С, как все остальные
примеры этой книги. Как . мы уже говорили, мониторы являются структурным
компонентом языка программирования, и компилятор должен их распознавать
11О Глава 2. Процессы

и организовывать взаимное исключение. Языки Pascal, С и многие другие не


поддерживают мониторов, поэтому странно было бы ожидать от их компилято­
ров выполнения правил взаимного исключения. И в самом деле, как компилято­
ру отличить процедуры монитора от остальных?
Эти языки не поддерживают и семафоров, хотя решить эту проблему несложно:
добавьте в библиотеку две короткие процедуры на ассемблере, выполняющие те
же действия, что и системные вызовы up и down. Компиляторам даже не обяза­
тельно знать о них. Разумеется, операционная система должна знать о семафо­
рах, но если операционная система поддерживает семафоры, вы можете писать
для нее программы на С, С++ или даже (при мазохистских наклонностях) на
FORTRAN. Что же касается мониторов, язык должен обеспечивать их встроен­
ную поддержку.
Другая проблема, связанная с мониторами и семафорами, состоит в том, что они
были разработаны для решения задачи взаимного исключения в системе с одним
или несколькими процессорами, имеющими доступ к общей памяти. Помещение
семафоров в совместно используемую память с защитой в виде команд T S L
может исключить условия гонок. Однако эти примитивы неприменимы в рас­
пределенной системе, состоящей из нескольких процессоров с собственной па­
мятью у каждого, связанных локальной сетью. Вывод из всего вышесказанного
следующий: семафоры являются примитивами слишком низкого уровня, а мо­
ниторы применимы только в некоторых языках программирования. Примитивы
не подходят и для обмена информацией между компьютерами - в этом случае
нужно что-то другое.

2 . 2 . 8 . Передача сообщений
В роли чего-то другого выступает механизм передачи сообщений. Взаимодействие
между процессами такого рода строится на двух примитивах: s end и r e c e ive,
которые являются скорее системными вызовами, нежели структурными компо­
нентами языка (что отличает их от мониторов и делает похожим на семафоры).
Поэтому их легко можно инкапсулировать в библиотечные процедуры, например:
s end ( de s t i na t ion , &me s sage ) ;
r e c e ive ( sourc e , &me s s age ) ;

Первый запрос посылает сообщение заданному приемнику, а второй получает


сообщение от указанного источника (или от любого источника, если это не име­
ет значения). Если сообщения нет, второй запрос блокируется до поступления
сообщения либо немедленно возвращает код ошибки.

Разработка систем передачи сообщени й


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

может затеряться в сети. Чтобы избежать потери сообщений, отправитель и по­


лучатель договариваются, что при получении сообщения получатель посылает
обратно подтверждение приема. Если отправитель не получает подтверждение
через некоторое время, он отсылает сообщение еще раз.
Теперь представим, что сообщение получено, но подтверждение до отправителя
не дошло. Отправитель направит сообщение еще раз, и до получателя оно дойдет
дважды. Крайне важно, чтобы получатель мог отличить копию предыдущего со­
общения от нового сообщения. Обычно проблема решается введением порядко­
вого номера сообщения в тело самого сообщения. Если к получателю приходит
сообщение с номером, совпадающим с номером предыдущего сообщения, оно
классифицируется как копия и игнорируется. Решение проблемы успешного об­
мена информацией в условиях ненадежного механизма передачи сообщений со­
ставляет основу компьютерных сетей.
Для систем обмена сообщениями также важен вопрос названий процессов. Необ­
ходимо однозначно определять процесс, указанный в вызове s end или r e c e i ve.
Кроме того, встает вопрос аутентификации: каким образом клиент может опре­
делить, что он взаимодействует с настоящим файловым сервером, а не с само­
званцем?
Помимо этого существуют конструктивные проблемы, существенные при распо­
ложении отправителя и получателя на одном компьютере. Одной из таких про­
блем является производительность. Копирование сообщений из одного процесса
в другой происходит гораздо медленнее, чем операция над семафором или вход
в монитор. Было проведено множество исследований с целью повышения эф­
фективности передачи сообщений. В [ 18 ] , например, предлагалось ограничивать
размер сообщения размерами регистров и передавать сообщения через регистры.

Решение проблемы производителя и потребителя


путем передачи сообщен и й
Теперь рассмотрим решение проблемы производителя и потребителя посредст­
вом передачи сообщений и без использования общей памяти. Решение представ­
лено в листинге 2.8. Мы предполагаем, что все сообщения имеют одинаковый
размер, и сообщения, которые посланы, но еще не получены, автоматически по­
мещаются операционной системой в буфер. В этом решении передается N со­
общений, по аналогии с N сегментами в буфере. Потребитель начинает с того,
что посылает производителю N пустых сообщений. Как только у производителя
оказывается элемент данных, который он может предоставить потребителю, он
берет пустое сообщение и отсылает назад полное. Таким образом, общее число
сообщений в системе постоянно и их можно хранить в заранее отведенной об­
ласти памяти.
Если производитель работает быстрее, чем потребитель, все сообщения будут
ожидать потребителя в цельном виде. При этом производитель блокируется
в ожидании пустого сообщения. Если потребитель работает быстрее, ситуация
обратная: все сообщения будут пустыми, а потребитель блокируется в ожидании
полного сообщения.
1 12 Глава 2. Процессы

Л истинг 2.8.Решение проблемы производителя и потребителя


с использованием N сообщений
# d e f ine N 1 0 0 ! * Количе с т в о с е гментов в буфере * /
vo i d produc er ( vo i d )

i n t i t em ;
me s s age m ; / * Буфер для с о о бщений * /
whi l e ( TRUE )
i t em = produc e_i t em ( ) ; / * Сформировать нечто , чтобы з аполнить
! * буфер * /
r e c e i ve ( consumer. , &m ) ; / * Ожидание прибытия пу с т о г о с о общения * !
bu i l d_me s sage ( &m , i t em ) ; ! * Сформировать с о о бщение для отправки * !
s end ( con sumer , &m ) ; ! * Отослать эл емент потребителю * /

vo i d c on sumer ( vo i d )

i n t i t em , i ;
me s s age m ;
for (i = О; i < N; i++ )
{
s end ( produc er , &m ) ;
/ * Отослать N пус тых с о о бщений * /
wh i l e ( TRUE ) {
r e c e ive ( produc e r , &m ) ; /* Полу чить с о о бщение с эл ементом * /
i t em = ext rac t_i t em ( &m ) ; /* Из влечь элемент и з с о о бщения * /
s end { p roduc er , &m ) ; /* Отослать пу с т о е соо бщение * /
con sume_i t em ( i t em ) ; !* Обработка эл емента * /

Передача сообщений реализуется по-разному. Рассмотрим способ адресации


сообщений. Можно присвоить каждому из процессов уникальный адрес и адре­
совать сообщение непосредственно процессам. Другой подход состоит в исполь­
зовании новой структуры данных, называемой почтовым ящиком . Почтовый
ящик - это буфер для определенного количества сообщений, тип которых зада­
ется при создании ящика. При использовании почтовых ящиков адресуемыми
параметрами в вызовах s end и r e c e i ve являются почтовые ящики, а не процес­
сы. Если процесс пытается послать сообщение в переполненный почтовый ящик,
ему приходится ждать, пока хотя бы одно сообщение не будет оттуда удалено.
В задаче производителя и потребителя оба они создадут почтовые ящики, доста­
точно большие, чтобы хранить N сообщений. Производитель будет посылать со­
общения с данными в почтовый ящик потребителя, а потребитель отправлять
пустые сообщения в почтовый ящик производителя. В случае почтовых ящиков
способ буферизации очевиден: в почтовом ящике получателя хранятся сообще­
ния, которые были посланы процессу-получателю, но еще не получены.
Другой крайностью при использовании почтовых ящиков является принци­
пиальное отсутствие буферизации. При таком подходе, если вызов s end вы­
полняется раньше, чем r e c e i ve, процесс-отправитель блокируется до вызова
r e c e ive, когда сообщение может быть напрямую скопировано от отправителя
2. 3 . Классические п роблем ы взаимодействия между проце ссами 1 13

к получателю без промежуточной буферизации. Если вызов r e c e i ve выполня­


ется раньше, чем s end, процесс-получатель блокируется до вызова s end. Этот
метод часто называют рандеву, он легче реализуется, чем схема буферизации со­
общений, но менее гибок, поскольку отправитель и получатель должны работать
в режиме жесткой синхронизации.
Процессы, формирующие операционную систему MINIX 3, для взаимодейст­
вия между собой используют метод рандеву с сообщениями фиксированного
размера. Таким же способом пользовательские процессы взаимодействуют с ком­
понентами операционной системы, хотя с точки зрения программиста это неза­
метно: системные вызовы <1:скрыты� библиотечными процедурами. Взаимодейст­
вие между пользовательскими процессами в MINIX 3 происходит посредством
каналов, которые по своей сути являются почтовыми ящиками. Единственная
разница между механизмом каналов и настоящими почтовыми ящиками состоит
в том, что в каналах нет разграничения сообщений. Другими словами, если ис­
точник поместит в канал 1 0 сообщений по 1 00 байт, а приемник прочитает
1 000 байт, то он сразу прочитает все 10 сообщений. В реальной системе обмена
сообщениями каждая операция read возвращает единственное сообщение. Ко­
нечно, если существует договоренность всегда использовать сообщения одного
и того же размера, то подобных проблем не возникает. Кроме того, можно ввести
специальный символ для разделения сообщений (скажем, перевод строки).
Передача сообщений, как правило, применяется в системах параллельного про­
граммирования. Широкую известность получила система MPI ( Message-Passing
Interface - интерфейс передачи сообщений), используемая в научных расчетах.

2 . 3 . Класси ч еские п роблем ы


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

2 . 3 . 1 . П роблема обедающих философов


В 1965 году Дейкстра сформулировал и решил проблему синхронизации, на­
званную им проблемой обедающих философов. С тех пор каждый, кто изобре­
тал еще один новый примитив синхронизации, считал своим долгом проде­
монстрировать достоинства нового примитива на примере решения проблемы
обедающих философов. Задачу можно сформулировать следующим образом:
пять философов сидят за круглым столом и у каждого есть тарелка со спагетти.
Спагетти настолько скользкие, что каждому философу нужны две вилки, чтобы
управиться с яством. Но между каждыми двумя тарелками лежит только одна
вилка (рис. 2.7).
1 14 Глава 2 . Процессы

Рис. 2 . 7 . Время обеда на факультете философии


Жизнь философа состоит из чередующихся периодов поглощения пищи и раз­
мышлений. (Разумеется, это абстракция, даже применительно к философам, но
остальные процессы жизнедеятельности для нашей задачи несущественны. ) Ко­
гда философ голоден, он пытается получить две вилки, левую и правую, в любом
порядке. Если ему удается завладеть двумя вилками, он некоторое время ест, за­
тем кладет вилки обратно и продолжает размышления. Вопрос состоит в сле­
дующем: можно ли написать алгоритм, который моделирует эти действия для
каждого философа и никогда не оказывается заблокированным? ( Кое-кто счита­
ет, что требование наличия двух вилок выглядит несколько искусственно. Воз­
можно, нам следует заменить итальянскую пищу блюдами китайской кухни: спа­
гетти - рисом, а вилки - палочками.)
В листинге 2.9 представлено очевидное решение проблемы. Процедура t ake_
fork ждет, пока указанная вилка не освободится, и берет ее. К сожалению, это
решение неверно - представьте себе, что все пять философов возьмут одно­
временно свои левые вилки. Каждый останется без правой вилки, то есть будет
иметь место взаимная блокировка.
Л истинг 2 . 9 . Неверное решение проблемы обедающих философов
Jlde f ine N 5 / * Количе с т в о фил о с о ф о в * /

vo i d ph i l o s o sphe r ( int i ) /* i - номер филос офа , от о до 4 * /


{
wh i l e ( TRUE ) {
t h i nk ( ) ; /* Фил о с о ф размышля ет * /
t ake_f ork ( i ) ; /* Берет л е вую вилку * /
t ake_fork ( ( i + l ) % N ) ; /* Берет правую вилку * /
eat ( ) ; /* Спа г е ти , ням-ням * /
put_ f o r k ( i ) ; /* Кладе т на стол левую вилку * /
put_fork ( ( i + l ) % N ) ; /* Кладет на стол правую вилку * /
2 .3. Классические проблем ы взаимодействия между процессам и 115

Можно изменить программу так, чтобы после получения левой вилки проверя­
лась доступность правой. Если правая вилка недоступна, философ кладет левую
обратно, ждет некоторое время и повторяет весь процесс. Этот подход также не
будет работать, хотя и по другой причине. Если не повезет, все пять философов
могут начать процесс одновременно, взять левую вилку, обнаружить отсутст­
вие правой, положить левую обратно на стол, одновременно взять левую вилку,
и так до бесконечности. Ситуация, в которой все программы продолжают рабо­
тать сколь угодно долго, но не могут добиться хоть какого-то прогресса, называ­
ется зависанием (starvation).
Вы можете подумать: <1: Если философы будут размышлять в течение некото­
рого случайно выбранного промежутка времени после неудачной попытки взять
правую вилку, вероятность того, что все процессы будут продолжать топтать­
ся на месте хотя бы в течение часа, невелика� . Это правильно, и для большинст­
ва приложений повторение попытки спустя некоторое время снимает проблему.
Например, в локальной сети Ethernet в ситуации, когда два компьютера посы­
лают пакеты одновременно, каждый должен подождать случайно заданное вре­
мя и повторить попытку - на практике это решение хорошо работает. Тем не
менее в некоторых приложениях предпочтительным является другое решение,
работающее всегда и не зависящее от случайных чисел ( например, в прило­
жении, предназначенном для обеспечения безопасности на атомных электро­
станциях).
Листинг 2.9 можно улучшить, исключив взаимные блокировки и зависания процес­
сов; для этого достаточно защитить пять команд, следующих за запросом think,
бинарным семафором. Тогда философ должен будет выполнить операцию down
для переменной rnut ex прежде, чем тянуться к вилкам. А после возврата вилок
на место ему следует выполнить для переменной rnut ex операцию up. С теорети­
ческой точки зрения решение вполне подходит. С позиций практики возникают
проблемы эффективности: в каждый момент времени может есть спагетти только
один философ. Но вилок пять, поэтому логичней бьто бы разрешить есть в каж­
дый момент времени двум философам.
Решение, представленное в листинге 2. 1 0, исключает взаимные блокировки и по­
зволяет реализовать максимально возможный параллелизм для любого числа
философов. Здесь используется массив s t a t e для отслеживания душевного со­
стояния каждого философа: он либо ест, либо размышляет, либо голодает (пы­
таясь получить вилки). Философ может начать есть, только если ни один из его
соседей не ест. Соседи философа i определяются макросами LEFT и RIGHT (то
есть если i 2, то LEFT 1 и R I GHT 3).
= = =

В программе используется массив семафоров, по одному на философа, чтобы бло­


кировать голодных философов, если их вилки заняты. Обратите внимание, что
каждый процесс запускает процедуру phi l o s ophe r в качестве своей основной
программы, но остальные процедуры t a ke_f orks, put_forks и t e s t являют­
ся обычными процедурами, а не отдельными процессами.
116 Глава 2. П роцессы

Листинг 2 . 1 О . Решение проблемы обедающих философов


# d e f ine N 5 /* Количе с тв о фило с офов * /
#de f ine LEFT ( i +N . 1 ) % N /* Номер л е в о г о с о с еда фило с о ф а с номером i * /
# de f ine RI GHT ( i + 1 ) %N /* Номер пра вого с о с еда фило с офа с номером i * /
# de f ine TH I NKING о /* Фил о с о ф размышляе т * /
# de f ine HUNGRY 1 /* Фило с о ф пытается получить вилки * /
# de f ine EAT ING 2 /* Фило с о ф е с т * /
typede f int s emapho re ; /* Семафоры - о с о бый вид целочис ленных
/* переменных * /
int s t a t e ( N ] ; /* Мас с и в для отс лежив ания с о с тояния каждого
/* фило с офа * /
s emapho re mut ex 1; /* В з аимоис ключение для критичес ких с е кций * /
s emapho re s [ N ] ; /* Каждому фил о с о фу - по с емафору * /

vo i d phi l o s ophe r ( int i ) / * i - номер фило с офа , от О до N- 1 * /

whi l e ( TRUE ) { /* Пов торять пос тоянно * /


t h i nk ( ) ; /* Фил о с о ф размышляе т * /
t ake_f orks ( i ) ; /* Получает две вилки или блокируется * /
eat ( ) ; /* Спагетти , ням- ням * /
pu t_ f o r k s ( i ) ; /* Кладет на с тол о б е вилки * /

vo i d t ake_ f o r k s ( int i ) / * i - номер филос офа , от О до N- 1 * /

down ( &mut e x ) ; /* Вход в критич е с кую с е кцию * /


s t a t e ( i ] = HUNGRY ; /* Выявл ение голодного филос офа * /
test ( i ) ; /* Попытка получить две вилки * /
up ( &mu t ex ) ; /* Выход и з критической с е кции * /
down ( & s [ i ] ) ; /* Блокиро вка , е с ли вилок не дос тало с ь * /

vo i d put_ f o r k s ( i ) / * i - номер фило с офа , о т О до N - 1 * /

down ( &mu t e x ) ; /* Вход в критич е с кую с е кцию * /


s t a t e [ i ] = TH INKING ; /* Фил о с о ф перес тал е с т ь * /
t e s t ( LEFT ) ; /* Про верить , может ли е с т ь с о с е д с л е в а * /
t e s t ( RI GHT ) ; /* Пров ерить , может ли е с т ь с о с е д справа * /
up ( &mu t ex ) ; /* Выход и з критической с е кции * /

vo i d t e s t ( i ) / * i - номер фило с офа , о т О до N - 1 * /

if ( state [ i ]
== HUNGRY & & s t a t e [ LEFT ] != EAT ING & & s t a t e [ RIGHT ] != EAT ING ) {
s t a t e [ i ] = EAT ING ;
up ( & s [ i ] ) ;

2 . 3 . 2 . П роблема читателей и писателей


Проблема обедающих философов полезна для моделирования процессов, сорев­
нующихся за монопольный доступ к ограниченному количеству ресурсов, напри­
мер к устройствам ввода-вывода. Другой известной проблемой является проблема
2.3. Классические проблемы взаимодействия между процессам111 1 17

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


себе базу данных для бронирования билетов на самолет, к которой пытается полу­
чить доступ множество клиентов. Можно разрешить одновременное считывание
данных из базы, но если процесс записывает информацию в базу, доступ осталь­
ных процессов должен быть прекращен, даже доступ на чтение. Как запрограм­
мировать читателей и писателей? Одно из решений представлено в листинге 2. 1 1 .
Л и стинг 2. 1 1 . Решение проблемы читателей и писателей
typede f int s emaphore ; /* Воспользуйт е с ь с воим воо бражением * /
s emaphore mu t ex = 1 ; /* Контроль доступа к rc * /
s emapho re db = 1 ; /* Контроль д о с тупа к б а з е данных * /
int rc = О ; /* Колич е с т в о процес с о в , читающих или * /
/* желающих читать * /

vo i d reader ( vo i d )

wh i l e ( TRUE ) /* П о в торять до бес конечно сти * /


down ( &mu t ex ) ; /* Получение монопольно го до с тупа к rc * /
rc = rc + l ; /* Одним читающим проце с с ом больше * /
i f ( rc = = 1 ) down ( &db ) ; /* Если этот читающий проце с с - * /
/* первый . . . * /
up ( &mut ex ) ; /* Отказ от монопольно го д о с тупа к rc * /
read_dat a_ba s e ( ) ; /* Д о с туп к данным * /
down ( &mu t ex ) ; /* Получение монопольно го д о с тупа к rc * /
rc = rc - 1 ; /* Одним читающим проце с с ом меньше * /
i f ( rc = = О ) up ( &db ) ; /* Если этот читающий проце с с - / *
/* последний . . . * /
up ( &mu t ex ) ; /* Отказ о т монопольно го доступа к rc * /
u s e_dat a_read ( ) ; /* Вне критической с е кции * /

vo i d wr i t e r ( vo i d )

wh i l e ( TRUE ) /* По вторять до б е с конечно с ти * /


t h ink_up_dat a ( } ; /* Вне критической с е кции * /
down ( &db ) ; /* Получение монопольного до с тупа * /
wr i t e_dat a_ba s e ( } ; /* З апис ь данных * /
up ( & db ) ; /* Отказ от монопольно го доступа * /

Первый читающий процесс выполняет операцию down для семафора dЬ, чтобы
получить доступ к базе. Последующие читатели просто увеличивают значение
счетчика rc. По мере уменьшения числа читающих из базы значение счетчика
уменьшается, и последний читающий процесс выполняет для семафора dЬ опера­
цию up, позволяя блокированному пишущему процессу получить доступ к базе.
В этом решении один момент требует комментариев. Представьте, что в то время
как один читатель уже пол ьзуется базой, другой читатель запрашивает к ней
доступ. Доступ разрешается, поскольку читающие процессы друг другу не меша­
ют. Доступ разрешается и третьему., и последующим читателям.
1 18 Глава 2. П роцессы

Затем доступ запрашивает пишущий процесс. Запрос отклоняется, поскольку


пишущим процессам необходим монопольный доступ, и пишущий процесс при­
останавливается. Пока в базе есть хотя бы один активный читающий процесс,
доступ остальным читателям разрешается, а они все приходят и приходят. Если
предположить, что новый читающий процесс запрашивает доступ каждые 2 с,
а для работы с базой ему надо 5 с, то пишущий процесс никогда в базу не попадет.
Чтобы избежать такой ситуации, нужно немного изменить программу: если
пишущий процесс ждет доступа к базе, новый читающий процесс доступа не по­
лучает, а становится в очередь за пишущим процессом. Теперь пишущему про­
цессу нужно подождать, пока базу покинут уже находящиеся в ней читающие
процессы, но не нужно пропускать вперед читающие процессы, пришедшие к ба­
зе после него. Недостаток этого решения заключается в снижении производи­
тельности, вызванном ослаблением конкуренции. В [ 2 7 ) представлено реше­
ние, в котором пишущим процессам предоставляется более высокий приоритет.

2 . 4 . Планиро вание
Когда компьютер работает в многозадачном режиме, на нем могут быть активны­
ми несколько процессов, пытающихся одновременно получить доступ к процес­
сору. Эта ситуация возникает, когда два и более процессов находятся в состоя­
нии готовности. Если доступен только один процессор, необходимо выбирать
между процессами. Отвечающая за это часть операционной системы называется
планировщиком, а используемый алгоритм алгоритмом планирования.
-

Многие вопросы планирования в одинаковой степени касаются процессов и про­


граммных потоков. Сначала мы акцентируем внимание на планировании про­
цессов, а затем коснемся специфических вопросов планирования программных
потоков.

2 . 4 . 1 . Основы планирования
Давным-давно, в о времена систем пакетной обработки, перфокарт и магнитных
лент в качестве устройств ввода, алгоритм планирования был прост: запустить
следующую задачу. С появлением систем разделения времени алгоритм пла­
нирования усложнился, поскольку теперь несколько задач одновременно могли
ожидать обслуживания. На некоторых мэйнфреймах до сих пор совмещаются
системы пакетной обработки и службы разделения времени. Может показаться,
что на персональных компьютерах активным является только один процесс: в са­
мом деле, пользователь, работающий с документом в текстовом редакторе, едва
ли станет компилировать программу в фоновом режиме. В то же время фоновые
задания отнюдь не являются редкостью. Примером является демон электронной
почты, осуществляющий прием и передачу сообщений. Вы также можете поду­
мать, что в последние годы быстродействие компьютеров возросло настолько, что
о нехватке ресурсов процессора можно забыть. Однако и требования современных
2.4. План ирова н ие 1 19

приложений к ресурсам совсем не те, что раньше: вспомните хотя бы обработку


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

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

Время
____,
•�
Рис. 2 . 8 . Чередование периодов использования процессора и ожидания ввода-вывода:
а - процесс ограничен вычислительными возможностями; б процесс ограничен
-

возможностями ввода-вывода
Рисунок 2.8 иллюстрирует одну важную вещь. Некоторые процессы (рис. 2.8, а)
большую часть своего времени тратят на вычисления, другие же процессы
(рис. 2.8 , б) в основном ожидают ввода-вывода. Первые оzраничены вычисли­
телънъtМи возможностями, вторые возможностями ввода-вывода. Процессы,
-

ограниченные вычислительными возможностями процессора, имеют длительные


периоды вычислений, а ожидания ввода-вывода происходят редко. Процессы,
ограниченные возможностями ввода-вывода, напротив, регулярно тратят время
на ожидание ввода-вывода, а их вычислительные циклы невелики. Обратите
внимание на то, что ключевым фактором является продолжительность вычисли­
тельного цикла, а не цикла ввода-вывода. Ограниченность процессов возможно­
стями ввода-вывода возникает не потому, что они выполняют мало вычислений
между запросами на ввод-вывод, а потому, что обслуживание этих запросов яв­
ляется длительным. Время считывания дискового блока не зависит от скорости
обработки считанных данных.
Следует заметить, что с повышением быстродействия процессоров узким ме­
стом для процессов становится ввод-вывод. Это объясняется тем, что прогресс
1 20 Глава 2. Л роцессы

в производстве процессоров значительно опережает прогресс в производстве


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

Когда требуется планирование


Планирование требуется в самых разных ситуациях. Оно, безусловно, необходи­
мо в двух случаях.
1 . При завершении процесса.
2. При блокировании процесса семафором или вводом-выводом.
Обе ситуации приводят к тому, что выполнение текущего процесса прекращает­
ся, а значит, требуется выбрать следующий процесс для запуска.
Планирование обычно осуществляется еще в трех случаях, хотя и без жесткой
логической необходимости.
1. При создании нового процесса.
2. При прерывании ввода-вывода.
3. При прерывании от таймера.
Когда появляется новый процесс, имеет смысл заново оценить приоритеты.
Иногда родительский процесс может запросить другой уровень приоритета для
дочернего процесса.
Прерывание ввода-вывода, как правило, означает окончание работы устройства
ввода-вывода. В этом случае существует вероятность, что блокированный ранее
процесс может быть запущен.
Прерывание от таймера дает возможность проверить, не выполняется ли теку­
щий процесс слишком долго. С точки зрения реакции на прерывания от таймера
алгоритмы планирования делятся на два класса. При невытесняющем алгоритме
планирования процесс выбирается и выполняется до блокирования (вызванного
вводом-выводом либо ожиданием другого процесса) или добровольного завер­
шения. При вытесняющем алгоритме планирования процесс выбирается и вы­
полняется в течение некоторого временного интервала. Если по окончании ин­
тервала процесс не завершил работу, он приостанавливается, а планировщик
выбирает другое задание (при его наличии). При вытесняющем планирова­
нии необходимо прерывание от таймера в конце каждого временного интервала
с последующей передачей процессора под управление планировщика. В отсут­
ствие таймера невытесняющий алгоритм является единственным вариантом
планирования.

Категории алгоритмов планирования


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

по-разному должен оптимизировать управление процессами. Отметим wи вари­


анта окружения.
1. Системы пакетной обработки.
2. Интерактивные системы.
3. Системы реального времени.
В системах пакетной обработки не существует проблемы пользователей, ожи­
дающих у своих терминалов как можно более скорой реакции системы. Таким
образом, вполне приемлемым является применение невытесняющих алгоритмов
планирования или вытесняющих алгоритмов с длительным временным интерва­
лом. Эти алгоритмы позволяют повысить производительность за счет уменьше­
ния количества переключений между процессами.
В окружении с интерактивными пользователями необходимо вытеснение, позво­
ляющее избежать захвата процессора одним процессом и отказа в обслуживании
всем остальным. Даже если ни один из процессов не �зациклится� из-за ошибки
в программе, возможны ситуации, в которых процесс парализует работу систе­
мы. Вытеснение предотвращает подобное поведение.
Как ни странно, в системах реального времени вытеснен:ие не всегда оказывается
необходимым. Дело в том, что процессы в таких системах должны работать бы­
стро, а значит, и блокирование не бывает долгим. Системы реального времени
содержат только те программы, которые требуются для решения конкретных задач.
Интерактивные системы, напротив, являются универсальными и могут включать
самые разные программы, в том числе бесполезные и даже вредоносные.

Цел и ал г оритма планир ов ания


Чтобы разработать алгоритм планирования, необходимо определить, что от него
требуется. Некоторые цели алгоритма планирования зависят от окружения, в ко­
тором он будет применяться, однако есть и общие характеристики, наличие кото­
рых желательно всегда. Мы перечислим и рассмотрим некоторые из них.
+ Все системы:
+ равноправие - предоставление каждому процессу справедливой доли про­
цессорного времени;
+ применение политик - :наблюдение за соблюдением установленной поли­
тики;
+ баланс - обеспечение работой всех компонентов системы.
+ Системы пакетной обработки:
+ пропускная способность - выполнение максимального количества зада­
ний в единицу времени;
+ время оборота - минимизация времени, затрачиваемого на ожидание об­
служивания и обработку задания;
+ коэффициент использования процессора - обеспечение постоянной заня­
тости процессора.
1 22 Глава 2. Процессы

+ Интерактивные системы:
+ время отклика - быстрая реакция на запросы;
+ пропорциональность - соответствие ожиданиям пользователей.
+ Системы реального времени:
+ соответствие временным ограничениям - избежание потерь данных;
+ предсказуемость - избежание потери качества в мультимедиа-системах.
При всех обстоятельствах необходимо справедливое распределение процессор­
ного времени. Сопоставимые процессы должны получать сопоставимое обслу­
живание. Выделять одному процессу намного больше ресурсов процессора, чем
другому, эквивалентному, несправедливо. Разумеется, с различными категория­
ми процессов следует обращаться совершенно по-разному. Сравните, например,
задачи обеспечения безопасности атомной электростанции и начисления зара­
ботной платы в компьютерном центре.
Применение системных политю< связано со справедливым распределением ре­
сурсов. Если, к примеру, локальная политика требует, чтобы процессы управле­
ния безопасностью могли быть запущены в любой момент даже при условии, что
программа выдачи платежных ведомостей будет задержана на 30 секунд, плани­
ровщик должен обеспечить выполнение таких условий работы.
Еще одна общая цель планировщика - по возможности загружать все части сис­
темы работой. Если процессор и все устройства ввода-вывода работают непре­
рывно, их производительность выше, чем при простое каких-либо компонентов.
Например, в системе пакетной обработки планировщик решает, ка1ше процессы
загружать в память для выполнения. Предпочтительнее иметь в памяти несколь­
ко из процессов, ограниченных возможностями процессора и ввода-вывода, чем
сначала загрузить и выполнить только первые, а затем - только вторые. Процес­
сы, ограниченные вычислительными возможностями, начнут конкурировать за
вычислительный ресурс, а диск в это время будет простаивать. Когда очередь
дойдет до процессов, ограниченных возможностями ввода-вывода, будет про­
стаивать процессор, поскольку основная борьба развернется за устройства вво­
да-вывода. Лучше всего поддерживать в работе всю систему, тщательно отбирая
процессы для обработки.
Как правило, менеджеры корпоративных компьютерных центров с интенсивной
пакетной обработкой оценивают производительность подведомственных систем
по трем показателям: пропускной способности, времени оборота и коэффициенту
использования процессора. Пропускной способностью называется число зада­
ний, выполняемых системой в секунду. Ясно, что 50 заданий в секунду лучше,
чем 40. Время оборота - это среднее время с момента ввода задания до его вы­
полнения. Оно показывает, сколько в среднем пользователь вынужден ждать
результатов обработки. Нетрудно сформулировать правило: чем меньше время
оборота, тем лучше.
Алгоритм планирования, максимизирующий пропускную способность, совсем не
обязательно сводит к минимуму время оборота. Например, если планировщик
выбирает из группы заданий в первую очередь самые короткие, а затем - самые
2. 4 . План и ро вание 1 23

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


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

В интерактивных системах, особенно в серверах с разделением времени, цели яв­


ляются совершенно иными. Наиболее важная из них - минимизировать время
оm'Кllика - время с момента ввода команды до получения результата. На персо­
нальном компьютере с выполняющимся фоновым процессом (к примеру, считы­
вающим и сохраняющим сообщения электронной почты, полученные из сети)
запрос пользователя об открытии файла или запуске программы должен иметь
более высокий приоритет. Обслуживание может считаться хорошим, если все
интерактивные запросы обрабатываются системой в первую очередь.
С обслуживанием связано понятие пропорци01шлъности. Пользователи имеют рас­
пространенные (хотя зачастую неверные) представления о том, сколько времени
должно занимать то или иное действие. Если принято считать, что действие яв­
ляется длительным, пользователи принимают это, однако если действие считает­
ся быстрым, но система тратит на него много времени, пользователи выражают
недовольство. Пусть, к примеру, компьютер подключен к Интернету с помощью
простого аналогового модема. Если с момента щелчка на значке до установки со­
единения проходит 45 секунд, пользователь воспримет это как должное. Однако
если столько же времени потребуется на разъединение, то через полминуты поль­
зователь начнет ворчать, а пятнадцатью секундами позже разразится бранью с пе­
ной у рта. Все это объясняется общепринятым мнением о том, что набор номера
и подключение к сети требуют значительно больше времени, чем разрыв соеди­
нения. Иногда (как в этом примере) планировщик не может повлиять на время
отклика, хотя в других случаях он способен на это (особенно если задержка вы­
звана неудачным выбором процесса для обработки).
Системы реального времени по характеристикам отличаются от интерактивных
систем, а следовательно, цели планирования в них также иные. Особенностью
систем реального времени является наличие ограничения по времени обработки.
Например, если компьютер управляет устройством, генерирующим данные с опре­
деленной частотой, опоздание при запуске процесса сбора данных может привести
1 24 Глава 2. П роцессы

к их ·потере. Таким образом, главным требованием систем реального времени явля­


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

2 . 4 . 2 . План и рование в системах


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

Первым пришел - первым обслужен


Пожалуй, самым простым алгоритмом планирования является алгоритм под на­
званием первым пришел - первым обслужен. Согласно этому алгоритму, процессы
обрабатываются в порядке поступления запросов на использование процессора.
Упрощенно говоря, мы имеем дело с единственной очередью готовых процессов.
Первое задание, поступившее в систему в начале рабочего дня, немедленно при­
нимается и обрабатывается столько, сколько требуется. По мере поступления
других заданий они помещаются в конец очереди, и когда текущий процесс бло­
кируется, из очереди на выполнение выбирается первый процесс. После снятия
блокировки предыдущий процесс обрабатывается как новый, то есть помещается
в конец очереди.
Главное преимущество такого алгоритма планирования заключается в простоте
и легкости программирования. Кроме того, он является справедливым в том же
смысле, в каком справедливой считается продажа дефицитных билетов на концерт
или спортивное состязание людям, стоящим в очереди с 2 часов ночи. В алгоритме
�первым пришел - первым обслужен� все готовые процессы помещаются в един­
ственный связанный список. Выбор процесса на выполнение равносилен удале­
нию из списка первого элемента, а ввод в очередь нового или разблокированного
процесса - добавлению элемента в конец списка. Что может быть проще?
К сожалению, у алгоритма �первым пришел - первым обслужен� есть и не менее
важный недостаток. Представьте, что вы имеете дело с процессом, ограниченным
вычислительными возможностями и запускаемым раз в секунду, и множеством
процессов, ограниченных возможностями ввода-вывода, тратящих мало време­
ни на процессорную обработку, но требующих для завершения 1 000 операций
2. 4 . П л аниро ван ие 1 25

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


процессора, выполняется одну секунду, а затем считывает блок с диска. Далее вы­
полняются все процессы, ограниченные возможностями ввода-вывода, и также
читают данные с диска. Получив информацию, процесс, ограниченный вычисли­
тельными возможностями, снова выполняется одну секунду, а за ним следуют
все остальные процессы.
В результате оказывается, что каждый процесс, ограниченный возможностями
ввода-вывода, считывает блок данных раз в секунду, а, следовательно, для завер­
шения ему потребуется 1 000 секунд. Если бы алгоритм планирования прерывал
процесс, ограниченный вычислительными возможностями, каждые 10 мс, про­
цесс, ограниченный возможностями ввода-вывода, завершился бы за 10, а не за
1000 секунд. При этом процесс, ограниченный вычислительными возможностя­
ми, не оказался бы слишком задержанным.

Самое короткое задание - первое


Теперь рассмотрим еще один пакетный невытесняющий алгоритм, основанный
на предположении о том, что время выполнения процессов известно заранее.
Например, в страховых компаниях специалисты, используя повседневный опыт,
могут весьма точно сказать, сколько времени займет обработка пакета из 1 ООО по­
лисов. Если в очереди есть несколько одинаково важных заданий, планиров­
щик выбирает самое короткое задание первым. Посмотрите на рис. 2.9, а. У нас
есть четыре задания: А, В, С и D с временем выполнения 8, 4, 4 и 4 мин соответ­
ственно. Если мы запустим их в данном порядке, время оборота задания А будет
8 мин, В - 1 2 мин, С - 1 6 мин, D - 20 мин, а среднее время составит 14 мин.

8 4 4 4 4 4 4 8

А в с D в с D А

а б
Пример планирования по алгоритму «самое короткое задание - первое»: запуск
Рис. 2 . 9 . а -

четырех заданий в исходном порядке; б - запуск в соответствии с алгоритмом


А теперь запустим задания в соответствии с алгоритмом �самое короткое зада­
ние - первое�, как показано на рис. 2 . 1 0 , б. Значения времени оборота составят
4, 8, 1 2 и 20 мин соответственно, а среднее время будет равно 1 1 мин. Алгоритм
оптимизирует задачу. Рассмотрим четыре процесса с временем выполнения а, Ь,
с и d. Первое задание выполняется за время а, второе - за время а + Ь и т. д.
Среднее время оборота будет равно (4а + ЗЬ + 2с + d)/4. Очевидно, что вклад
времени а в среднее время больше, чем всех остальных интервалов времени,
поэтому первым должно выполняться самое короткое задание, а последним -
самое длительное, вносящее вклад, равный собственному времени оборота. Точ­
но так же рассматривается система с любым количеством заданий.
Следует отметить, что алгоритм �самое короткое задание - первое� является
оптимальным лишь при наличии сразу всех заданий. В качестве контрпримера
можно рассмотреть пять заданий, А, В, С, D и Е, причем первые два доступны
1 26 Глава 2. П роцессы

сразу же, а три оставшиеся - через три минуты. Время выполнения этих за­
даний составляет 2 , 4, 1, 1 и 1 мин соответственно, а время оборота О, О, 3, 3 -

и 3 мин. Вначале можно выбрать только А или В, поскольку остальные недос­


тупны. Если руководствоваться алгоритмом �самое короткое задание - первое»,
задания будут запущены на обработку в следующем порядке: А, В, С, D, Е и сред­
нее время оборота составит 4,6 мин. Если же запустить их в порядке В, С, D, Е, А,
оно будет равно 4,4 мин.

Зада ние с н аиме ньшим временем завершения


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

Т рехуровневое планирован ие
С определенной точки зрения системы пакетной обработки позволяют осуще­
ствлять планирование на трех разных уровнях, как показано на рис. 2 . 1 0. После
поступления в систему новые задания сначала помещаются в очередь, храни­
мую на жестком диске. Далее планировщик допуска решает, какие задания при­
нять в систему. Остальные ожидают во входной очереди, пока не будут выбра­
ны планировщиком. Как правило, контроль допуска обеспечивает желательное
сочетание заданий, ограниченных возможностями процессора и ввода-вывода.
Возможен и другой подход - короткие задания выбираются быстрее, чем дли­
тельные. Планировщик допуска может выбирать задания из очереди по собст­
венному усмотрению, независимо от того, какие из них пришли раньше, а какие -
позже.

1 Процессор о 1
t Планировщик процессора
�о r ооооо
Задание Входная очередь

rЬd
1 1 101010 101

� О·=-.::.....
Планировщик Планировщик Диск
допуска памяти
Рис . 2 . 1 0 . Трехуровневое планирование
2.4. П л анирование 1 27

После того как задание допущено в систему, для него может быть создан про­
цесс, способный претендовать на ресурсы процессора. Тем не менее возможна
ситуация, в которой процессов окажется так много, что в имеющейся памяти их
не удастся разместить. В этом случае часть процессов будет вытеснена на жест­
кий диск. Какие процессы вытеснить, а какие оставить в памяти, решает второй
уровень планирования, а соответствующий планировщик называется планиров­
щиком памяти.
Планирование памяти должно осуществляться регулярно, поскольку процессы,
находящиеся на диске, нуждаются в обслуживании. Однако перенос процесса
с диска в оперативную память является затратным, а потому планирование имеет
смысл выполнять не чаще одного раза в секунду. Постоянное •жонглирование•
содержимым памяти приведет к тому, что значительная часть пропускной спо­
собности диска будет потеряна, замедлив ввод-вывод файлов.
Чтобы оптимизировать производительность системы в целом, планировщику па­
мяти имеет смысл рассчитать желаемое число и типы процессов в памяти. Число
процессов называется степенью многозадачности. Зная, какие процессы ограни­
чены вычислительными возможностями, а какие - возможностями ввода-вывода,
планировщик может нацелить усилия на поддержку в памяти нужного сочета­
ния процессов. В качестве очень грубого приближения можно привести следую­
щий пример: если некоторый класс процессов тратит 20 % времени на вычисле­
ния, то наличие в памяти 5 таких процессов обеспечит 100-процентную загрузку
процессора.
Планировщик памяти периодически анализирует каждый процесс на жестком
диске, чтобы решить, следует ли перенести его в основную память. Решение мо­
жет основываться на следующих критериях.
+ Сколько времени прошло с последнего переноса процесса в оперативную па-
мять или на жесткий диск?
+ Сколько процессорного времени получил данный процесс за последнее время?
+ Каков объем процесса? (Небольшие процессы вряд ли переполнят память.)
+ Насколько важным является процесс?
На третьем уровне планирования из находящихся в оперативной памяти процес­
сов выбирается тот, который будет запущен следующим. Как правило, плани­
ровщика этого уровня называют планировщиком процессора и именно его чаще
всего имеют в виду, говоря о планировании. Планировщик процессора может
использовать любой подходящий алгоритм с вытеснением или без вытесне­
ния. Часть алгоритмов уже нами рассмотрена; другие мы рассмотрим в следую­
щем разделе.

2 . 4 . З . Планирован ие в и нтерактивных системах


Теперь мы сосредоточим внимание на некоторых алгоритмах, которые можно
использовать в интерактивных системах. Все эти алгоритмы также подходят
для планировщика процессора пакетных систем. В интерактивных системах нет
1 28 Глава 2. П роцессы

трехуровневого планирования, однако планировщики памяти и процессора су­


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

Циклическое планирован ие
Одним из наиболее старых, простых, справедливых и часто используемых явля­
ется алгоритм циклического, или карусельного, планирования. Каждому процес­
су предоставляется некоторый интервал времени процессора, так называемый
квантом. Если к концу кванта процесс все еще работает, он прерывается, а управ­
ление передается другому процессу. Разумеется, если процесс блокируется или
прекращает работу раньше, переход управления происходит в этот момент. Реа­
лизация циклического планирования проста. Планировщику нужно всего лишь
согласно поддерживать список процессов в состоянии готовности (рис. 2. 1 1 , а).
Исчерпав свой лимит времени, процесс отправляется в конец списка (рис. 2. 1 1 , б).

Текущий Текущий
процесс


а б
Рис. 2. 1 1 . Циклическое планирование: - список процессов в состоянии готовности;
а
б- список процессов в состоянии готовности после того, как процесс В
исчерпал свой квант
Единственным интересным моментом этого алгоритма является величина кван­
та. Переключение с одного процесса на другой занимает некоторое время -
необходимо сохранить и загрузить регистры и карты памяти, обновить таблицы
и списки, сохранить и перезагрузить кэш памяти и т. п. Представим, что переклю­
чение процессов, или переключение контекста, как его иногда называют, занимает
1 мс, включая переключение карт памяти, перезагрузку кэша и т. п. Пусть вели­
чина кванта установлена равной 4 мс. В таком случае 20 % процессорного време­
ни уйдет на администрирование - это слишком много.
Для повышения эффективности назначим размер кванта, скажем, 1 00 мс. Теперь
пропадает только 1 % времени. Но представьте, что будет в системе разделения
времени, если 10 пользователей одновременно нажмут клавишу возврата карет­
ки. В список будет занесено 1 О процессов. Если процессор был свободен, первый
процесс будет запущен немедленно, второму придется ждать 1 00 мс и т. д. По­
следнему процессу, возможно, придется ждать целую секунду, если все осталь­
ные не блокируются за время кванта. Большинству пользователей секундная за­
держка вряд ли понравится.
Важен и тот фактор, что если установленное значение кванта больше среднего
интервала работы процессора, переключение процессов будет происходить ред­
ко. Напротив, большинство процессов окажутся заблокированными прежде, чем
истечет квант, сто потребует переключение процессов. Устранение принудитель­
ных переключений процессов повышает производительность системы, так как
2 . 4 . План ирование 1 29

переключения будут происходить только тогда, когда это логически необходимо,


то есть когда процесс заблокирован и не может продолжать работу.
Вывод можно сформулировать следующим образом: слишком малый квант ведет
к частому переключению процессов и небольшой эффективности, но слишком
большой квант может привести к замедленной реакции на короткие интерактивные
запросы. Разумным компромиссом обычно является значение кванта в 20-50 мс.

П риоритетное планирование
Алгоритм карусельного планирования базируется на важном допущении о том,
что все процессы равнозначны. В случае компьютера с большим числом пользо­
вателей это может быть не так. Например, в университете первыми должны об­
служиваться деканы, затем профессора, секретари, уборщицы, сторожа и лишь
потом студенты. Необходимость принимать во внимание подобные внешние фак­
торы приводит к приоритетному планированию. Основная идея проста: каждому
процессу присваивается свой приоритет в 4Табеле о рангах� и управление пере­
дается готовому к работе процессу с самым высоким приоритетом.
Даже на персональном компьютере с одним пользователем могут выполняться
несколько процессов, отдельные из которых являются более важными, чем дру­
гие. Демон, отвечающий за пересылку электронной почты в фоновом режиме,
имеет более низкий приоритет, чем процесс, отображающий на экране видео­
фильм в реальном времени.
Чтобы предотвратить бесконечное выполнение высокоприоритетных процессов,
планировщик может уменьшать приоритет выполняемого процесса с каждым
тактом (то есть по каждому прерыванию от таймера). Если в результате приори­
тет текущего процесса окажется ниже приоритета следующего процесса, про­
изойдет переключение. Возможно также предоставление каждому процессу мак­
симального отрезка времени работы. Как только время кончается, управление
передается следующему по значимости процессу.
Приоритеты процессам могут присваиваться статически или динамически. На во­
енной базе процессу, запущенному генералом, присваивается приоритет 100, пол­
ковником 90, майором 80, капитаном 70, лейтенантом 60 и т. д. А в ком­
- - - -

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


может стоить 100 долларов в час, со средним 75, с низким 50. Команда n i c e
- -

в системе UNIX позволяет пользователю добровольно снизить приоритет своих


процессов, проявляя любезность по отношению к остальным пользователям.
Справедливости ради следует отметить, что этой командой никто никогда не
пользуется.
Система вправе динамически назначать приоритеты для достижения своих целей.
Например, некоторые процессы настолько ограничены возможностями устройств
ввода-вывода, что большую часть времени проводят в ожидании завершения
соответствующих операций. Когда бы ни потребовался процессор такому про­
цессу, его следует немедленно предоставить, чтобы процесс мог начать обраба­
тывать следующий запрос ввода-вывода, который будет выполняться параллель­
но с вычислениями другого процесса. Если заставить процесс, ограниченный
1 30 Глава 2. П роцессы

возможностями устройств ввода-вывода, длительное время ждать доступа к про­


цессору, он будет неоправданно долго находиться в памяти. Простой алгоритм
обслуживания процессов, сдерживаемых возможностями устройств ввода-выво­
да, состоит в установке приоритета, равного 1//, где / - часть использованного
в последний раз кванта. Процесс, утилизировавший всего 1 мс из 50-миллисе­
кундного кванта, получит приоритет 50, процесс, использовавший 25 мс, полу­
чит приоритет 2, а процесс, задействовавший весь квант, получит приоритет 1 .
Часто бывает удобно сгруппировать процессы в классы п о приоритетам и ис­
пользовать приоритетное планирование среди классов и циклическое планиро­
вание внутри каждого класса. На рис. 2. 1 2 представлена система с четырьмя
классами приоритетов. Алгоритм планирования выглядит следующим образом:
пока в классе 4 есть готовые к запуску процессы, они запускаются один за
другим сагласно алгоритму циклического планирования и каждому отводится
квант времени. При этом классы с более низким приоритетом не будут их беспо­
коить. Если в классе 4 нет готовых к запуску процессов, запускаются процессы
класса 3 и т. д. Если приоритеты постоянны, до процессов класса 1 процессор
может не дойти никогда.

Готовые
Начало очередей к запуску процессы
Приоритет 4 (Самый высокий приоритет)
Приоритет 3
Приоритет 2
Приоритет 1 (Самый низкий приоритет)
Рис. 2. 1 2 . Алгоритм планирования с четырьмя классами приоритетов

Операционная система MINIX 3 использует похожий алгоритм, однако по умол­


чанию в ней поддерживаются 16 классов приоритетов. В MINIX 3 компоненты
операционной системы выполняются как процессы. Задания (драйверы ввода­
вывода) и серверы (менеджер памяти, файловая система и сеть) имеют наиболее
высокие приоритеты. Начальный приоритет каждого задания или службы опре­
деляется во время компиляции. Драйверу медленного устройства ввода-вывода
можно задать более низкий приоритет, чем драйверу быстрого устройства или
сервера. Как правило, приоритеты процессов пользователя ниже, чем систем­
ных компонентов, однако во время выполнения все приоритеты могут меняться.

Планирование с несколькими очередями


Один из первых 4Приоритетных� планировщиков был реализован в системе
CTSS [23) . Основной проблемой системы CTSS стало слишком медленное пере­
ключение процессов, поскольку в памяти компьютера I B M 7094 мог находиться
только один процесс. Каждое переключение означало выгрузку текущего про­
цесса на диск и считывание нового процесса с диска. Разработчики CTSS быст­
ро сообразили, что эффективность будет выше, если процессам, ограниченным
2 .4. План ирование 1 31

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


большие кванты, но часто. С одной стороны, это уменьшит количество подкачек
с диска в память, но с другой, как мы уже видели, приведет к увеличению времени
отклика. В результате было разработано решение с классами приоритетов. Про­
цессам класса с максимальным приоритетом выделялся один квант, процессам
следующего класса - два кванта, следующего - четыре кванта и т. д. Когда про­
цесс использовал все отведенное ему время, он переходил на класс ниже.
В качестве примера рассмотрим процесс, которому необходимо производить вы­
числения в течение 1 00 квантов. Вначале ему будет предоставлен один квант, за­
тем процесс будет сброшен на диск. В следующий раз ему достанется 2 кванта,
затем 4, 8, 1 6, 32, 64, хотя из 64 он использует только 37. В этом случае понадо­
бятся всего 7 подкачек (включая первоначальную загрузку) вместо 1 00, которые
понадобились бы в случае циклического алгоритма. Помимо того, по мере погру­
жения в очередь приоритетов процесс будет запускаться все реже, в пользу более
коротких процессов.
Чтобы дать погибнуть процессу, который при запуске считался «долгоиграю­
щим�, но позже стал интерактивным, была разработана следующая стратегия.
Как только с терминала приходит сигнал возврата каретки, процесс, соответст­
вующий этому терминалу, переносится в класс максимального приоритета, по­
скольку предполагается, что он становится интерактивным. Однако однажды
пользователь, запустивший процесс, значительно ограниченный вычислитель­
ными возможностями, обнаружил, что простое нажатие клавиши Enter сущест­
венно сокращает время отклика, и рассказал об этом друзьям. Мораль этой исто­
рии такова: осуществить задуманное на практике гораздо сложней, чем в теории.
Для разделения процессов по классам используются многие другие алгоритмы.
Например, в системе XDS 940, разработанной в Беркли [ 7 1 ) , было четыре класса
приоритетов: терминал, ввод-вывод, короткий квант и длинный квант. Когда
запускался процесс, ожидающий вывода на терминал, он перемещался в класс
высшего приоритета (терминал). Когда снималась блокировка процесса, ожидав­
шего доступа к диску, он переходил во второй класс. Если к концу отведенного
времени процесс все еще работал, он сначала зачислялся в третий класс. Если про­
цесс слишком много раз полностью задействовал свой квант времени, не бло­
кируясь на терминале или другом устройстве ввода-вывода, он перемещался в по­
следний класс. Этот метод практикуется во многих системах для предоставления
преимущества интерактивным процессам по сравнению с фоновыми.

Самый короткий процесс - следующий


Поскольку в пакетных системах выполнение самых коротких заданий первыми
обеспечивает минимальное среднее время отклика, было бы весьма желательно
добиться подобного результата и при интерактивной обработке. Как правило,
функционирование интерактивного процесса состоит из циклической последова­
тельности ожиданий исполнения и исполнения команд. Если уподобить исполне­
ние команды «заданию�, то, выполняя самые короткие процессы первыми, мы мог­
ли бы свести общее время отклика к минимуму. Единственной проблемой является
определение кратчайшего среди текущих, готовых к выполнению процессов.
1 32 Глава 2. Процессы

Один из подходов - оценить время окончания процессов на основе их поведе­


ния в прошлом и выбрать тот процесс, чья оценка минимальна. Предположим,
что для некоторого терминала оценка длительности обработки одной команды
при первом запуске составила Т0 , а при втором запуске - Т1• Мы можем обновить
первичную оценку, взяв взвешенную сумму двух результатов: а Т0 + ( 1 - а) Т1• От
значения а зависит скорость убывания влияния старых запусков на общую оцен­
ку. Например, если а = 1 /2 , ряд оценок будет следующим:
Т0 , Т0/2 + Т1/2, Т0/4 + Т1/4 + Т2/2, Т0/8 + Т1/8 + Т2/4 + Т3/2.
После трех запусков весовой коэффициент Т0 снизился до 1 /8.
Метод оценки путем взвешивания текущего и предыдущих результатов иногда
называют старением. Он применим во многих ситуациях, где необходимо выпол­
нить прогноз на основе множества измерений. Метод старения особенно легко
реализовать для а = 1 / 2. Все, что требуется сделать, - сложить новое значение
с текущей оценкой и разделить сумму пополам (сдвигом вправо на один бит).

Гарантированное планирование
Принципиально другим подходом к планированию является предоставление поль­
зователям реальных обещаний и затем их исполнение. Вот обещание, которое
легко произнести и легко выполнить: если вместе с вами с процессором работа­
ют п пользователей, вам будет предоставлено 1/п мощности процессора. И в сис­
теме с одним пользователем и п запущенными процессорами каждому достанет­
ся 1/п циклов процессора.
Чтобы сдержать это обещание, система должна отслеживать распределение про­
цессора между процессами с момента создания каждого процесса. Затем система
рассчитывает количество ресурсов процессора, на которое процесс имеет право,
например, время с момента создания, деленное на п. После этого можно сосчи­
тать отношение времени, предоставленного процессу, к времени, на которое он
имеет право. Полученное значение 0,5 означает, что процессу выделили только
половину положенного, а 2,0 говорит о том, что процессу досталось в два раза
больше его нормы. Далее запускается процесс, у которого это отношение наи­
меньшее, пока оно не станет больше, чем у его ближайшего соседа.

Лотерейное планирование
Хотя идея обещаний пользователям и их выполнения хороша, но ее трудно реа­
лизовать. Для получения предсказуемых результатов применяется другой алго­
ритм, называемый лотерейным планированием [ 1 26].
В его основе лежит раздача процессам «лотерейных билетов� на доступ к раз­
личным ресурсам, в том числе к процессору. Когда планировщику необходимо
принять решение, выбирается случайным образом лотерейный билет, и его обла­
датель получает доступ к ресурсу. Что касается доступа к процессору, «лотерея�
может разыгрываться 50 раз в секунду, и победитель получает 20 мс времени
процессора.
2. 4 . Планирование 1 33

Если перефразировать Джорджа Оруэлла, можно сказать, что все процессы


равны, но некоторые равнее других. Более важным процессам можно раздать
дополнительные билеты, чтобы повысить вероятность выигрыша. Если тираж
всего 1 00 билетов и 20 из них находятся у одного процесса, то ему достанется
20 % времени процессора. В отличие от приоритетного планирования, где очень
трудно оценить, что означает, скажем, приоритет 40, в лотерейном планировании
все очевидно. Каждый процесс получит процент ресурсов, примерно равный про­
центу имеющихся у него билетов.
Лотерейное планирование характеризуется несколькими интересными свойства­
ми. Например, если при создании процессу достается несколько билетов, то уже
в следующем розыгрыше его шансы на выигрыш пропорциональны количеству
билетов. Другими словами, лотерейное планирование обладает высокой «отзыв­
чивостью» .
Взаимодействующие процессы могут п р и необходимости обмениваться биле­
тами. Так, если клиентский процесс посылает сообщение серверному процессу
и затем блокируется, он вправе передать все свои билеты серверному процессу,
чтобы увеличить шанс запуска сервера. Когда серверный процесс заканчивает
работу, ему ничего не стоит вернуть все билеты обратно. Действительно, если
клиентов нет, то и билеты серверу не нужны.
Лотерейное планирование позволяет решать задачи, которые не решить с помо­
щью других алгоритмов. В качестве примера приведем видеосервер, на котором
несколько процессов передают своим клиентам потоки видеоинформации с раз­
личной частотой кадров. Предположим, что процессы используют частоты 1 0, 20
и 25 кадров в секунду. Предоставив процессам соответственно 1 0, 20 и 25 биле­
тов, можно реализовать загрузку процессора в желаемой пропорции 10:20:25.

Справедливое планирован ие
До настоящего момента мы планировали процессы, игнорируя вопрос о том, кто
является их владельцами. Если, к примеру, пользователь 1 запустит 9 процессов,
а пользователь 2 - 1 процесс, то, согласно циклическому или приоритетному
планированию, пользователь 1 получит 90 % процессорного времени, а пользо­
ватель 2 - 1 0 %.
Чтобы избежать подобного «неравенства» , некоторые системы используют в пла­
нировании информацию о владельцах процессов. Каждому пользователю пре­
доставляется определенная доля ресурсов процессора, и процессы выбираются
на исполнение таким образом, чтобы обеспечить принятый вариант разделения.
Например, если двум пользователям обещана половина процессорного времени,
система обеспечит этот показатель независимо от запущенных ими процессов.
Пусть пользователь 1 запустил четыре процесса А, В, С и D, а пользователь
-

2 - единственный процесс Е. Если в системе принято циклическое планирова­


ние, то всем требованиям удовлетворит следующая последовательность выпол­
нения процессов:
A E B E C E D E A E B E C E D E ...
1 34 Глава 2 . Процессы

Если пользователю 1 предоставлено вдвое больше процессорного времени, чем


пользователю 2, последовательность примет вид:
A B E C D E A B E C D E ...
Разумеется, справедливое планирование может быть реализовано и множеством
других способов, в зависимости от принятого толкования справедливости.

2 . 4 . 4 . План и рование в системах


реал ьного времени
В системах реалъноzо времени, как и следовало ожидать, существенную роль
играет время. Чаще всего одно или несколько внешних физических устройств
генерируют входные сигналы, и компьютер должен адекватно на них реагировать
в течение заданного временно го интервала. Например, компьютер в проигрыва­
теле компакт-дисков получает биты от дисковода и должен за очень маленький
промежуток времени преобразовать их в музыку. Если процесс преобразования
будет слишком долгим, звук окажется искаженным. Подобные системы исполь­
зуются для наблюдения за пациентами в палатах интенсивной терапии, для ав­
томатического пилотирования самолета, для управления роботами на автомати­
зированном производстве. В любом из этих случаев запоздалая реакция ничуть
не лучше, чем отсутствие реакции.
Системы реального времени делятся на жесrтше системы реалъноzо времени, что
означает наличие жестких сроков для каждого задания (в них обязательно надо
укладываться), и zиб'КUе системы реалъноzо времени, в которых нарушения вре­
менн о го графика нежелательны, но допустимы. в обоих случаях реализуется
разделение программы на несколько процессов, каждый из которых предсказу­
ем. Эти процессы чаще всего бывают короткими и завершают свою работу в те­
чение секунды. Когда появляется внешний сигнал, именно планировщик должен
обеспечить соблюдение графика.
Внешние события, на которые система должна реагировать, можно разделить на
периодичеС'КИе (возникающие через регулярные интервалы времени) и неперио­
дичес'КИе (возникающие непредсказуемо). Возможно наличие нескольких потоков
периодических событий, которые система должна обрабатывать. В зависимости
от времени, затрачиваемого на обработку каждого из событий, может оказаться,
что система не в состоянии своевременно обработать их все. Если в систему по­
ступает т периодических событий, событие с номером i поступает с периодом Р;
и на его обработку уходит
С; секунд работы процессора, то своевременную обра­
ботку всех потоков обеспечивает выполнение следующего условия:

! СР;;
i=t
�1.

Системы реального времени, удовлетворяющие этому условию, называются пла ­


нируемыми.
В качестве примера рассмотрим гибкую систему реального времени с тремя пе­
риодическими сигналами с периодами в 1 00, 200 и 500 мс соответственно. Если
2. 4 . Планирование 1 35

на обработку этих сигналов уходит, в том же порядке, 50, 30 и 1 00 мс, система


является планируемой, поскольку 0,5 + О, 15 + 0,2 < 1. Даже при добавлении чет­
вертого сигнала с периодом в 1 с системой все равно можно будет управлять пу­
тем планирования, пока время обработки сигнала не превысит 150 мс. В этих
расчетах существенным является предположение, что время переключения меж­
ду процессами пренебрежимо мало.
Алгоритмы планирования для систем реального времени бывают как статиче­
скими, так и динамическими. В первом случае все решения по планированию
принимаются заранее, еще до запуска системы. Во втором случае решения выно­
сятся по ходу дела. Статическое планирование применимо лишь при наличии
априори точной информации о работе, которую необходимо выполнить, и огра­
ничениях, которые должны быть соблюдены. Алгоритмы динамического плани­
рования не выдвигают подобных требований.

2 . 4 . 5 . П ол итика и механ изм планирования


Вплоть до настоящего момента мы подразумевали, что процессы в системе при­
надлежат разным пользователям и конкурируют за доступ к процессору. Чаще
всего именно так все и происходит, но возможна ситуация, в которой под управле­
нием одного процесса выполняются множество дочерних процессов. Например,
у процесса, управляющего базой данных, может быть много дочерних процес­
сов, обрабатывающих отдельные запросы или выполняющих конкретные функции
(анализ запроса, доступ к диску и т. п.). Вполне вероятно, что родительский про­
цесс лучше представляет, какой из его дочерних процессов более важен (или для
которого фактор времени более критичен), а какой - менее. К сожалению, ни
один из рассмотренных нами планировщиков не в силах получать информацию
от пользовательских процессов и учитывать ее при принятии решений по пла­
нированию. В результате планировщики редко выносят оптимальное решение.
Преодолеть проблему можно, отделив механизм планирования от полиmи'IСU пла­
нирования. Таким образом, мы реализуем ситуацию, в которой алгоритм плани­
рования будет каким-либо образом параметризован, но параметры могут быть
заданы пользовательским процессом. Обратимся еще раз к примеру базы дан­
ных. Пусть ядро поддерживает алгоритм приоритетного планирования, но суще­
ствует системный запрос, посредством которого процесс может устанавливать
(и менять) приоритеты своих дочерних процессов. В этом случае родительский
процесс имеет право управления планированием дочерних процессов, хотя сам
он планирования не осуществляет. Механизм определяется ядром, но политику
задает пользовательский процесс.

2 . 4 . 6 . Планирование програм м н ых потоков


Если некоторые процессы системы используют несколько программных пото­
ков, мы сталкиваемся с двумя уровнями параллелизма: процессами и программ­
ными потоками. В таких системах планирование выполняется принципиально
1 36 Глава 2. П роцессы

другим способом, в зависимости от уровней программных потоков, поддержи­


ваемых системой: пользовательского, ядра или обоих одновременно.
Сначала рассмотрим пользовательский уровень. Поскольку ядро не всегда знает
о существовании программных потоков, оно обращается с процессами стандарт­
ным образом, выбирая, к примеру, процесс А и предоставляя ему управление
в течение заданного кванта. Планировщик программных потоков внутри процес­
са А решает, какой программный поток запустить - предположим, А 1. Поскольку
не существует прерываний от таймера, поддерживающих потоковую многозадач­
ность, выполнение программного потока А 1 будет продолжаться столько, сколь­
ко необходимо самому потоку. А если он займет весь квант, выделенный процес­
су А, ядро попросту выберет следующий процесс для обслуживания.
Когда процесс А будет запущен снова, программный поток А 1 возобновит вы­
полнение и будет занимать время процесса А до тех пор, пока не завершится.
Однако такое поведение никак не затронет другие процессы. Они будут полу­
чать свою долю процессорного времени независимо от того, что происходит
в процессе А.
Теперь представим, что время выполнения программных потоков процесса А су­
щественно меньше, чем квант выделяемого ему времени: скажем, каждый поток
выполняется в течение 5 мс, в то время как квант длится 50 мс. В этом случае
в течение кванта планировщик программных потоков несколько раз передаст
управление различным программным потокам. Например, это может привести
к такой последовательности обслуживания потоков: А 1, А2, АЗ, А 1, А2, АЗ, А 1,
А2, АЗ, А 1, после чего ядро переключится на процесс В. Данную ситуацию иллю­
стрирует рис. 2 . 1 3, а.

Порядок Процесс А Процесс В Процесс А Процесс В


выполнения
потоков

2. Система
реального
времени
выбирает поток
1 . Ядро выбирает процесс 1 . Ядро выбирает поток lj
Возможные: А 1 , А2, АЗ, А 1 , А2, АЗ Возможные: А 1 , А2, АЗ, А 1 , А2, АЗ
Невозможные: А1 , В1 , А2, В2, АЗ, ВЗ Также возможные: А1 , В1 , А2, В2, АЗ, ВЗ
а б
Планирование программных потоков: - планирование программных
Рис. 2. 1 3 . а
потоков уровня пользователя при времени выполнения потока 5 мс и кванте 50 мс;
б -планирование программных потоков уровня ядра в тех же условиях
Система реального времени может использовать любой из алгоритмов плани­
рования, описанных ранее. На практике чаще всего прибегают к циклическому
и приоритетному алгоритмам. Единственным ограничением является отсутствие
таймера, прерывающего программный поток, который выполняется слишком дошо.
2 . 5 . Процессы в MI N IX 3 1 37

Теперь рассмотрим ситуацию с программными потоками уровня ядра. Ядро само


выбирает программный поток для выполнения, при этом оно решает, учитывать
или не учитывать процесс, которому принадлежит программный поток. Каждому
потоку выделяется квант времени, и, если он не успевает завершиться к оконча­
нию кванта, выполнение потока приостанавливается. Если квант процесса равен
50 мс, а квант программного потока 5 мс, то в случайно взятые 30 мс процес­
-

сорного времени возможна следующая последовательность исполнения про­


граммных потоков: А 1, В1, А2, В2, ВЗ. В аналогичных условиях такая ситуация
исключена с программными потоками уровня пользователя. Иллюстрация пла­
нирования программных потоков уровня ядра приведена на рис. 2. 13, б.
Основное отличие между программными потоками уровней пользователя и ядра
заключается в производительности. Для переключения между пользовательски­
ми программными потоками требуется всего несколько машинных команд, в то
время как на уровне ядра осуществляется полное переключение контекста, вклю­
чая смену карты памяти и очистку кэша. Это отнимает на несколько порядков
больше времени. С другой стороны, блокирование программного потока по вводу­
выводу на уровне ядра не останавливает весь процесс, как это происходит на
уровне пользователя.
Поскольку ядро знает о том, что переключение между потоком процесса А и по­
током процесса В является более затратным, чем запуск следующего программ­
ного потока процесса А (из-за изменения карты памяти и потери актуальности
кэша), эту информацию можно использовать при принятии решения. К примеру,
при прочих равных условиях планировщик может отдать предпочтение про­
граммному потоку, принадлежащему тому же процессу, что и только что забло­
кированный поток, а не потоку, принадлежащему другому процессу.
Еще один важный фактор заключается в том, что программные потоки уровня
пользователя могут использовать планировщик потоков, предоставляемый прило­
жением. К примеру, веб-сервер имеет специальный программный поток диспет­
черизации, принимающий и распределяющий входящие запросы рабочим про­
граммным потокам. Если рабочий поток оказывается заблокированным, какой
поток должен выполняться следующим? Приложение, знающее назначение всех
программных потоков, безусловно, выберет диспетчера, который получит возмож­
ность запустить еще один рабочий поток. Подобная стратегия максимизирует
степень параллелизма в среде, где рабочие программные потоки часто блокиру­
ются дисковым вводом-выводом. Ядро же никогда не узнает, для чего использует­
ся тот или иной поток, хотя существует возможность присвоения программным
потокам различных приоритетов. В целом, внутренние планировщики заданий
управляют работой приложения более эффективно, чем ядро.

2 . 5 . П роцессы в M I N IX 3
Сейчас, завершив изучение принципов управления процессами, взаимодействия
между ними и алгоритмы планирования, мы можем приступить непосредствен­
но к их реализации в MINIX 3. В отличие от UNIX, где ядро представляет собой
1 38 Глава 2 . Процессы

монолитную программу, не разбитую на модули, ядро MINIX само является на­


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

2 . 5 . 1 . Внутренняя структура системы M I N IX 3


Начнем наше изучение MINIX с общего обзора системы. Структурно система
разбита на четыре уровня, каждый из которых выполняет строго определенные
функции. Эти уровни показаны на рис. 2. 14.

1 ! !
Уровень

Польэоввтельский Пользовательский Пользовательский Пользовательские


• • •
4

1 1 ...
lпit процесс процесс процесс процессы

Менеджер Файловая Информационный Сетевой Серверные Режим


з

1 1 ...
процессов система сервер сервер процессы пользователя

Драйвер Драйвер Драйвер Драйверы


2 диска терминала устройств
Ethemet

Я дро
1
1
1
Таймерное
задание
:
1
Системное
задание
Ядро } Режим ядра

Рис. 2 . 1 4 . Четыре уровня структуры MINIX 3. Только процессы нижнего уровня могут
использовать привилегированные команды (команды режима ядра)
Ядро, находящееся на нижнем уровне, осуществляет обработку и управление со­
стояниями готовности, выполнения и блокировки (см. рис. 2.2). Кроме того, яд­
ро обрабатывает все сообщения, передаваемые между процессами, - проверяет
допустимость адресата, обнаруживает в физической памяти буферы приема и пе­
редачи и копирует байты от отправителя к получателю. Частью ядра является
механизм доступа к портам ввода-вывода и прерываниям, в современных про­
цессорах требующий использования привилегированных команд режима ядра,
которые недоступны обычным процессам.
Помимо ядра, нижний уровень содержит два модуля, функционирующих по­
добно драйверам устройств. Таймерное задание представляет собой драйвер вво­
да-вывода в том смысле, что оно взаимодействует с аппаратным обеспечением,
генерирующим тактовые сигналы. Тем не менее, в отличие от драйвера диска
или линии связи, оно недоступно пользователю, поскольку имеет интерфейс
только с ядром.
Одной из главных функций уровня 1 является предоставление вышестоящим
драйверам и серверам набора вызовов ядра. С помощью этих вызовов осуществ­
ляется чтение и запись портов ввода-вывода, копирование данных между адрес­
ными пространствами и т. д. Реализация вызовов ядра возложена на системное
задание. Хотя системное и таймерное задания находятся в адресном пространстве
ядра, их планирование осуществляется раздельно, а сами процессы имеют собст­
венные стеки вызовов.
2.5. Процессы в MI N IX 3 1 39

Большая часть ядра и все задания ядра и системы написаны на языке С. Однако
в некоторых небольших фрагментах ядра используется ассемблер - в тех, кото­
рые отвечают за обработку прерываний, низкоуровневые механизмы управления
переключением контекста между процессами (сохранение и восстановление ре­
гистров и т. п.) и устройствами управления памятью. В общем, ассемблер приме­
нялся там, где ядру было необходимо непосредственное низкоуровневое взаимо­
действие с аппаратным обеспечением, недоступное для языка С. При переносе
MINIX 3 на новую архитектуру ассемблерный код должен быть соответствую­
щим образом переписан.
Три уровня, находящихся над ядром, могут рассматриваться как один, поскольку
ядро, в сущности, взаимодействует с ними одинаково. Уровни ограничены коман­
дами полъзователъскоzо режима, а ядро планирует запуск каждого из них. Уров­
ни не имеют прямого доступа к портам ввода-вывода и, более того, ни один из
них не способен обращаться к памяти за пределами выделенного ему сегмента.
Тем не менее привилегии процессов (к примеру, возможность вызывать ядро)
могут различаться. На этом и построено разделение по уровням 2, 3 и 4. Процессы
уровня 2 имеют самые широкие привилегии, процессы уровня 3 - более ограни­
ченные, и, наконец, процессы уровня 4 совсем лишены привилегий. К примеру,
драйверы устройств, находящиеся на уровне 2, могут без ограничений требо­
вать, чтобы системное задание выполнило чтение или запись данных в порты
ввода-вывода. Драйвер необходим каждому типу устройств - дискам, принте­
рам, терминалам, сетевым интерфейсам и др. Драйверы устройств могут выпол­
нять и другие вызовы ядра, например копирование считанных данных в адрес­
ное пространство другого процесса.
Третий уровень включает серверы - процессы, предоставляющие полезные услу­
ги пользовательским процессам. Два сервера являются обязательными. Менеджер
процессов (Process Manager, РМ) выполняет все системные вызовы MINIX 3,
связанные с запуском и остановкой процессов (например, f o rk, е х е с и exi t ) ,
а также сигналами (например, a l arrn и k i l l , т о есть вызовами, способными из­
менить состояние процесса). Кроме того, менеджер процессов ответственен за
управление памятью, в частности, с помощью системного вызова br k. Файловая
система (File Systern, FS) выполняет все файловые системные запросы, такие
как re ad, rnount и chdi r.
Важно понимать различие между вызовами ядра и системными РОSIХ-вызова­
ми. Вызовы ядра представляют собой низкоуровневые функции, предоставляе­
мые системным заданием драйверам и серверам для выполнения необходимых
действий. Типичный вызов ядра - чтение данных из аппаратного порта ввода­
вывода. Системные РОSIХ-вызовы, такие как read, f o rk и unl ink, напротив,
являются высокоуровневыми и доступны программам, расположенным на уров­
не 4. Пользовательские программы выполняют большое количество систем­
ных вызовов, однако не выполняют вызовов ядра. Конечно, если разрешить себе
�жонглирование• терминами, вызов ядра можно было бы назвать системным
вызовом. Механизмы выполнения обоих типов вызовов схожи, и вызовы ядра
допустимо рассматривать как особый частный случай системных вызовов.
1 40 Глава 2. Процессы

В дополнение к менеджеру процессов и файловой системе, уровень 3 включает


и другие серверы, выполняющие функции, специфичные для MINIX 3. Можно
с уверенностью утверждать, что функциональность менеджера процессов и фай­
ловой системы поддерживается в любой операционной системе. Информацион­
ный сервер (Inforrnation Server, IS) предоставляет отладочную и статусную ин­
формацию о других драйверах и серверах. Подобная возможность необходима
скорее в экспериментальной системе, подобной MINIX 3, нежели в коммерче­
ской операционной системе, которую пользователи изменять не могут. Сервер
реинкарнации (Reincarnation Server, RS) стартует одновременно с ядром; его на­
значение - запускать и при необходимости перезапускать драйверы устройств,
отсутствующие в памяти. В частности, при сбое драйвера в процессе работы сер­
вер реинкарнации обнаруживает неполадку, завершает работу драйвера, если она
не была завершена, и запускает его новую копию, обеспечивая высокую степень
отказоустойчивости системы. Аналогичная возможность в большинстве опера­
ционных систем не поддерживается. В сетевой системе на уровне 3 имеется не­
обязательный сетевой сервер (network server). Серверы не могут осуществлять
ввод-вывод напрямую - вместо этого они обращаются с запросами ввода-выво­
да к драйверам. Серверы также способны взаимодействовать с ядром при по­
средничестве системного задания.
Как было отмечено в начале главы 1 , операционные системы решают две основ­
ные задачи: управляют ресурсами и реализуют при помощи системных вызовов
интерфейс расширенной машины. В MINIX 3 распределением ресурсов в основ­
ном занимаются драйверы уровня 2, а ядро помогает им в доступе к портам вво­
да-вывода и работе с прерываниями. Интерпретация системных вызовов осуще­
ствляется менеджером процессов и серверами файловой системы на уровне 3.
Файловая система была тщательно разработана в виде сервера и ее легко с не­
большими изменениями переместить на удаленный компьютер.
Для того чтобы включить в систему дополнительные серверы, ее не требуется
перекомпилировать. Серверы могут быть установлены в любое время - как при
запуске MINIX 3, так и позднее. Хотя драйверы устройств, как правило, запус­
каются при старте системы, их можно запустить и позже. Драйверы устройств
и серверы компилируются и хранятся на диске одинаково - как обычные испол­
няемые файлы. После запуска им назначаются необходимые привилегии. Поль­
зовательская программа, называемая службой, предоставляет интерфейс к серверу
реинкарнации, ответственному за это действие. Хотя драйверы и серверы явля­
ются независимыми процессами, они отличаются от пользовательских процес­
сов тем, что в ходе работы системы они, как правило, никогда не завершаются.
Мы будем зачастую объединять драйверы и серверы уровней 2 и 3 под общим
названием системных процессов. Безусловно, системные процессы являются частью
операционной системы. Они не принадлежат никакому пользователю, и многие,
если не все, из них стартуют раньше, чем первый пользователь входит в систему.
Еще одно отличие между системными и пользовательскими процессами состоит
в том, что системные процессы имеют более высокий приоритет выполнения.
Обычно приоритет драйверов выше, чем процессов, однако такой режим работы
2 . 5 . Процессы в M I N IX 3 1 41

автоматически не поддерживается. Приоритет выполнения в MINIX 3 назнача­


ется каждому процессу в отдельности. Это позволяет задать драйверу, обслужи­
вающему медленное устройство, более низкий приоритет, чем серверу, реакция
которого должна быть быстрой.
Наконец, уровень 4 содержит все пользовательские процессы: оболочки, редак­
торы, компиляторы и творения самого пользователя. Пока пользователи входят
в систему, работают в ней и выходят из нее, многочисленные пользовательские
процессы запускаются и завершаются. Как правило, в системе выполняется так­
же несколько процессов, запускаемых сразу после ее загрузки и работающих все
время функционирования системы. Примером такого процесса является процесс
i n i t ; мы рассмотрим его в следующем разделе. Кроме того, вероятно присутст­
вие нескольких демонов. Демон представляет собой фоновый процесс, периоди­
чески запускающийся или работающий в ожидании какого-либо события (на­
пример, прихода пакета по сети). В определенном смысле демон - это сервер,
запускаемый независимо и функционирующий как пользовательский процесс.
Однако в отличие от «настоящих� серверов, устанавливаемых при запуске, де­
мон допускает назначение себе более высокого приоритета, чем у обыкновенных
пользовательских процессов.
Следует уделить особое внимание терминам «задание� и «драйвер устройст­
ва� . В предыдущих версиях MINIX все драйверы устройств были объединены
с ядром, что давало им возможность доступа к структурам данных ядра и друг
друга. Кроме того, драйверы могли впрямую использовать порты ввода-вывода.
Они назывались заданиями, что позволяло не путать их с полностью независи­
мыми пользовательскими процессами. В операционной системе MINIX 3 драй­
веры устройств реализованы исключительно в пользовательском пространстве.
Единственным исключением является таймерное задание, однако оно не является
типичным драйвером устройства, так как пользовательские процессы не имеют
к нему доступа через файл устройства. В данной книге мы старались применять
термин «задание� только к таймерному и системному заданиям, которые встрое­
ны в ядро. В остальных случаях мы по возможности тщательно вместо термина
«задание� использовали термин «драйвер устройства� . Тем не менее имена
функций, переменных, а также комментарии в программном коде, возможно, не
были обновлены с той же скрупулезностью. Это означает следующее: встретив
в исходном коде MINIX 3 слово «task� (задание), воспринимайте его как «device
driver� (драйвер устройства).

2 . 5 . 2 . Уп равление п роцессами в M I N IX 3
В MINIX 3 процессы соответствуют описанной несколько ранее обобщенной мо­
дели процесса. Процессы могут запускать процессы-потомки, те, в свою очередь,
могут делать то же самое, образуя, таким образом, дерево процессов. По сути, все
пользовательские процессы в системе - листы одного дерева, в корне которого
находится процесс ini t (см. рис. 2. 14). Разумеется, особый случай представля­
ют серверы и драйверы, поскольку часть из них должна быть запущена раньше
любого пользовательского процесса, включая i n i t .
1 42 Глава 2. Процессы

Запуск M I N IX 3
Каким же образом запускается операционная система? Этому вкратце посвяще­
ны несколько следующих страниц. О загрузке некоторых других операционных
систем вы можете узнать в [4 1 ] .
В большинстве компьютеров, оснащенных дисковыми устройствами, имеется
иерархия загрузочных дисков. Как правило, если в дисководе обнаруживается
дискета, она используется в качестве загрузочного диска. Если дискеты нет, но
в устройство CD-ROM вставлен компакт-диск, система пытается загрузиться
с него. Если же ни дискета, ни компакт-диск не вставлены, в качестве загрузоч­
ного выбирается первый жесткий диск. Порядок иерархии можно задать в BIOS
сразу после подачи питания на компьютер. Иногда поддерживается возможность
загрузки с других устройств, в основном сменных.
Аппаратное обеспечение считывает первый сектор первой дорожки загрузочного
диска в память и исполняет записанный на нем код. В случае дискеты этот сек­
тор содержит программу начальной загрузки. Она очень мала - не более одного
сектора ( 5 1 2 байт). Программа начальной загрузки M INIX 3 загружает дру­
гую, б ольшую программу boo t , а та, в свою очередь, - операционную систему.
В случае загрузки с жесткого диска требуется дополнительный промежуточный
этап. Жесткие диски разбиваются на разделы, и первый сектор жесткого диска
содержит небольшую программу и таблиИJJ разделов, вместе они называются глав­
ной загрузочной записъю ( Master Boot Record, MBR). Программная часть считы­
вает таблицу разделов и выбирает активный раздел. В первом секторе активного
раздела расположена программа начальной загрузки, которая загружается, после
чего действует так же, как при загрузке с дискеты, запуская программу boo t .
Устройства CD-ROM появились позже, чем дискеты и жесткие диски. При под­
держке загрузки с компакт-диска существует возможность копирования в память
более чем одного сектора. Компьютер, поддерживающий загрузку с CD-RO M,
может единовременно разместить в оперативной памяти блок данных большого
объема. Как правило, в этом случае с компакт-диска загружается точная копия
загрузочной дискеты, которая используется в качестве виртуального диска. Да­
лее управление передается виртуальному диску и загрузка продолжается так,
как будто в качестве физического загрузочного устройства используется диске­
та. Устаревшие компьютеры, оснащенные устройством CD- RO М, но не поддер­
живающие загрузку с компакт-диска, позволяют скопировать на дискету загру­
зочный образ, посредством которого можно запустить систему (разумеется, при
вставленном в устройство чтения компакт-диске).
В любом случае после запуска программа boot ищет на диске различные компо­
ненты системы и размещает их по нужным адресам - создает загрузочный образ.
Ключевыми составляющими загрузочного образа являются ядро (включающее
таймерное и системное задания), менеджер процессов и файловая система. В за­
грузочный образ должен входить как минимум один драйвер диска. Кроме того,
в загрузочном образе имеется ряд других программ - сервер реинкарнации, вир­
туальный диск, консоль, драйверы журналов и процесс ini t .
2 . 5 . Процессы в M I N IX 3 1 43

Следует особо подчеркнуть, что все части загрузочного образа являются отдель­
ными программами. После загрузки необходимых компонентов - ядра, ме­
неджера процессов и файловой системы - можно отдельно загрузить множество
других частей. Исключение составляет сервер реинкарнации, который должен
являться частью загрузочного образа. После инициализации он назначает обыч­
ным загруженным процессам приоритеты, делающие их системными. Сервер ре­
инкарнации также перезапускает драйверы в случае аварии (именно этим объ­
ясняется его название). Как было отмечено ранее, загрузочный образ должен
содержать как минимум один дисковый драйвер. Если корневая файловая систе­
ма подлежит копированию на виртуальный диск, также потребуется драйвер па­
мяти (в противном случае его можно загрузить позднее). Драйверы t ty и l og
являются на загрузочном образе необязательными. Они загружаются как можно
раньше потому, что на начальной стадии запуска системы желательно иметь воз­
можность отображать сообщения на консоли и записывать информацию в жур­
нал. Разумеется, процесс ini t тоже можно загрузить позднее, однако он управ­
ляет начальным конфигурированием системы, и включение его в загрузочный
образ упрощает работу.
Запуск системы - не простая операция. Программа boot вынуждена брать на
себя действия, выполняемые дисковым драйвером и файловой системой до того,
как эти компоненты будут готовы взять управление на себя. В следующем разделе
мы подробнее рассмотрим вопросы запуска MINIX 3, а сейчас достаточно сказать,
что как только процесс загрузки завершается, ядро начинает работу.
В процессе инициализации ядро запускает сначала таймерное и системное за­
дания, затем - менеджер процессов и файловую систему. Менеджер процессов
и файловая система совместными усилиями запускают остальные серверы и драй­
веры, входящие в загрузочный образ. После запуска и инициализации все эти
компоненты блокируются и ожидают действий. Планировщик MINIX 3 назнача­
ет процессам приоритеты. Первый пользовательский процесс, ini t , запускается
лишь после того, как все задания, драйверы и серверы, загруженные из образа,
оказываются в состоянии блокировки. Системные компоненты, загружаемые
из загрузочного образа или в ходе инициализации, перечислены в табл. 2.3.

Некоторые важные системные компоненты MINIX 3 (помимо них могут


Табл и ца 2 . 3 .
присутствовать и другие, например драйвер Etherпet или сетевой сервер)
Компонент Описание Источник загрузки
kernel Ядро плюс таймерное и системное задания Загрузочный образ
pm Менеджер процессов Загрузочный образ
fs Файловая система Загрузочный образ
rs (Пере)запуск серверов и драйверов Загрузочный образ
memory Драйвер виртуального диска Загрузочный образ
log Буферизация вывода в журнал Загрузочный образ
tty Драйвер консоли и клавиатуры Загрузочный образ
driver Драйвер (Ьios или дискеты) Загрузочный образ
продолжение.Р
1 44 Глава 2. Процессы

Табл ица 2.3 ( прод олжение )


Компонент Описание Источник загрузки
iпit Предок всех пользовательских процессов Загрузочный образ
floppy Драйвер дискеты (если загрузка идет с жесткого диска) /etc/rc
is Информационный сервер (для отладочных данных) /etc/rc
cmos Чтение СМОS-часов для установки времени /etc/rc
raпdom Генератор случайных чисел /etc/rc
priпter Драйвер принтера /etc/rc

И нициализация дерева процессов


Процесс ini t является первым пользовательским процессом и одновременно по­
следним процессом, загружаемым как часть загрузочного образа. Глядя на рис. 1.5,
можно подумать, что рост дерева процессов начинается с процесса init, однако
это не совсем так. Такое утверждение было бы верным в традиционной операци­
онной системе, однако MINIX 3 работает несколько иначе. Во-первых, несколько
системных процессов запускаются раньше, чем ini t . Задания CLOCK (таймер­
ное задание) и SYS TEM (системное задание) - уникальные процессы, невидимые
за пределами ядра. У них нет идентификаторов, и они не входят в дерево процес­
сов. Первым процессом, выполняемым в пользовательском пространстве, явля­
ется менеджер процессов. Он имеет нулевой идентификатор процесса и не явля­
ется ни предком, ни потомком по отношению к каким-либо другим процессам.
Предком всех прочих процессов, запущенных из загрузочного образа (например,
драйверов и серверов), является сервер реинкарнации. Смысл такой структуры
заключается в том, что сервер реинкарнации должен получать уведомления при
необходимости перезапуска этих компонентов.
Как мы увидим далее, даже после запуска процесса ini t различия между по­
строением дерева процессов в MINIX 3 и традиционных операционных системах
остаются. В UNIХ-подобных системах процессу ini t присваивается идентифи­
катор 1, и хотя в MINIX 3 процесс i n i t запускается не первым, для него тоже
зарезервирован традиционный идентификатор 1. Как и все пользовательские про­
цессы загрузочного образа (за исключением менеджера процессов), ini t являет­
ся одним из потомков сервера реинкарнации. Первое, что делает процесс ini t , -
запускает сценарий оболочки / et c / rc (это же характерно и для стандартных
UNIХ-подобных систем). Этот сценарий, в свою очередь, запускает дополни­
тельные драйверы и серверы, не являющиеся частью загрузочного образа. Любая
программа, запущенная сценарием rc, является дочерней по отношению к ini t .
Одной из первых запускается утилита s ervi c e . Она тоже является потомком
ini t , однако здесь снова начинаются отступления от традиционной схемы.
Утилита s e rvi c e предоставляет пользовательский интерфейс для сервера ре­
инкарнации. Сервер реинкарнации запускает обычную программу и превращает
ее в системный процесс. В частности, он запускает такие программы, как f l oppy
(если она не использовалась ранее для загрузки системы) , cmo s (необходима
для чтения часов реального времени) и i s (информационный сервер, управляю­
щий отладочной информацией, генерируемой по нажатию функциональных кла-
2 . 5 . Процессы в M I N IX 3 1 45

виш F1 , F2 и др. на клавиатуре консоли) . Одна из задач сервера реинкарнации -


сделать все системные процессы, кроме менеджера процессов, своими потомками.
После запуска драйвера устройства cmo s сценарий rc может инициализировать
часы реального времени. К этому моменту все необходимые файлы должны при­
сутствовать на корневом устройстве. Серверы и драйверы находятся в каталоге
/ sЬin, а остальные команды, нужные для запуска, - в каталоге / Ь i n. По завер­
шении начальных шагов запуска выполняется монтирование других файловых
систем, таких как / u s r. Важной функцией сценария rc является проверка
наличия повреждений файловых систем, которые могли появиться в результате
последней аварии системы. Тест прост - если система завершает свою работу
корректно, посредством команды shut down, в журнал истории загрузки / u s r /
adm / wtmp добавляется запись. Команда shut down -с проверяет, является ли
последняя запись wtmp записью завершения работы. Если нет, предполагается,
что система закончила работу аварийно, и для проверки всех файловых систем
запускается утилита f s ck. Последняя функция сценария / et c / rc - запуск де­
монов. Эта функция может выполняться при помощи вспомогательных сценари­
ев. Если вы изучите данные, выводимые командой ps axl , предназначенной для
вывода идентификаторов PID ( Process I Dentifieг - идентификатор процесса)
и PPID ( Parent Process IDentifier - идентификатор родительского процесса), вы
увидите, что демоны updat e и u sy s l ogd обычно находятся среди первых по­
стоянных процессов, являющихся потомками ini t .
Наконец, ini t считывает файл / e t c / t tyt ab, в котором перечислены все воз­
можные терминальные устройства. Если какое-либо устройство может быть ис­
пользовано как терминал для входа в систему (в стандартной поставке это толь­
ко консоль и не более трех виртуальных консолей, хотя вы можете добавлять
последовательные линии и сетевые псевдотерминалы), то в файле / e t c / t ty t ab
у него присутствует запись в поле get ty. Для каждого такого терминала ini t
запускает дочерний процесс. Обычно каждый из этих дочерних процессов запус­
кает программу / u s r / Ьi n / get ty, которая выводит приглашение и ждет, ко­
гда пользователь введет свое имя. Если же для определенного терминала требу­
ются какие-то специальные действия ( например, телефонное подключение) , то
в / e t c / t ty t ab можно указать команду (например, / u s r / Ь i n / s t t y ) , кото­
рая будет выполнена перед запуском g е t t у .
Когда пользователь вводит свое имя для входа в систему, запускается процесс
/ Ь i n / l og i n, которому в качестве аргумента передается имя пользователя. Про­
цесс l og i n определяет наличие пароля, при необходимости запрашивает и све­
ряет его. После успешного входа процесс l og i n запускает пользовательскую
оболочку. По умолчанию используется оболочка / Ь i n / sh, однако в файле / et c /
pas swd можно указать другую оболочку. Оболочка воспринимает и интерпрети­
рует вводимые пользователем команды, запуская для их исполнения новые про­
цессы. Таким образом, оболочки являются прямыми потомками процесса ini t ,
пользовательские процессы являются его �внуками�, а все процессы вместе об­
разуют единое дерево. Фактически, в дерево входят все процессы (пользователь­
ские и системные), кроме внутренних заданий ядра и менеджера процессов. Тем
1 46 Глава 2. П роцессы

не менее, в отличие от традиционных операционных систем семейства UNIX,


ini t не является корнем дерева процессов, а структура дерева не позволяет
определить порядок запуска процессов.
Для управления процессами в MINIX 3 предназначены два основных системных
вызова: fork и ехе с . Сделать вызов f ork это единственный способ создать
-

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


му. При запуске программы ей выделяется объем памяти, указанный в ее заго­
ловке. Этот объем памяти удерживается за программой все время работы, хотя
распределение памяти между сегментом данных, сегментом стека и неиспользо­
ванным пространством может меняться динамически.
Вся информация о процессах хранится в таблице процессов, поля которой поде­
лены между ядром, менеджером памяти и файловой системой. Когда появляется
новый процесс (благодаря вызову f ork) или же когда процесс завершается (по
системному вызову exi t или по сигналу), то прежде всего свои поля в таблице
процессов обновляет менеджер процессов. Затем он посылает ядру и файловой
системе сообщения с указаниями поступить таким же образом.

2 . 5 . З . Взаимодействие между
процессами в M I N IX
Для отправки и приема сообщений предусмотрено три примитива. Им соответ­
ствуют следующие библиотечные процедуры языка С:
+ Отправление сообщения процессу dest:
send ( de s t , &me s s age ) ;

+ Получение сообщения от процесса s ource (или от любого процесса):


r e c e ive ( s our c e , &me s s age ) ;

+ Отправление сообщения и ожидание ответа от процесса:


s end_re c ( s r c_ds t , &me s s age ) ;

У всех трех вызовов во втором аргументе передается локальный адрес данных


сообщения. Механизм передачи сообщений в ядре копирует эти данные из буфе­
ра отправителя в буфер приемника. При использовании процедуры s end_r ec
ответ записывается поверх исходного сообщения. В принципе, механизм переда­
чи сообщений в ядре можно было бы переделать так, чтобы обмениваться ими
через сеть и тем самым реализовать распределенную систему. Однако на практи­
ке все осложняется тем, что сообщения обычно содержат указатели на большие
структуры данных и в распределенной системе требуется как-то реализовать ме­
ханизм копирования этих данных через сеть.
Процесс, задание или сервер может вести обмен сообщениями только с определен­
ным кругом других процессов. Более детально этот вопрос рассматривается нами
позднее. Как правило, передача сообщений ведется сверху вниз (см. рис. 2. 14).
Обмен сообщениями допустим между процессами, расположенными на одном
уровне либо на смежных уровнях. Пользовательские процессы не могут пасы-
2 . 5 . Процессы в M I N IX 3 1 47

лать сообщения друг другу. Вместо этого пользовательские процессы уровня 4


передают сообщения серверам уровня 3 , а те, в свою очередь, передают сообще­
ния драйверам уровня 2.
Когда процесс отправляет сообщение другому процессу, который в этот момент
его не ждет, отправитель блокируется до тех пор, пока адресат не выполнит вызов
r e c e i ve. Другими словами, в MINIX 3 во избежание проблем с буферизацией
отправленных, но еще не принятых сообщений применяется метод рандеву. Дос­
тоинства такого подхода - простота и отсутствие необходимости управления бу­
ферами (включая ситуацию переполнения буферов). Кроме того, все сообщения
в момент сборки имеют фиксированную длину, а следовательно, структурно ис­
ключается самый распространенный источник ошибок - переполнение буферов.
Причина ограничений на обмен сообщениями следующая. Предположим, что про­
цессу А разрешается сделать вызов s end или s endr ec непосредственно к про­
цессу В, а затем процессу В разрешается сделать вызов r e c e i ve с процессом А,
указанным в качестве отправителя, но запрещается выполнять вызов s end к про­
цессу А. Предположим, А пытается отправить В сообщение и блокируется; если В
тоже попытается отправить сообщение А и заблокируется, возникнет взаимная
блокировка. Ресурсом, необходимым каждому из процессов для завершения
операции, является не физический компонент, подобный порту ввода-вывода,
а вызов r e c e i ve адресатом. Более подробно взаимная блокировка рассматрива­
ется в главе 3.
Иногда необходимо действие, не вызывающее блокировку сообщений. Сущест­
вует еще один важный примитив передачи сообщений, вызываемый библиотеч­
ной процедурой языка С:
no t i fy ( de s t ) ;

Эта процедура используется в случае, когда одному процессу необходимо уведо­


мить другой о наступлении важного события. Процедура no t i fy не является
блокирующей; то есть отправитель продолжает выполнение независимо от того,
ждет адресат или нет. Подобная схема уведомления позволяет исключить воз­
можность взаимной блокировки процессов.
Передача уведомлений осуществляется через механизм сообщений, однако пере­
даваемая информация весьма ограниченна. В общем случае сообщение содержит
только данные, идентифицирующие отправителя, и временну ю метку, устанав­
ливаемую ядром. Иногда этого оказывается достаточно. К примеру, клавиатура
вызывает процедуру no t i fy при нажатии одной из функциональных клавиш
(F1 -F1 2) либо комбинации функциональной клавиши с клавишей Shift. В опера­
ционной системе MINIX 3 функциональные клавиши используются для сброса
отладочной информации. Драйвер Ethernet - пример процесса, генерирующего
единственный вид отладочного дампа и не нуждающегося в ином взаимодейст­
вии с драйвером консоли. В такой ситуации уведомление, посылаемое драйве­
ром консоли драйверу Ethernet при нажатии клавиши, сбрасывающей отладоч­
ные данные, воспринимается однозначно. В других случаях само уведомление не
несет большого смысла, однако получивший его процесс может послать отправи­
телю сообщение, запрашивающее дополнительную информацию.
1 48 Глава 2 . Процессы

Уведомления упрощены не случайно. Поскольку вызов not i fy не приводит к бло­


кировке, его можно посылать раньше, чем получатель завершит вызов r e c e i ve.
Однако простота сообщения означает, что неполученное уведомление можно
с легкостью сохранить до следующего вызова r e c e i ve адресатом. В такой ситуа­
ции буквально каждый бит является существенным. Уведомления предназначе­
ны для использования немногочисленными системными процессами. У каждого
системного процесса имеется битовая карта активных уведомлений, в которой
каждый бит соответствует определенному системному процессу. Поэтому, если
процесс А желает послать уведомление процессу В в то время, когда последний
не блокирован вызовом r e c e i ve, механизм доставки сообщений устанавливает
в битовой карте активных уведомлений процесса В бит, соответствующий про­
цессу А. При вызове re c e i ve процессом В первым шагом является проверка би­
товой карты. Проверка позволяет обнаружить наличие активных уведомлений
от нескольких источников сразу. Единственного бита достаточно, чтобы восста­
новить информационное содержимое уведомления - идентификационные дан­
ные отправителя и временну ю метку, устанавливаемую ядром при доставке. Как
правило, при помощи временн:Ь1х меток определяют истечение времени, поэтому
если метка указывает на более позднее время, чем время первой попытки от­
правителя послать уведомление, это не приводит к негативным последствиям.
В механизме уведомлений имеется еще одно усовершенствование. В некоторых
случаях используется дополнительное поле сообщений-уведомлений. Если уведом­
ление информирует получателя о прерывании, в сообщение включается битовая
карта всех возможных источников прерываний, а если уведомление сгенерирова­
но системным заданием, его частью является битовая карта всех активных сигна­
лов получателя. Возникает естественный вопрос: каким образом хранится допол­
нительная информация, если уведомление посылается процессу, не ожидающему
сообщений? Дело в том, что упомянутые битовые карты находятся в структурах
данных ядра и их не нужно копировать. Если получение уведомления отклады­
вается, уведомление превращается в один бит. Когда получатель делает вызов
recei ve, уведомление восстанавливается. При этом определяется его источник,
а по источнику можно обнаружить местонахождение дополнительной инфор­
мации сообщения. Зная источник уведомления, адресат определяет не только
наличие в нем дополнительной информации, но и способ ее интерпретации.
Взаимодействие между процессами поддерживается еще несколькими примити­
вами, о которых мы поговорим в следующем разделе, но они не столь важны, как
s end, s endrec и no t i fy.

2 . 5 . 4 . Планирование п роцессов в M I N IX 3
Мультипрограммную операционную систему движет система прерываний. Сде­
лав запрос на ввод данных, процесс блокируется, позволяя выполняться другим
процессам. Когда запрошенные данные становятся доступными, процесс преры­
вается диском, клавиатурой или другим оборудованием. Кроме того, прерывания
генерируются и таймером, это гарантирует, что процесс, не требующий ввода, не
2.5. Процессы в M I NIX 3 1 49

будет работать слишком долго. Задача нижнего уровня MINIX 3 - скрыть пре­
рывания, преобразовав их в сообщения. С точки зрения процессов, завершение
операции устройством ввода-вывода - это сообщение, передаваемое устройст­
вом определенному процессу, пробуждающее его и приводящее его в состояние
готовности.
Прерывания также генерируются программно; в этом случае их иногда называют
исключения.ми. Описанные ранее операции s end и r e c e i ve транслируются сис­
темной библиотекой в команды проzраммноzо прерывания, эффект от которого
ничем не отличается от аппаратного прерывания. Процесс, исполняющий про­
граммное прерывание, немедленно блокируется, а ядро активизируется для об­
работки прерывания. Пользовательские программы не обращаются к операциям
s end и rec e i ve впрямую, однако каждый раз при использовании одного из
системных вызовов, перечисленных в табл. 1 . 1 (непосредственно или через биб­
лиотечную процедуру), происходит внутренний вызов s endrec и генерируется
программное прерывание.
Каждый раз, когда происходит прерывание процесса, независимо от источни­
ка прерывания (таймер или периферийное устройство), у системы появляется
возможность принять решение, какому процессу больше требуется процессор.
Конечно, это обязательно нужно делать и при завершении процесса, но в систе­
мах, подобных MINIX 3, переключения должны происходить чаще, чем заверше­
ния процессов.
Планировщик MINIX использует многоуровневую систему очередей. Определе­
ны шестнадцать очередей, хотя при повторной компиляции число очередей мож­
но без труда увеличить или уменьшить. Самый низкий приоритет назначается
только процессу I DLE, который выполняется исключительно во время простоя.
По умолчанию пользовательские процессы запускаются с приоритетом на не­
сколько уровней выше наименьшего.
Как правило, серверы в очереди имеют более высокий приоритет, чем пользова­
тельские процессы, драйверы - более высокий приоритет, чем серверы, а тай­
мерное и системное задания - самый высокий приоритет. Обычно в отдельно
взятый момент времени используются не все 1 6 возможных очередей, а лишь не­
сколько. Процесс может быть перемещен из одной очереди в другую системой
или (с оговорками) пользователем при помощи команды n i c e . •дополнитель­
ные� уровни открывают возможности для экспериментов и по мере добавления
в систему новых драйверов позволяют изменить предлагаемые по умолчанию
параметры для оптимизации производительности. Например, если вы захотите
добавить сервер, передающий в сеть потоковые аудио- и видеоданные, вы сможе­
те назначить ему более высокий стартовый приоритет, чем у других серверов.
Вы также можете снизить приоритет текущего сервера или драйвера, чтобы обес­
печить более высокую производительность нового сервера.
Помимо очередей с разными приоритетами, существует еще один механизм,
обеспечивающий одним процессам преимущество над другими. Квант, то есть
временной интервал, выделяемый процессу для работы, имеет разную длитель­
ность для разных процессов. Квант пользовательских процессов относительно
1 50 Глава 2. П роцессы

короткий; драйверы и серверы же, как правило, выполняются до тех пор, пока не
входят в состояние блокировки. Квант применяется к драйверам и серверам для
того, чтобы избежать сбоев в работе системы (зависаний) , однако он значи­
тельно длиннее, чем у пользовательских процессов. Если процесс исчерпал свой
квант, он считается готовым к работе и помещается в конец очереди. Если же
выясняется, что исчерпавший текущий квант процесс выполнялся и в предыду­
щем кванте, это трактуется как признак <�:зацикливания», препятствующего ра­
боте процессов с более низким приоритетом. В этом случае процесс помещается
в конец более низкоприоритетной очереди. Если ситуация повторяется, приори­
тет процесса снова снижается тем же способом. В конечном счете система рано
или поздно выберет другой процесс для запуска.
Процесс, приоритет которого был снижен, может снова его повысить. Если про­
цесс расходует свои кванты времени, но не препятствует выполнению других про­
цессов, он помещается в очередь с более высоким приоритетом и может подобным
образом достичь наивысшего доступного ему уровня. Очевидно, что такой про­
цесс нуждается в процессорном времени, но не захватывает его в ущерб другим.
В рамках очереди планирование процессов осуществляется при помощи цикли­
ческого алгоритма с небольшим изменением. Если процесс не исчерпал свой
квант, но вышел из состояния готовности, это воспринимается как блокирование
по вводу-выводу. Когда процесс вновь готов к выполнению, он помещается в на­
чало очереди, но ему выделяется не целый квант, а лишь неиспользованная часть
кванта, в ходе которого он был заблокирован. Такой подход обеспечивает опера­
тивность реакции пользовательских процессов на ввод-вывод. Процесс, исчер­
павший выделенный ему квант, помещается в конец очереди согласно классиче­
скому циклическому алгоритму.
Поскольку задания, как правило, имеют наивысший приоритет, за ними следуют
драйверы, затем серверы и лишь затем пользовательские процессы, последние
выполняются только в случае, если все системные процессы бездействуют. Сис­
темные же процессы ради обслуживания пользовательских процессов отложены
быть не могут.
Выбирая процесс для запуска, планировщик обращается к очереди с наивысшим
приоритетом. Если один или несколько ее процессов готовы, из их числа выби­
рается первый. В противном случае аналогично проверяется очередь с более
низким приоритетом и т. д. Поскольку драйверы отвечают на запросы серверов,
а серверы - на запросы пользовательских процессов, в какой-то момент все вы­
сокоприоритетные процессы заканчивают свою работу и переходят в состояние
ожидания, а пользовательские процессы получают возможность выполняться и
генерируют новые запросы. Если же система обнаружит, что ни один процесс не
готов, она выберет процесс I DLE, переводящий процессор в состояние низкого
энергопотребления до появления прерывания.
По каждому сигналу таймера проверяется, не истек ли квант текущего процесса.
Если квант истек, планировщик перемещает процесс в конец очереди (для этого
не требуется никаких действий, если очередь состоит из единственного процесса).
Затем выбирается следующий процесс. Предыдущий процесс может получить
2.6. Реализ ац ия п роцессов в M I N IX 3 1 51

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

2 . 6 . Р еал иза ц ия п ро ц ессов в M I N IX 3


Мы уже почти добрались для изучения реальноm кода, но предварительно следует
сказать несколько слов о терминах, которые вы встретите в ходе изложения. Тер­
мины «Процедура• и «функция• означают то же самое, что «Подпрограмма•. Имена
переменных, процедур и файлов записываются моноширинным шрифтом (на­
пример: rw_ f l ag) и состоят только из строчных букв. Исключениями являются
три встроенных в ядро задания - они пишутся прописными буквами: CLOCK,
SYS TEM и I DLE. Системные вызовы пишутся строчными буквами, например, read.

Книга не попадает в печать в первый же день после того, как авторы ставят по­
следнюю точку, и книга, и программное обеспечение постоянно развиваются.
Поэтому между приведенными в книге листингами и версиями кода на компакт­
диске могут быть небольшие различия. Но обычно различия незначительны, не
более одной или двух строк. Приведенный исходный код упрощен: из него удале­
ны не обсуждаемые в книге фрагменты. Полную версию вы найдете на компакт­
диске. Кроме того, текущая редакция, включая новые средства, дополнительное
программное обеспечение и документацию, представлена на веб-сайте операци­
онной системы MINIX 3 (http://www . minixЗ. org).

2 . 6 . 1 . Структура исходного кода M I N IX 3


Как уже отмечалось, реализация операционной системы MINIX 3 предназначена
для персонального компьютера, совместимого с IBM РС, оснащенного с расши­
ренным набором микросхем и 32-разрядными словами (например, 80386, 80486,
Peнtium, Peнtium Рго, 11, 111, 4, М и D). Мы объединим все перечисленные про­
цессоры под общим названием «32 -разрядные процессоры Iнtel•. Полный путь
к исходному коду С на стандартной платформе Iнtel выглядит так: / u s r / s r c /
(символ / в конце означает, что путь указывает на каталог). Каталог исходных
кодов на других платформах может быть иным. В книге ссылки на файлы с ис­
ходным кодом MINIX 3 будут даваться относительно каталога s rc / . Важным
подкаталогом дерева исходных кодов является каталог src / inc lude / , содер­
жащий главные копии заголовочных С-файлов. Мы будем называть этот каталог
inc l ude / .
1 52 Глава 2. П роцессы

Каждый каталог дерева исходных кодов содержит файл с именем Mak e f i l e,


управляющий выполнением стандартной UNIХ-утилиты rnake. Файл Make f i l e
задает режим компиляции файлов, находящихся в одном каталоге с ним, а также
способен управлять компиляцией в одном или нескольких подкаталогах. Меха­
низм функционирования утилиты rnake отличается сложностью, и его полное
описание выходит за рамки темы этой книги. Тем не менее, в общем, функциони­
рование сводится к эффективной компиляции программ, исходный код которых
находится в нескольких файлах. Утилита rnake гарантирует, что все необходимые
файлы участвуют в компиляции. Она проверяет актуальность скомпилированных
ранее модулей и при необходимости перекомпилирует те, чьи исходные файлы
были изменены с момента последней компиляции. Модули, оставшиеся неизмен­
ными, не перекомпилируются, что дает экономию времени. Наконец, утилита rnake
преобразует несколько независимо скомпилированных модулей в исполняемую
программу, а также может принимать участие в установке готовой программы.
При желании местоположение дерева s rc / можно изменить, поскольку в фай­
лах Make f i l e всех каталогов пути к файлам исходного С-кода являются отно­
сительными. К примеру, вы можете поместить исходные коды в корневую фай­
ловую систему, / src / - это ускорит компиляцию, если корневым устройством
является виртуальный диск. Если вы разрабатываете собственную версию опе­
рационной системы, можете сделать копию каталога s r c / с другим именем.
Особый случай представляет путь к заголовочным С-файлам. В процессе компиля­
ции подразумевается, что исходные файлы находятся в каталоге / u s r / inc lude
(либо в соответствующем каталоге платформы, отличной от Intel). Тем не менее
файл Make f i l e из каталога s r c / t o o l s / , используемый для перекомпиляции
системы, ориентирован на то, что главная копия заголовочных файлов размеще­
на в каталоге / u s r / s r c / inc lude (в системе Intel). Тем не менее перед пере­
компиляцией системы дерево / usr / inc l ude / стирается и туда копируется содер­
жимое / u sr / src / inc lude. Это делается для того, чтобы получить возможность
хранить все файлы, необходимые для разработки MINIX 3, в одном месте. Кроме
того, так проще поддерживать несколько копий дерева исходных и заголовочных
кодов во время экспериментов с различными конфигурациями операционной
системы. Тем не менее, если вы захотите отредактировать заголовочный файл
при таком эксперименте, вы должны использовать его копию в каталоге s r c /
inc lude, а не в каталоге / u s r / inc lude / .
Для новичков в языке С самое время познакомиться с особенностями заключе­
ния имен файлов в кавычки при использовании команды # i nc lude. Каждый
компилятор С имеет каталог заголовочных файлов, предлагаемый по умолча­
нию, в котором он ищет файлы, указанные в команде # inc lude. Зачастую этим
каталогом является / u s r / inc l ude / . Когда имя включаемого файла заключено
между символами операций сравнения ( < . . . > ), компилятор ищет файл в ката­
логе заголовочных файлов, предлагаемом по умолчанию, либо в указанном под­
каталоге. К примеру:
# inc lude < имя файла>

Эта команда включает файл из каталога / u s r / inc lude / .


2 . 6 . Реал изаци я пр оцессов в M I N IX З 1 53

Многие программы также требуют в локальных заголовочных файлах определе­


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

чала выполняется в каталоге, где находится файл исходного кода (или в указан­
ном подкаталоге), и лишь затем при его отсутствии - в каталоге, предлагаемом
по умолчанию. Например, следующая команда считывает локальный файл:
Jl i nc lude " имя файла "

Каталог i n c l ude / содержит набор заголовочных файлов в стандарте POSIX,


а также три подкаталога:
+ sy s / - дополнительные заголовочные РОSIХ-файлы;
+ rn i n i x / - заголовочные файлы операционной системы MINIX 3;
+ i brn/ - заголовочные файлы с определениями, специфичными для IBM РС.
Для поддержки МINIХ-расширений и программ, работающих в среде MINIX 3,
в каталог inc lude / включены и другие подкаталоги и файлы; это касается как
компакт-диска, так и веб-сайта MINIX 3. Например, для поддержки сетевых рас­
ширений предусмотрены каталоги inc lude / arp a / , i nc lude / net и подката­
лог inc lude / ne t / gen / .
Помимо подкаталога src / inc l ude / , каталог s r c / содержит три других важ­
ных подкаталога с исходным кодом операционной системы:
+ kerne l / - уровень 1 (планирование, сообщения, часы и системные задачи);
+ dr iver s / - уровень 2 (драйверы диска, консоли, принтера и других уст-
ройств) ;
+ s erver s / - уровень 3 (менеджер процессов, файловая система, другие сер-
веры).
Кроме того, есть еще три каталога, содержимое которых в книге не рассматрива­
ется, но которые нужны для получения работоспособной среды:
+ s rc / l iЫ - исходные коды библиотечных процедур (например, ореп, read);
+ s rc / t o o l s / - файлы Make f i l e и сценарии сборки системы MINIX 3;
+ src / boot / - коды загрузки и установки MINIX 3.
В стандартной комплектации MINIX 3 вы найдете еще множество исходных
файлов, не упомянутых в книге. Помимо исходных кодов менеджера процессов
и файловой системы, каталог s r c / s e rv e r s / включает код программы i n i t
и сервера реинкарнации, r s . Оба эти компонента обеспечивают работоспособ­
ность операционной системы. Исходный код сетевого сервера находится в ка­
талоге s rc / s erve r s / i net / . Каталог s r c / driver s / содержит исходный код
драйверов устройств, не рассматриваемых в книге, в частности альтернативные
дисковые драйверы, драйверы звуковой платы и сетевых адаптеров. Поскольку
MINIX 3 - экспериментальная операционная система, предназначенная для мо­
дификации, в нее включен каталог s r c / t e s t / с программами, обеспечиваю­
щими тщательное тестирование скомпилированной системы. Безусловно, любая
1 54 Глава 2 . П роцессы

операционная система существует для поддержки команд (программ), работаю­


щих под ее управлением, поэтому в MINIX 3 включен объемный каталог s r c /
comrnands / с исходными кодами утилит, таких как c a t , е р , dat e, l s , pwd -
всего более 200 команд. В нем же вы найдете нескольких важных приложений
с открытым кодом, разработанных в рамках проектов GNU и BSD.
В «книжной» версии MINIX 3 опущены многие необязательные компоненты
операционной системы. Поверьте, включить все в одну книгу для нас так же не­
возможно, как для вас - изучить ее за один семестр. При компиляции «книж­
ной» версии из файлов Make f i l e бьши удалены ссылки на все ненужные файлы.
В стандартном файле Make f i l e необходимо указывать дополнительные компо­
ненты, даже если они не участвуют в компиляции, однако удаление неиспользуе­
мых файлов и условных операторов упрощает чтение кода.
Для удобства мы упоминаем только имена файлов в случаях, когда путь к ним
очевиден из контекста. Тем не менее имейте в виду, что в различных каталогах
имеются файлы с совпадающими именами, например c o n s t . h. Файл s r c /
kerne l / const . h определяет константы, используемые ядром, файл src /
servers / prn/ c ons t . h
- константы менеджера процессов и т. д.
Файлы, расположенные в одном каталоге, рассматриваются совместно, поэтому
наличие повторяющихся имен в разных каталогах не вызовет путаницы. На со­
провождающем книгу компакт-диске файлы представлены в порядке их обсу­
ждения в книге, что позволяет сделать чтение упорядоченным. Имеет смысл
обзавестись парой закладок, чтобы с легкостью чередовать изучение текста и лис­
тингов.
Приложение Б содержит упорядоченный по алфавиту список всех файлов, пред­
ставленных на компакт-диске. Список разбит на разделы, соответствующие заго­
ловочным файлам, драйверам, ядру, файловой системе и менеджеру процессов.
Индексы в этом приложении, на веб-сайте и компакт-диске ссылаются на пере­
численные объекты по номеру строки в исходном коде.
Код уровня 1 системы находится в каталоге src / kerne l / . Файлы этого катало­
га обеспечивают управление процессами - нижнего уровня структуры опера­
ционной системы MINIX 3, как было показано на рис. 2 . 1 4 . Этот уровень вклю­
чает функции, обслуживающие инициализацию системы, прерывания, передачу
сообщений и планирование процессов. Два тесно связанных между собой моду­
ля компилируются в один двоичный код, однако выполняются как отдельные
процессы. Эти два модуля - системное задание, предоставляющее интерфейс
между службами ядра и процессами более высоких уровней, и таймерное за­
дание, поддерживающее тактовые сигналы для ядра. В главе 3 мы обратимся к фай­
лам некоторых подкаталогов каталога s r c / drivers, предназначенных для под­
держки драйверов устройств - уровень 2 на рис. 2 . 1 4 . Далее, в главе 4, наше
внимание будет сконцентрировано на файлах менеджера процессов, располо­
женных в каталоге s r c / s erver s / prn. Наконец, в главе 5 предметом рассмот­
рения станет файловая система, исходный код которой размещен в каталоге
src / s erver s / f s / .
2 . 6 . Реал изаци я процессов в M I NIX 3 1 55

2 . 6 . 2 . Компиляция и запуск M I N IX 3
Чтобы скомпилировать операционную систему MINIX 3, запустите команду
make в каталоге s r c / t oo l s. Команда имеет ряд параметров, управляющих ре­
жимами установки. Эти параметры можно просмотреть, запустив команду make
без параметров. Простейшим методом установки является выполнение команды
make image.
При использовании команды make image последняя копия заголовочных фай­
лов каталога s r c / inc l ude / копируется в каталог u s r / inc l ude / . Затем ис­
ходные коды каталога s r c / kerne l / и некоторых подкаталогов s rc / s ervers /
и s r c / dr i v er s / компилируются в объектные файлы. Все объектные файлы
s r c / kerne l / компонуются в единственную исполняемую программу, kerne l ,
объектные файлы s r с / s erver s / pm - в программу pm, а объектные файлы
s r c / s e rv e r s / f s / - в программу f s . Дополнительные программы, являю­
щиеся в табл. 2.3 частью загрузочного образа, также подвергаются компиляции
и компоновке в своих каталогах. В частности, программы r s и i n i t создают­
ся в подкаталогах s r c / dr iver s / , а memory, l og и t ty - в подкаталогах src /
dr iver s / . Компонент, упомянутый в табл. 2.3 как «драйверi> , может быть од­
ним из нескольких дисковых драйверов. Мы будем считать, что операционная
система MINIX 3 сконфигурирована под загрузку с жесткого диска с использова­
нием стандартного драйвера at_wini, компилируемого в каталоге src / drivers /
at_wini / . Можно добавить и множество других драйверов, однако большинст­
во из них не обязательно включать в загрузочный образ. Это же касается и сете­
вой поддержки; компиляция базовой системы MINIX 3 не зависит от наличия
или отсутствия сети.
Для того чтобы готовая к работе система MINIX 3 могла загружаться, програм­
ма i ns t a l lboot (ее исходный код находится в каталоге s r c / boot l ) именует
kerne l , pm, f s , i n i t и прочие ее компоненты, дополняет их так, чтобы размер
каждого компонента был кратен размеру сектора диска (для упрощения незави­
симой загрузки программ), и объединяет их в единый файл. Это файл является
загрузочным образом; вы можете скопировать его в каталог / bo o t / или под­
каталог / boot / image / дискеты или раздела жесткого диска. Позже программа
монитора загрузки поместит загрузочный образ в память и передаст управление
операционной системе.
На рис. 2 . 1 5 показана структура памяти после того, как программы загрузочно­
го образа отделены друг от друга и загружены. Ядро располагается в нижней час­
ти адресного пространства, в то время как все остальные компоненты - выше
1 Мбайт. Сначала пользовательским программам выделяется свободное место
над ядром. Когда очередную программу не удается разместить там, ее поме­
щают выше, над программой ini t. Разумеется, детали зависят от конфигурации
системы. Например, на рис. 2 . 1 5 система MINIX 3 содержит кэш блоков, способ­
ный хранить 5 1 2 блоков размером по 4 Кбайт. Это весьма скромная конфигурация;
при достаточном объеме памяти размер кэша следует увеличить. В то же вре­
мя, если размер кэша блоков сократить, всю систему можно разместить в памяти
1 56 Глава 2. П роцессы

ниже отметки 640 К, при этом в оставшуюся память можно загрузить несколько
пользовательских процессов.

доступная Предел памяти


l Память,
пользовательским 1
программам 3549 к
scr/servers/iпet/iпit lпit 3537 к
scr/drivers/at wiпi/at wiпi Драйвер диска
scr/drivers/log_log - Драйвер журнала 3489 к
341 6 к
scr/drivers/memory/memory Драйвер памяти 3403 к
scr/drivers/tty/tty Драйвер консоли 3375 к
scr/servers/rs/rs Сервер реинкарнации
3236 К (Зависит от количества
scr/servers/fs/fs буферов в файловой системе)
Файловая система
1 093 к
scr/server/pm/pm Менеджер процессов
/ Постоянная
1 024 к
память и память
контроллера ввода­
вывода (недоступна
MINIX 3)/
загрузки] '' б4О К
1

• [Монитор
'l Память, доступная �
1 к о
пользовательским
программам
Системное задание 55 К
scr/kemel/kemel тсlймеРiiоезаданив
1- - - - - - - - - - - -
Ядро
i [Используется BIOSJ: 2 К Начало области ядра
[Векторы 1к
прерываний]
о
Рис. 2 . 1 5 . Структура памяти после загрузки MINIX З с диска. Ядро, серверы и драйверы
компилируются и компонуются независимо друг от друга. Названия соответствующих
программ перечислены слева. Размеры указаны приблизительно и не соответствуют
масштабам рисунка
Важно понимать, что MINIX 3 состоит из нескольких полностью независимых
друг от друга программ, взаимодействующих друг с другом посредством сообще­
ний. Процедура p ani c из каталога s r c / s e rvers / f s / не конфликтует с одно­
именной процедурой из каталога src / s erver s / prn / , поскольку при компонов­
ке они помещаются в разные исполняемые файлы. Единственные процедуры,
совместно используемые тремя компонентами операционной системы, находят­
ся в библиотеке s rc / l i Ы . Модульная структура позволяет с легкостью моди­
фицировать, скажем, файловую систему, не затронув менеджер процессов. Более
того, не составляет труда полностью удалить файловую систему и поместить ее
на другой компьютер, играющий роль файлового сервера и взаимодействующий
с пользовательскими машинами, обмениваясь сообщениями по сети.
2 . 6 . Реал изаци я п роцессов в M I NIX 3 1 57

Еще один пример модульности MINIX 3 - абсолютное безразличие менеджера


процессов, файловой системы и ядра к поддержке или отсутствию поддержки
сети. Драйвер Etl1ernet и сервер inet могут быть активированы после загрузки
загрузочного образа. Они будут запущены сценарием / e t c / rc и размещены в об­
ласти памяти, доступной пользовательским программам. Система MINIX 3 с се­
тевой поддержкой может быть задействована как удаленный терминал либо как
FTP- или веб-сервер. Изменение базовых компонентов MINIX 3 потребуется
лишь в одном случае - если вы захотите добавить возможность входа в систему
через сеть. В этом случае необходимо перекомпилировать драйвер консоли, t ty,
предварительно сконфигурировав в нем псевдотерминалы.

2 . 6 . З . Общие заголовоч н ы е файл ы


В каталоге inc lude / и его подкаталогах содержится набор файлов с общими
константами, типами данных и макроопределениями. Большинство этих опреде­
лений обусловлены стандартом POSIX, который указывает, какое определение
в каком файле каталога inc lude / или его подкаталоге inc lude / sy s / находит­
ся. Файлы, которые присутствуют в этих каталогах, называются заzоловочны­
ми, или в'К.llючаемыми, и имеют расширение . h . Подключаются они директивой
# inc lude языка С. Благодаря заголовочным файлам упрощается поддержка
большой системы.
Для компиляции пользовательских программ требуются по большей части заго­
ловочные файлы из каталога inc lude / , а файлы из каталога inc lude / sys /
традиционно нужны для компиляции системных утилит. Но это - не слишком
важное ограничение, и в типичной программе, будь то пользовательская програм­
ма или часть системы, задействуются файлы из обоих каталогов. Здесь мы обсу­
дим файлы, необходимые для компиляции MINIX 3, сначала те из них, которые
находятся в каталоге inc lude / , а затем - из каталога inc lude / sys / . В следую­
щем разделе мы рассмотрим файлы в каталогах inc l ude / rni n i x / и inc lude /
ibrn / , которые соответственно содержат код, специфичный для системы MINIX 3
и ее реализации на I ВМ-совместимых компьютерах.
Файлы в I<аталоге inc lude / в действительности предназначены для решения
общих задач, поэтому в большинство модулей с исходным кодом системы они не
включаются. Вместо этого они добавляются в другие заголовочные файлы, напри­
мер в главный заголовочный файл src / kerne l / kernel . h, файлы src /rnrn/rnrn . h
и s r c / f s / f s . h, подключаемые при каждой сборке системы. Главные заголо­
вочные файлы каждой из трех составных частей системы служат каждый для
своих целей, но начало у всех них одинаковое, оно приведено в листинге 2 . 1 2 .
Главные заголовочные файлы еще будут обсуждаться в этой книге, цель данного
обзора - только подчеркнуть, что заголовочные файлы из различных каталогов
используются совместно. В этом и следующем разделах мы упомянем каждый из
перечисленных в листинге 2 . 1 2 файлов.
1 58 Глава 2. П р оцессы

Часть основного заголовка, подключающая все заголовочные файлы, нужные


Листинг 2 . 1 2 .
в коде. Обратите внимание на два файла const.h, один из которых находится
в каталоге include/, второй - в локальном каталоге
# i nc lude <minix / c on f i g . h> / * Этот файл ДОЛЖЕН включаться первым * /
# inc l ude <ans i . h> / * Этот файл ДОЛЖЕН включаться в торым * /
# i nc l ude < l im i t s . h>
# i nc l ude < e r rno . h>
# i nc l ude < sy s / type s . h>
# i nc l ude <min i x / con s t . h>
# i nc l ude <mi n i x / type . h>
# i nc l ude <minix . sy s l ib . h>
# i nc l ude " const . h "

Начнем с первого из файлов в каталоге i n c lude / - an s i . h (строка 0000) .


Это - второй заголовочный файл, обрабатываемый при компиляции любой из
частей M INIX 3 - лишь файл i n c l ude / m i n i x / c o n f i g . h обрабатывается
раньше. Назначение ans i . h - удостовериться, что компилятор соответствует
требованиям стандарта языка С, который определяется Международной органи­
зацией по стандартизации. Этот стандарт иногда называется также ANS I С,
поскольку изначально, до международного признания, он разрабатывался Аме­
риканским национальным институтом стандартов (American National Standard
Institute, ANSI ) . Соответствующий стандарту компилятор должен определять
несколько макросов, пригодных к использованию в компилируемых программах.
Например, макрос sтос у «правильного�. компилятора должен иметь зна­
_ _

чение 1 , как если бы препроцессор С получил строку


# d e f ine � S TD C� 1

Сейчас поставляемый с MINIX 3 компилятор удовлетворяет стандарту, но более


старые версии MINIX были разработаны до его принятия, поэтому MINIX 3 все
еще можно скомпилировать классическим компилятором С Кернигана и Ритчи.
MINIX создавалась как легко переносимая система, и возможность привлекать
старые компиляторы - важная составная часть задуманного. Следующая коман­
да в строках 0023 и 0025 обрабатывается в том случае, если применяется стан­
дартный компилятор:
#de f ine _ANS I

В a n s i . h определяются несколько макросов, причем способ зависит о т того,


определен ли макрос AN S I Это пример макроса проверки поддерживаемых
_ .

функций.
Еще один определенный здесь макрос проверки поддерживаемых функций -

_ P O S I X S OURC E (строка 0065 ) . Его наличие требуется согласно стандарту


_

POSIX. Мы проверяем определение мaкpoca P O S I X S OURC E в случае, если оп­


_ _

ределены другие макросы стандарта POSIX.


При компиляции программ на языке С типы данных аргументов функций и воз­
вращаемых ими значений должны быть известны до генерации использующего
их кода. В сложной системе выдерживать такой порядок определения функций
затруднительно, поэтому в С можно указывать прототипы функций, позволяющие
обьявлятъ типы аргументов и возвращаемых значений до определения, функции.
2. 6. Реализация пр оцессов в M I NIX 3 1 59

Наиболее важным макросом в файле ans i . h является _


PROTOTY P E . Он позво­
ляет записывать прототип функции в таком виде:
_РRОТОТУРЕ ( тип - р е зультата , имя - функции , ( тип- аргумента аргумент , . . . ) )

Если компилятор соответствует стандарту, препроцессор приводит эту запись


к следующему виду:
тип -ре зультата имя - функции ( тип- аргумента аргумент , . . . )

Если же компилятор более старый, макрос транслируется так:


тип - р е зультата имя - функции ()

Прежде чем переходить от ans i . h к другим файлам, обратим внимание еще на


один момент. Все содержимое файла (кроме начальных комментариев) обрамле­
но следующими строками:
Jl i fnde f _ANS I_H
Jl endi f / *_ANS I_H * /

Сразу после строки # i fnde f AN S I H определяется сам макрос AN S I H На­


_ _ _ _ .

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


файл будет включен только один раз. При повторном включении все его содер­
жимое игнорируется. Подобная техника используется во всех файлах из катало­
га inc lude / .
Здесь нужно пояснить два момента. Во-первых, во всех последовательностях
# i fnde f . . . # de f i ne в главных заголовочных каталогах имена файлов предва­
ряются подчеркиванием. В каталогах исходных С-кодов может присутствовать
другой заголовочный файл с тем же именем; к нему будет применен тот же ме­
ханизм, однако без подчеркивания. Таким образом, включение файла из глав­
ного заголовочного каталога не воспрепятствует обработке одноименного за­
головочного файла цз локального каталога. Во-вторых, обратите внимание на
комментарий / * ANS I H * / после # i fnde f . Он не является обязательным, а все­
_ _

го лишь помогает отслеживать вложенные конструкции # i fnde f . . . # endi f


и # i f de f . . . # endi f . Тем не менее при написании комментариев следует со­
блюдать осторожность: отсутствие комментариев лучше, чем ошибки в них.
Второй файл из i n c l ude / , косвенно включаемый в большинство исходных
файлов MINIX 3, - это l imi t s . h (строка О 1 00 ). В нем объявлены основные
размеры, относящиеся как к типам языка (разрядность целого числа), так и к опе­
рационной системе (максимальная длина имени файла).
Обратите внимание на то, что номера строк в исходных текстах на сопровождаю­
щем книгу компакт-диске начинаются с новой сотни с каждым новым фай­
лом. Другими словами, не следует думать, что файл ans i . h содержит 100 строк,
с 00000 по 00099. По этой причине небольшие изменения, вносимые в один файл,
скорее всего (хотя и не гарантированно), не подействуют на последующие фай­
лы. Кроме того, когда в листинге появляется новый файл, указывается особый
заголовок, включающий ряд символов + , имя файла и еще один ряд символов +
(без нумерации строк). Пример такого заголовка вы можете видеть между стро­
ками 00068 и 00 1 00.
1 60 Глава 2 . Процессы

Большинство главных заголовочных файлов также включает файл e r rno . h


(строка 0200). Здесь содержатся коды ошибок, возвращаемые пользователю в гло­
бальной переменной e rrno после системных вызовов. Эта переменная также
индицирует некоторые внутренние ошибки, например попытку переслать со­
общение несуществующему процессу. Внутри системы было бы неэффективно
проверять значение глобальной переменной после вызова функции, способного
привести к ошибке, однако функции зачастую возвращают другие целые чис­
ла, например количество байтов, переданных во время операции ввода-вывода.
В MINIX 3 коды ошибок отрицательны, благодаря чему система может распо­
знать их. Перед тем как возвращенное функцией значение передается программе
пользователя, оно преобразуется в положительное. Такой эффект достигается за
счет того, что каждый код ошибки задается строкой вида (строка 0236):
� de f ine Е РЕRМ ( SIGN 1 )
_
При компиляции системных файлов в главных заголовочных файлах определя­
ется макрос S Y S TEM в результате чего S I GN транслируется в � - � , а при ком­
_ , _

пиляции пользовательских программ _ S Y S TEM никогда не определяется, и _SIGN


просто игнорируется.
Следующая рассматриваемая группа файлов не включается в главные заголо­
вочные файлы, хотя и повсеместно используется в коде MINIX 3. Главный из
них - uni s t d . h (строка 0400). В нем перечислены константы, большая часть из
которых обязательна в стандарте POSIX. Кроме того, в нем описаны прототипы
многих функций, в том числе всех функций для доступа к системным вызовам
MINIX 3. Второй файл - s t r i ng . h (строка 0600); он содержит прототипы мно­
гочисленных функций, работающих со строками. Файл s i gna l . h (строка 0700)
задает стандартные имена сигналов. В нем же определено несколько специаль­
ных сигналов для внутреннего использования операционной системы MINIX 3.
Поскольку функции операционной системы выполняются независимыми процес­
сами, а не единым ядром, системным компонентам необходим особый способ взаи­
модействия друг с другом, подобный обмену сигналами. В файле s i gna l . h оп­
ределены прототипы некоторых функций для работы с сигналами. Как мы позд­
нее увидим, использование сигналов характерно для всех компонентов MINIX 3.
В файле f cnt l . h (строка 0900) указываются различные параметры, используе­
мые при управлении файлами. Например, благодаря задаваемым здесь констан­
там для открытия файла в режиме чтения можно указывать макрос O R D ONL У _

вместо того, чтобы напрямую передавать значение О. Этот файл нужен в основ­
ном файловой системе, но он же применяется в нескольких местах внутри ядра
и менеджера памяти.
Как мы увидим при рассмотрении уровня драйверов устройств MINIX в главе 3,
консольный и терминальный интерфейсы операционной системы сложны:, по­
скольку большое количество различного оборудования должно взаимодейство­
вать с операционной системой и пользовательскими программами стандартным
образом. Для управления устройствами ввода-вывода терминального типа исполь­
зуются константы, макросы и функции, прототипы которых приведены в файле
t e rmi o s . h (строка 1000). Самая главная структура здесь - структура t e rmi o s .
2 . 6 . Реализация процессов в M I N IX 3 1 61

В нее входят различные флаги, управляющие режимами работы, переменные


для задания скоростей ввода и вывода данных, а также массив специальных сим­
волов, таких как I NTR и KI LL. Эта структура, как и многие макросы и функции
в файле t e rmi o s . h, регламентирована стандартом POSIX.
Тем не менее, каким бы всеобъемлющим ни был стандарт POSIX, он не может
включить в себя все, что может понадобиться. Поэтому вторая часть файла, на­
чиная со строки 1 1 40, относится к расширениям POSIX. Некоторые из них
вполне очевидны, например определение стандартных скоростей передачи дан­
ных ( 57 600 бод и выше) и поддержка отображения окон терминалами. По­
добные расширения не запрещены POSIX, поскольку стандарт не может объять
необъятное. Но при разработке в MINIX 3 программ, которые рассчитаны на пе­
ренос в другое окружение, следует избегать специфичных для MINIX 3 опреде­
лений. Сделать это несложно. Если в файле имеются специфичные для MINIX
расширения, то их использование контролируется макрокомандой
# i f d e f _MI N I X

Если макрос _MINIX не определен, расширения игнорируются.


Поддержка �сторожевых� таймеров задается файлом t ime r s . h (строка 1300),
включенным в главный заголовочный файл ядра. Этот файл определяет струк­
туру s t ru c t t imer и прототипы функций, работающих со списками таймеров.
В строке 132 1 появляется определение типа tmr_func_t . Это - указатель на
функцию, применение которого продемонстрировано в строке 1332: в структуре
t imer, используемой в качестве элемента списка таймеров, один элемент имеет
тип tmr_func_t и определяет функцию, вызываемую при истечении таймера.
Упомянем еще четыре файла, находящихся в каталоге inc lude / . Файл stdl ib . h
определяет типы, макросы и прототипы функций, как правило, необходимые для
компиляции С-программ, по сложности превосходящих самые примитивные.
Файл s t d l ib . h - один из наиболее часто применяемых заrоловочных файлов
при компиляции пользовательских программ, хотя лишь немногие исходные
тексты ядра MINIX 3 ссылаются на него. Файл s t d i o . h знаком любому, кто
начинал осваивать язык С с написания известной программы � нello, World! � .
Его вряд л и можно встретить в системных файлах, хотя о н используется почти
в каждой пользовательской программе. Файл а . out . h определяет формат фай­
лов , в которых исполняемые программы хранятся на диске. В нем определе­
на структура е х е с , информация которой используется менеджером процес­
сов для загрузки нового образа программы при системном вызове ехе с . Файл
s t dde f . h задает несколько употребительных макросов.
Теперь переместимся в подкаталог inc lude / sy s / . Как видно из листинга 2. 1 2,
все главные заголовочные файлы основных составных частей MINIX 3 включают
в себя файл sys / typ e s . h (строка 1400) сразу после ans i . h. В typ e s . h опреде­
ляются различные типы данных, встречающиеся в MINIX 3. Благодаря ему можно
избежать различных ошибок, связанных с неправильным использованием базо­
вых типов. Размеры некоторых типов данных (в битах) для 1 6- и 32-разрядных
систем указаны в табл. 2.4. Кроме того, обратите внимание, что имена всех типов
1 62 Глава 2. П роцессы

данных заканчиваются символами «_t » . Это - больше чем договоренность,


это - требование стандарта POSIX. Согласно последнему, суффикс «_t » явля­
ется зарезервированным и не должен использоваться в идентификаторах, не яв­
ляющихся именами типов.

Таблица 2 . 4 . Размеры (в битах) некоторых типов данных на 1 6- и 32-разрядных системах


Тип 1 6 - разрядная система M I N IX 32- разрядная система M I N IX
gid_t 8 8
dev_t 16 16
pid_t 16 32
ino_t 16 32
В настоящее время операционная система MINIX 3 полностью поддерживает
32-разрядные микропроцессоры, однако с течением времени 64-разрядные процес­
соры будут приобретать все большую значимость. При необходимости существу­
ет возможность создания типа данных, не поддерживаемого аппаратно. В строке
147 1 определен тип u 64_t в виде структуры s t ru c t { u 3 2 _t [ 2 ] } . В текущей
реализации он используется нечасто, однако иногда может быть полезен - к при­
меру, все данные о дисках и разделах (смещения и размеры) хранятся в виде
64-разрядных чисел, что позволяет задействовать диски очень большого объема.
В MINIX 3 используются множество определений типов данных, которые, в конеч­
ном счете, сводятся компилятором к относительно небольшому числу базовых
типов. Это делает код более удобочитаемым; например, переменная, объявлен­
ная с типом dev_t , содержит главный и вспомогательный номера, определяю­
щие устройство ввода-вывода. С точки зрения компилятора, ничего бы не изме­
нилось, если бы вы указали тип sho r t в качестве типа переменной. Отметим
еще одну особенность: многие типы объявлены «парами», где два имени разли­
чаются только регистром первой буквы, к примеру, dev_t и Dev_t . Все типы,
начинающиеся с прописной буквы, эквивалентны int. Они предназначены для
прототипов функций, которые должны использовать типы данных, совместимые
с int, с целью поддержки классических С-компиляторов. Более подробные объ­
яснения вы найдете в комментариях файла typ e s . h.
Внимания заслуживает еще один фрагмент условного кода (строки 1 502- 1 5 1 6),
начинающийся с директивы
� i f _EM_WS I Z E ==2

Как было отмечено ранее, большая часть условного кода удалена из текста, однако
мы сохранили этот пример, чтобы продемонстрировать механизм использования
условных определений. Maкpoc _EM_WS I Z E - еще один макрос проверки поддер­
живаемых функций, определенный компилятором. Он задает размер слова дан­
ных целевой системы в байтах. Последовательность # i f . . . # е l s е . . . # endi f
создает определения «раз и навсегда» , чтобы обеспечить корректную компиля­
цию кода независимо от разрядности системы - 16 или 32 бит.
В операционной системе MINIX 3 широко используются несколько других файлов
из каталога inc lude / sy s / . Файл sy s / s i gcont ext . h (строка 1 600) определяет
2.6. Реализация п роцессов в M I N IX 3 1 63

структуры для сохранения и восстановления нормального состояния системы до


и после исполнения процедуры обработки сигнала и используется ядром и ме­
неджером процессов. Файл sy s / s t a t . h (строка 1700) определяет структуру,
представленную в листинге 1 . 2 и возвращаемую системными вызовами s t a t
и f s t а t , прототипы функций s t а t и f s t а t , а также других функций, работаю­
щих со свойствами файлов. Ссылки на файл sys / s t a t . h имеются в нескольких
местах файловой системы и менеджера процессов.
Остальные файлы, которые мы рассмотрим в этом разделе, используются не так
широко. Файлы sys / d i r . h (строка 1800) определяют структуру записи катало­
га MINIX 3. Существует лишь одна прямая ссылка на данный файл, однако она
включает файл sys / di r . h в другой заголовочный файл, широко используемый
в файловой системе. Важным параметром, определяемым в sy s / di r . h, являет­
ся максимальная длина имени файла (60 символов). Файл sy s / wa i t . h (строка
1 900) содержит макросы, используемые системными вызовами wa i t и wa i t p id,
реализованными в диспетчере процессов.
Стоит упомянуть несколько файлов каталога inc lude / sy s / . Операционная
система M INIX 3 поддерживает трассировку исполняемых файлов и анализ
ключевых дампов в программе отладки. Файл sy s / p t ra c e . h определяет различ­
ные операции, выполняемые по системному вызову p t race. Файл sys / svrct l . h
включает структуры данных и макросы, используемые в svrc t l - неком подо­
бии системного вызова, предназначенным для координирования процессов сер­
верного уровня в ходе запуска системы. Системный вызов s e l e c t организует
ожидание ввода по нескольким каналам, например, для псевдотерминалов, ожи­
дающих сетевых подключений. Определения, необходимые вызову s e l e c t , на­
ходятся в файле sys / s e l e c t . h.
Мы намеренно отложили обсуждение файла sy s / i o c t l . h и связанных с ним
файлов, поскольку их назначение невозможно полностью понять, не разобравшись
с файлом rninix / i o c t l . h. Системный вызов i o c t l используется для управле­
ния устройствами. Число устройств, подключаемых к современным компьюте­
рам, неуклонно растет, и каждое из них нуждается в управлении. В данной книге
мы описываем операционную систему MINIX 3 с относительно небольшим ко­
личеством устройств ввода-вывода. Многие из них, включая сетевые интер­
фейсы, S СSI-контроллеры и звуковые карты, могут быть добавлены в систему.
Для того чтобы облегчить управление, нужные объявления разбиты на группы
и размещены в отдельных небольших файлах. Все эти файлы включены в файл
sy s / i o c t l . h (строка 2000 ) , действующий подобно главному заголовочному
файлу в листинге 2. 1 2. На компакт-диске имеется лишь один из файлов, sy s /
i o c_di s k . h (строка 2 1 00). Он, а также все прочие файлы, включенные в sy s /
i o c t l . h, расположены в каталоге inc lude / sy s / . Они являются частью �от­
крытого интерфейса» ; другими словами, программист может использовать их
для написания любой программы, предназначенной для работы в среде MINIX 3.
Тем не менее все указанные файлы зависят от дополнительных макроопределений
из файла rninix/ i o c t l . h (строка 2200), являющегося для них заголовочным.
Сам файл rninix/ ioct l . h не следует использовать при программировании, имен­
но поэтому он расположен в каталоге inc l ude /rninix / , а не inc l ude / sy s / .
1 64 Глава 2 . П роцессы

Макросы, заданные в упомянутых файлах, определяют, каким образом различ­


ные элементы, необходимые всем допустимым функциям, представляются 32-раз­
рядным целым числом, передаваемым системному вызову i o c t l . К примеру,
дисковые устройства требуют 5 видов операций, как видно из строк 2 1 10-2 1 14
файла sys / i o c_di s k . h. Символьный параметр ' d ' указывает вызову i o c t l ,
что операция относится к дисковому устройству, число о т 3 д о 7 задает код опе­
рации, а третий параметр операции чтения или записи определяет размер струк­
туры, в которую осуществляется передача данных. В файле m i n i x / i o c t l . h
строки 2225-223 1 показывают, что 8 бит символьного кода сдвигаются на 8 бит
влево, 13 бит размера структуры (младших) сдвигаются на 16 бит влево, а затем
к полученным значениям применяется логическая операция И, где вторым опе­
рандом является код операции. В старших трех разрядах 32-разрядного числа за­
писан код, определяющий тип возвращаемого значения.
Хотя может показаться, что все описанное требует большого объема работы, эта
работа выполняется на этапе компиляции и создает более эффективный интер­
фейс с системным вызовом на этапе выполнения, поскольку фактически пере­
данный параметр - наиболее естественный тип данных для процессора. Тем не
менее здесь вспоминается известный комментарий Кена Томпсона, помещенный
в исходный код одной из ранних версий UNIX:
/* предполагается , что вы это не по йм ете * /

В строке 224 1 файла minix/ i o c t l . h содержится прототип системного вызова


i o c t l . Как правило, программисты не совершают этот вызов впрямую, посколь­
ку во многих РОSIХ-функциях с прототипами в файле i n c l u de / t e rmi o s . h
устаревшая библиотечная функция i o c t l больше не используется для работы
с терминалами, консолями и другими подобными устройствами. Тем не менее
она по-прежнему нужна. Фактически, библиотека превращает РОSIХ-функции,
управляющие терминальными устройствами, в системные вызовы i o c t l .

2 . 6 . 4 . Заголовоч ные файл ы M I N IX 3


Файлы, специфичные для MINIX 3, расположены в каталогах inc lude / m i n i x /
и inc lude / i bm / . Первый каталог содержит файлы, общие для реализаций
MINIX на всех платформах, хотя в некоторых из файлов есть альтернативные
определения, зависящие от платформы. Один из таких файлов, i o c t l . h, был
рассмотрен нами в предыдущем подразделе. Второй каталог включает структуры
и макросы, специфичные для реализации M INIX 3 на платформе IBM.
Мы начнем изучение с каталога minix / . Ранее был упомянут файл c on f i g . h
(строка 2300), включаемый во все главные заголовочные файлы основных ком­
понентов системы и фактически являющийся первым файлом, обрабатываемым
компилятором. Во многих случаях, когда в системе меняется оборудование или
же нужно изменить способ работы MINIX 3, нужно лишь отредактировать этот
файл и перекомпилировать систему. Если вы редактируете файл, то, по всей
вероятности, вам следует также изменить комментарий к строке 2303, отразив
в нем суть нововведений.
2 . 6 . Реализация п роцессов в M I N IX 3 1 65

Параметры, задаваемые пользователем, находятся в первой части файла, хотя


некоторые из них не предназначены для редактирования в нем. В строке 2326
происходит включение заголовочного файла m i n i x / sys_c o n f i g . h и опреде­
ление нескольких унаследованых от него параметров. Программисты сочли это
целесообразным, поскольку некоторым системным файлам нужны лишь базовые
определения файла minix/ sys_conf i g . h, а остальные определения conf i g . h
являются излишними. В файле c o n f i g . h имеется множество имен файлов,
не начинающихся с символа подчеркивания и способных вызывать конфликты
с обычными именами, например именами CHI P или I NTEL, которые вполне мо­
гут встретиться в программах, перенесенных в MINIX 3 из другой операционной
системы. Все имена в sy s_c on f i g . h начинаются с подчеркивания, и вероят­
ность конфликтов значительно ниже.
Параметру МACH I NE в файле sy s_c on f i g . h задается значение _МACHINE_
I BM_PC. В строках 2330-2334 определены альтернативные значения для МACHINE.
Предыдущие версии MINIX переносились на платформы Sun, Ataгi и Macintosh,
а полный исходный код содержит варианты для альтернативного аппаратного
обеспечения. Большая часть исходного кода MINIX 3 не зависит от типа компь­
ютера, однако операционная система всегда включает в себя зависимые от плат­
формы фрагменты. Поскольку MINIX 3 - новая система, на момент написания
этой книги работа над ее переносом на платформы, отличные от Intel, еще не бы­
ла завершена.
Другие определения файла conf i g . h позволяют настроить операционную сис­
тему под конкретные нужды. Например, количество буферов, хранимых в дис­
ковом кэше файловой системы, следует задавать как можно большим, хотя это
и приводит к значительному потреблению памяти. В строке 2345 определено
кэширование 1 28 блоков - это число считается минимальным и приемлемо толь­
ко в случае, если система располагает менее 16 Мбайт оперативной памяти. Если
объем оперативной памяти больше, число кэшируемых блоков можно значитель­
но увеличить. Если желательно использовать модем или входить в систему через
сетевое подключение, значения параметров NR_RS_L INES и NR_PTYS (строки
2379 и 2380) следует увеличить, а затем перекомпилировать систему. Заключи­
тельная часть файла conf i g . h содержит необходимые определения, изменять
которые, однако, не рекомендуется. Многие из них задают альтернативные име­
на для констант, определенных в файле sys_c o n f i g . h.
Файл sys_con f i g . h (строка 2500) включает определения констант, которые
могут понадобиться системному программисту, например, пишущему новый
драйвер устройства. Вам вряд ли понадобится изменять содержимое этого фай­
ла, за исключением пapaмeтpa _NR_PROC S (строка 2522), задающего размер таб­
лицы процессов. Если вы хотите использовать систему MINIX 3 в качестве сете­
вого сервера с большим числом удаленных пользователей или одновременно
выполняемых процессов, может возникнуть необходимость увеличить значение
этой константы.
Следующий файл, cons t . h (строка 2600), иллюстрирует еще один распростра­
ненный вариант применения заголовочных файлов. В нем находится большое
1 66 Глава 2 . П роцессы

количество определений используемых в нескольких местах одновременно кон­


стант, значения которых обычно не меняют при перекомпиляции ядра. Однократ­
ное определение констант в заголовочном файле позволяет избежать ошибок,
вызываемых противоречивыми определениями в различных фрагментах кода,
использующих эти константы. Дерево исходных кодов MINIX 3 содержит ряд
других файлов с именем cons t . h, однако они имеют более ограниченное при­
менение. Например, определения, требующиеся только в ядре, помещены в файл
s rc / kerne l / con s t . h, определения файловой системы - в файл s rc / s e rvers /
f s / const . h, менеджер процессов использует для локальных определений файл
s rc / s e rve r s / prn / cons t . h. Файл inc lude /rninix / c ons t . h содержит толь­
ко определения, применяемые как минимум двумя компонентами операционной
системы.
Несколько определений из файла const . h стоит рассмотреть подробнее. Макрос
EXTERN позволяет объявлять переменные как внешние (строка 2608). Глобаль­
ные переменные, объявленные в заголовочных файлах и включаемые в два или
более файлов, объявляются с ключевым словом EXTERN , например:
EXTERN int who ;

Можно попытаться объявить переменную без ключевого слова EXTERN:

int who ;

Однако если бы переменная была объявлена таким образом и включена в не­


сколько файлов, некоторые редакторы связей указали бы на ошибку - много­
кратное определение переменной. Более того, справочное руководство по языку
С явно запрещает подобные конструкции [67 ] .
В о избежание проблем необходимо, чтобы объявление было прочитано так:
ext e rn int who ;

Но есть одно исключение. Использование макроса EXTERN решает проблему, по­


скольку макрос преобразуется в слово ext e rn во всех файлах, включающих за­
головочный файл const . h и не переопределяющих EXTERN как пустую строку
после включения c o n s t . h. Последнее делается в каждой части M INIX 3 по­
средством размещения глобальных определений в специальном файле g l o . h
(к примеру, s rc / kerne l / g l o . h ) , который неявно включается при компиляции.
В любом файле g l о . h присутствует фрагмент
# i f de f _TABLE
# unde f EXTERN
# de f ine EXTERN
# endi f

А в каждом файле t а Ы е . с имеется предшествующая # i nc lude директива


# de f ine _TABLE

Таким образом, когда заголовочный файл включается при компиляции t аЫ е . с ,


ключевое слово ext e rn перед объявлениями переменных не ставится (так как
в файле t аЫ е . с макрос EXTERN определен как пустая строка), поэтому все гло­
бальные переменные размещаются в одном месте, в объектном файле t аЫ е . о .
2 .6 . Реал изаци я п роцессов в M I N IX 3 1 67

Если вы новичок в программировании на С и не совсем понимаете, к чему при­


водят описанные действия, не пугайтесь - эти детали в действительности не так
важны. Это всего лишь деликатное перефразирование приведенного ранее ком­
ментария Кена Томпсона. У некоторых компоновщиков многократное включе­
ние одного файла может вызвать проблему, так как приводит к многократному
объявлению одних и тех же переменных. Макрос EXTERN используется для того,
чтобы сделать систему более переносимой между различными платформами с по­
тенциально проблематичными компоновщиками.
Макрос PRIVATE определяется в качестве синонима для ключевого слова s t a t i c .
Этот макрос всегда применяется для объявления процедур и данных, на которые
не будут ссылаться другие компоненты системы, чтобы имена этих объектов не
были видимы за пределами того файла, где они объявлены. Как правило, пере­
менные и процедуры необходимо по возможности размещать в локальной облас­
ти видимости. Макрос PUBL I C определен как пустая строка. Например:
PUB L I C vo i d l o c k_dequeue ( rp )

Это объявление преобразуется препроцессором в следующий код:


vo i d l o c k_dequeue ( rp )

В соответствии с синтаксическими правилами языка С этот фрагмент означает,


что функция с именем l o c k_dequeue экспортируется и может быть использо­
вана в других файлах, компонуемых в один двоичный код - в данном случае,
в любом месте ядра. Еще пример:
PRIVATE vo i d dequeue ( rp )

Это объявление функции в том же файле будет обработано препроцессором сле­


дующим образом:
s t a t i c vo i d dequeue ( rp )

Такая функция может быть вызвана только кодом, расположенным в том же ис­
ходном файле. Использовать макросы PRIVATE и PUBL I C необязательно, это -
только попытка исправить проблемы, вносимые соглашениями С (где по умол­
чанию имена размещаются в глобальной области видимости, а должно быть
наоборот).
В оставшейся части файла cons t . h определяются константы, повсеместно ис­
пользуемые в системе. Так, например, везде и всюду в коде фигурирует величина
базового блока памяти, зависящая от архитектуры системы. Для платформ Intel
она равна 1 024 байт. В строках 2673-268 1 определены другие ее значения, соот­
ветствующие платформам Intel, Motorola 68000 и Sun SP ARC. Кроме того, в этом
файле определяются удобные макросы МАХ и MIN. Например, для вычисления
большего из двух значений можно применить следующую запись:
z = МАХ ( х , у ) ;

Еще один файл, косвенно включаемый при каждой компиляции в главные заго­
ловочные файлы, - это t yp e . h (строка 2800). В нем содержится ряд описаний
ключевых типов и связанных с ними числовых констант.
1 68 Глава 2 . П роцессы

Первые две структуры определяют два различных типа карт памяти (строки
2828-2840): одна из них соответствует локальным областям (внутри пространст�
ва данных процесса), а другая - удаленным областям памяти (к примеру, вирту­
альному диску). Здесь самое время рассказать о концепциях обращения к памя­
ти. Как мы уже упомянули, размер базового блока памяти в MINIX 3 составляет
1024 байт. Существует две ссылки на память: ссылка phy s_c l i c k s использует­
ся ядром для доступа к любому элементу памяти системы, а ссылка v i r_
c l i cks предназначена для всех процессов, отличных от ядра. Ссылка v i r_
c l i cks всегда указывается относительно начала сегмента памяти, выделенной
определенному процессу, и ядру зачастую приходится осуществлять преобразо­
вания между виртуальными (относящимися к процессам) и физическими (отно­
сящимися к оперативной памяти) адресами. Подобное неудобство компенсиру­
ется тем, что процесс может самостоятельно выполнять внутренние обращения
к памяти, используя ссылку v i r_c l i ck s .
В ы могли бы предположить, что для задания размера памяти обоих типов доста­
точно одной ссылки. Тем не менее указание выделенной процессу памяти с по­
мощью ссылки v i r_c l i cks имеет одно преимущество: vi r_c l i cks гарантирует,
что ни одно обращение к памяти не выйдет за границы пространства, выделенного
процессу. Такая возможность присуща современным процессорам Intel, в част­
ности семейству Pentiurn, и называется защищенным режимом. Отсутствие за­
щищенного режима в ранних процессорах 8086 и 8088 вызывало проблемы при
разработке первых версий MINIX.
Еще одна важная структура, определенная в файле typ e . h, - s i gms g (строки
2866-2872). При перехвате сигнала ядро должно гарантировать, что процесс,
которому был послан сигнал, при следующем своем запуске начнет выполнять
процедуру обработки сигнала, а не продолжит работу в обычном порядке. Боль­
шая часть работы по управлению сигналами выполняется менеджером процес­
сов. В случае перехвата сигнала менеджер процессов передает ядру структуру,
подобную s i gmsg.
Структура k in f o (строки 2875-2893) используется для распространения инфор­
мации о ядре среди других компонентов системы. Менеджер процессов задейст­
вует эту информацию, формируя свою часть таблицы процессов.
Структуры данных и прототипы функций взаимодействия между процессам.и оп­
ределены в заголовочном. файле ipc . h (строка 3000). Наиболее важным опреде­
лением в этом файле является тип me s s age (строки 3020-3032). Его можно бы­
ло бы задать как массив определенного количества байтов, но для поддержания
хорошего стиля программирования он описан как структура, содержащая объ­
единение различных типов сообщений. Всего имеется семь форматов сообще­
ний, с именами от me s s_l до me s s_B (формат me s s_б больше не использует­
ся). В структуре me s s age есть поле m_s ource, представляющее собой структуру
с информацией об отправителе сообщения, поле m_typ e , определяющее формат
сообщения (для системного задания - SYS_EXEC), и поля с данными сообщения.
Структуры семи типов сообщений показаны на рис. 2. 1 6. Первые две и послед­
ние две структуры кажутся одинаковыми. С точки зрения размеров элементов
2 . 6 . Реализация процессов в M I NIX 3 1 69

данных это действительно так, однако в типах данных имеется множество разли­
чий. Если на 32-разрядном процессоре Intel типы int , l ong и указатели на дан­
ные имеют одинаковую разрядность 32 бита, это отнюдь не означает, что на друтой
аппаратной платформе картина будет той же. Семь различных форматов введены
именно для упрощения перекомпиляции MINIX 3 под различные архитектуры.

m_source m_source m_source m_source m_source m_source m_source


m_type m_type m_type m_type m_type m_type m_type
m1 i1 m2_i1 m3_i1 m4_11 m5_c2 I m5_c1 m7_i1 m8_i1
-

m1 i2 m2_i2 m3_i2 m4_12 m5_i1 m7_i2 m8_i2


-

m1 iЗ m2_i3 m3_p1 m4_13 m5_i 2 m7_i3 m8_p1


-

m1 _р1 m2_11 m4_14 m5_11 m7_i4 m8_p2


m1_p2 m1 12 m3_ca1 m4_15 m5_12 m7_p1 m8_p3
-

m1 _рЗ m2_p1 m5_1 3 m7_p2 m8_p4


Рис. 2 . 1 6 . Семь типов сообщений MINIX 3. Размеры элементов сообщений зависят
от архитектуры компьютера; здесь показаны размеры для процессоров
с 32-разрядными указателями, например семейства Pentium
Когда необходимо передать сообщение, содержащее, к примеру, три целочислен­
ных значения и три указателя, задействуется первая структура. Подобным же
образом используются и другие форматы. Как же можно присвоить нужное зна­
чение первому целочисленному полю в первой структуре? Предположим, что сооб­
щение имеет имя х. Тогда объединение (union), содержащее параметры сообщения,
будет иметь имя x . m_u. Чтобы обратиться к первой структуре этого объедине­
ния, подставляется имя х . m_u . m_m l . Наконец, чтобы обратиться к первому це­
лочисленному полю в этой структуре, следует применять запись: х . m_u . m_
ml . ml i 1 . Она довольно длинная, поэтому после описания самого типа me s s age
описываются несколько макроопределений, слегка укорачивающих запись. Так,
x . m_u . m_ml . ml i l можно заменить на x . m l_i l . Укороченные имена имеют
следующий формат: они начинаются с буквы �m•, затем следует номер структу­
ры, знак подчеркивания, один или два символа, обозначающие тип значения (це­
лое число, указатель, длинное целое, символ, массив символов, функция), после
чего записывается число, позволяющее локализовать нужное поле в структуре.
При обсуждении форматов сообщений имеет смысл обратить внимание на то,
что операционная система и компилятор зачастую �понимают• такие вещи, как
размещение структур, что может упростить жизнь программисту. В MINIX 3 по­
ля с типом int зачастую используются для хранения беззнаковых целых значе­
ний. В некоторых случаях это чревато ошибками переполнения, но система на­
писана в расчете на то, что компилятор без потерь может присваивать значения
типа uns i gned переменным типа int и наоборот без изменения данных и без
переполнения. При более строгом подходе нужно было бы заменить каждую такую
целочисленную переменную объединением, состоящим из полей int и uns igned.
То же самое относится и к полям типа l ong, которые иногда используются для
1 70 Глава 2. Процессы

передачи данных типа uns i gned l ong. Кто-то может сказать, что мы поступаем
неправильно, но если вы собираетесь перенести MINIX 3 на новую платформу,
то вам почти наверняка придется некоторое время поработать над точным фор­
матом сообщений, теперь же вы предупреждены, что поведению компилятора
также нужно уделить немало внимания.
В файле ip c . h также определены прототипы ранее описанных примитивов пе­
редачи сообщений (строки 3095-3 1 0 1 ). Наряду с основными примитивами s end
r e c e ive, s endrec и no t i fy, определен и ряд других. Последние применяются
редко и являются скорее пережитками ранних этапов разработки MINIX 3. Ста­
рые компьютерные программы являются источником интересных <�:археологиче­
ских находок� и в последующих выпусках операционной системы вполне могут
<�:вымереть� . Тем не менее без пояснений с нашей стороны некоторые читате­
ли наверняка засомневаются. Неблокирующие вызовы nb_s end и nb_r e c e i ve
в основном заменены вызовом no t i fy, появившимся позднее и лучше разре­
шавшим проблему передачи и проверки неблокирующих сообщений. Прототип
примитива e cho не имеет полей источника и приемника. Вызов e cho бессмыс­
лен в рабочем коде, однако полезен в процессе разработки, поскольку позволяет
определять время передачи и приема сообщений.
Один из файлов каталога i n c l ude / rni n ix, sys l ib . h (строка 3200) использу­
ется практически повсеместно и включен в главные заголовочные файлы всех
компонентов MINIX 3, относящихся к пользовательскому пространству. Для
доступа к самому себе ядру не нужны библиотечные функции, поэтому в заголо­
вочном файле ядра, s rc / kerne l / k e rne l . h, ссылка на sys l ib . h отсутствует.
Файл sys l ib . h содержит прототипы библиотечных С-функций, вызываемых
из операционной системы для доступа к другим системным службам.
Мы не будем описывать детали, касающиеся библиотек С, однако многие биб­
лиотечные функции являются стандартными и доступны в любом компиляторе
языка. Тем не менее функции, на которые ссылается файл sys l i b . h, специ­
фичны для MINIX 3, и перенос MINIX 3 в систему с другим компилятором потре­
бует переноса указанных функций. К счастью, это не составляет труда, посколь­
ку большинство функций попросту извлекают параметры, указанные в вызове,
и вставляют их в структуру сообщения, а затем отсылают сообщение и извлека­
ют результаты из ответа. Многие из таких библиотечных функций укладывают­
ся в дюжину строк С-кода.
Отдельного внимания в файле sys l i b . h заслуживают четыре макроса досту­
па к портам ввода-вывода по чтению и записи с использованием байтов и слов,
а также прототип функции sys_sdev i o , на которую ссылаются все макросы
(строки 324 1 -3250). Важнейшим механизмом, используемым в MINIX 3 и по­
зволяющим переместить все драйверы устройств в пользовательское простран­
ство, является передача запросов на чтение и запись портов ввода-вывода от драй­
веров устройств к ядру.
Несколько функций, которые могли бы быть включены в файл sys l ib . h, вынесе­
ны в другой файл, sysu t i l . h (строка 3400), поскольку их объектный код компи­
лируется в отдельную библиотеку. Две функции нуждаются в более тщательном
2. 6. Реализаци я процессов в M I N IX 3 1 71

рассмотрении. Первая из них - p r i n t f (строка 3442). Если вы имеете опыт


программирования на С, вы знаете p r i n t f как стандартную библиотечную
функцию, которую можно встретить почти во всех программах.
Тем не менее здесь вы имеете дело не с тем, к чему, вероятно, привыкли. Версию
p r i nt f из стандартной библиотеки нельзя использовать внутри системных ком­
понентов. Кроме того, стандартная функция p r i nt f предназначена для генера­
ции стандартного вывода и должна <�:уметы форматировать вещественные числа.
Наличие стандартного вывода требует взаимодействия с файловой системой,
однако в случаях неполадок проблемному компоненту желательно иметь воз­
можность отображать сообщения без помощи каких-либо других компонентов.
Кроме того, поддержка всех спецификаций формата стандартной версии p r int f
сделает код громоздким без веских на то причин. Таким образом, в библиотеку
системных утилит включена упрощенная версия функции print f , выполняю­
щая лишь действия, нужные операционной системе. Местоположение библиоте­
ки системных утилит зависит от платформы; в 32-разрядных системах Intel она
находится в файле / u s r / l i Ь / i 3 8 6 / l i bsysu t i l . a. Если файловая система,
менеджер процессов или другая часть операционной системы связывается с биб­
лиотечными функциями, сначала выполняется поиск в библиотеке системных
утилит и лишь затем - в стандартной библиотеке.
В следующей строке находится прототип функции kpu t с. Она вызывается сис­
темной версией p r int f и отображает символы на консоли. Однако здесь есть
некоторая тонкость. Функция kpu t с определена в нескольких местах; ее копия
имеется в библиотеке системных утилит и используется по умолчанию. Тем не
менее другие компоненты системы определяют собственные версии kpu t c . Мы
рассмотрим одну из них при изучении консольного интерфейса в следующей
главе. Версия kpu t c есть и у драйвера журнала (здесь мы не описываем его в де­
талях). Более того, ядро поддерживает свою версию функции kpu t c , но это -
особый случай. Ядро не пользуется функцией p r i nt f ; вместо нее для печати
внутри ядра определяется и применяется специальная функция kp rint f .
Когда процессу в MINIX 3 нужно сделать системный вызов, о н посылает сооб­
щение менеджеру процессов или файловой системе. Каждое сообщение включает
номер желаемого системного вызова. Номера системных вызовов хранятся в фай­
ле c a l lnr . h (строка 3500). Некоторые числа не используются - они зарезерви­
рованы для еще нереализованных вызовов или соответствуют вызовам предше­
ствующих версий MINIX и в настоящее время обрабатываемым библиотечными
функциями. В конце файла определены номера, не относящиеся к вызовам, пе­
речисленным в табл. 1 . 1 . Например, вызов svrc t l (упомянутый ранее), а также
вызовы ks i g, unpau s e , revi ve и t a s k_rep ly используются только внутри
операционной системы. Механизм системных вызовов удобен для реализации
этих вызовов. Поскольку перечисленные вызовы не могут быть задействованы
внешними программами, в последующих версиях MINIX 3 их можно модифици­
ровать, не опасаясь за совместимость с пользовательскими программами.
Далее следует файл c orn . h (строка 3600). Одна интерпретация его названия -
<�:общий� (common), другая - <�:взаимодействие� (communication). В реальности
1 72 Глава 2. Процессы

же файл содержит общие определения для обеспечения взаимодействия между


серверами и драйверами устройств. В строках 3623-3626 определены номера за­
даний. Чтобы отличать их от номеров процессов, номера заданий отрицательны.
В строках 3633-3640 заданы номера процессов загрузочного образа. Обратите
внимание на то, что номера процессов - это номера элементов таблицы процес­
сов. Их не следует путать с идентификаторами процессов (PID).
Следующий раздел файла c om . h определяет механизм формирования сообще­
ний, используемых для выполнения операции no t i fy. Номера процессов участ�
вуют в генерации величины, записываемой в поле сообщения m_typ e . Типы для
уведомлений и прочих сообщений, определяемые в данном файле, формиру­
ются сочетанием категории типа и числа, идентифицирующего тип. В конце
c om . h перечислены макросы, преобразующие осмысленные идентификаторы
в числовые шифры, задающие типы сообщений и имена полей.
В каталоге inc lude /minix / на сопровождающем книгу компакт-диске имеют­
ся несколько файлов. Файл devi o . h (строка 4 1 00) определяет типы и констан­
ты, поддерживающие доступ к портам ввода-вывода из пользовательского про­
странства, а также несколько макросов, упрощающих написание кода, в котором
описываются порты и значения. Файл dmap . h (строка 4200) задает структуру
dmap и массив элементов типа dmap. Структура представляет собой таблицу,
связывающую главные номера устройств с поддерживающими их функциями.
В этом же файле определяются главный и вспомогательный номера для драйве­
ра memo ry и главные номера для других важных драйверов устройств.
Каталог inc l ude /minix/ содержит несколько дополнительных специализиро­
ванных заголовочных файлов, необходимых для компиляции системы. Один из
них, u 6 4 . h, обеспечивает поддержку арифметических операций над 64-раз­
рядными целыми числами, требуемую для обработки адресов дисков большого
объема. При появлении системы UNI X, языка С, процессоров класса Pentuirn
и операционной системы MINIX об этом было невозможно даже помыслить.
Возможно, следующая версия MINIX 3 будет написана на языке, оснащенном
встроенной поддержкой 64-разрядных целых чисел и процессоров с 64-разряд­
ными регистрами. До этого времени определения файла u 6 4 . h сыграют роль
временного решения.
Итак, нам осталось рассмотреть еще три файла. Файл keymap . h определяет
структуры, которые используются в реализации раскладок клавиатуры, рассчи­
танных на символы различных языков. Кроме того, данный файл необходим
программам, которые намерены генерировать и загружать таблицы нестандартных
символов. В файле Ьi tmap . h представлено несколько макросов, упрощающих
битовые операции, такие · как установка, сброс и тестирование. Файл part i t i on . h
определяет информацию, необходимую MINIX 3 для задания дискового разде­
ла, - по абсолютному смещению в байтах и размеру либо по адресу в формате
(цилиндр, головка, сектор). Размер и смещение указываются в виде числа типа
u 6 4_t , позволяя использовать диски большого объема. В этом файле не описы­
вается расположение разделов на диске; файл с подобным описанием находится
в следующем каталоге.
2.6. Реализаци я п роцессов в M I N IX З 1 73

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


мы затронем, - inc l ude / ibm. В нем располагаются несколько файлов с опре­
делениями, специфическими для компьютеров семейства I B M РС. Поскольку
язык С �знает� лишь адреса памяти и не предоставляет доступа к портам ввода­
вывода, библиотека содержит процедуры чтения и записи портов на языке ассемб­
лера. Различные процедуры объявлены в файле i bm/p or t i o . h (строка 4300).
Доступны все возможные процедуры ввода и вывода для байтов, целых и расши­
ренных целых данных, причем как отдельных, так и в виде строк: от i nb (ввод
байта) до out s l (вывод строки чисел l ong ) . Низкоуровневым процедурам ядра
может потребоваться возможность включать и отключать прерывания процессо­
ра, также не поддерживаемая языком С. Для этой цели в библиотеку помещен
ассемблерный код, а функции int r_di s aЫ e и int r_enaЫ e объявлены в стро­
ках 4325 и 4326.
Следующий файл в каталоге - int e rrup t . h (строка 4400), определяющий адре­
са портов и областей памяти, используемых контроллером прерываний и BIOS
РС-совместимых компьютеров. Дополнительные определения портов ввода-вы­
вода содержатся в файле port s . h (строка 4500). Последний включает адреса,
необходимые для доступа к интерфейсу клавиатуры и микросхеме таймера, ис­
пользуемой микросхемой часов.
Некоторые файлы каталога inc l ude / i bm являются обязательными и должны
быть упомянуты. Файлы Ь i o s . h, memo ry . h и part i t i on . h снабжены объем­
ными комментариями, и вы можете прочесть их, чтобы подробнее изучить меха­
низм использования памяти и таблицы разделов диска. Файлы cmo s . h, cpu . h
и in t 8 6 . h предоставляют дополнительную информацию о портах, битовых фла­
гах процессора, вызовах служб BIOS и DOS в 1 6-разрядном режиме. Наконец,
файл di s kparm . h определяет структуру данных для форматирования дискет.

2 . 6 . 5 . Структуры дан н ых процессов


и заголовоч ные файл ы
Теперь мы познакомимся с MINIX поближе и посмотрим, на что похож код в ка­
талоге s r c / ke rne l . Сперва мы изучим настоящий системный заголовочный
файл k e rne l . h (строка 4600). Он начинается с объявления трех макросов. Пер­
вый из них - POS I X SOURC E макрос проверки поддерживаемых функций, оп­
_ _ ,

ределяется стандартом POSIX. Все подобные макросы начинаются с символа


подчеркивания. Этот макрос включается для того, чтобы были видимы все сим­
волы, как требуемые стандартом, так и разрешенные, но не обязательные, а не­
официальные расширения были бы скрыты. Следующие два макроопределения
уже упоминались - это макрос _MI N I X, переопределяющий действие макроса
_POS I X S OURCE для расширений MINIX 3, и макрос _SYSTEM, наличие которого
_

проверяется там, где при компиляции необходимо учитывать разницу между сис­
темным и пользовательским кодом (например, может меняться знак кода возврата).
Затем в kerne l . h включаются другие заголовочные файлы из каталога inc lude /
и его подкаталогов inc lude / sy s / , inc lude / m i n i x / и inc lude / ibm / , в том
1 74 Глава 2 . Процессы

числе перечисленные в листинге 2 . 1 2 . Все эти файлы уже были нами рассмот­
рены в двух предыдущих разделах. Затем присоединяются еще шесть заголовоч­
ных файлов из локального каталога s r c / ke rne l / , имена которых заключены
в кавычки.
Файл kerne l . h позволяет легко включить в исходные файлы большое количе­
ство необходимых определений при помощи одной команды:
# i nc lude " kernel . h "

Иногда имеет значение то, в каком порядке включаются заголовочные файлы.


Именно использование файла ke rne l . h позволяет раз и навсегда гарантировать
соблюдение требуемой последовательности. Это поднимает концепцию �сделал
и забьm�, реализуемую заголовочными файлами, на более высокий уровень. Ана­
логичные главные заголовочные файлы имеются в каталогах с кодами других
системных компонентов, в частности файловой системы и менеджера процессов.
Теперь давайте перейдем к локальным файлам, включаемым в k erne l . h. В пер­
вую очередь мы встретим файл conf i g . h, который, подобно своему �тезке� сис­
темного уровня inc lude /minix/ con f i g . h, должен включаться в файл исходно­
го кода раньше локальных заголовочных файлов. Как и каталог inc lude /
mini x / , каталог s rc / ke rne l / включает файлы cons t . h и type . h. В inc lude /
minix/ помещены файлы, используемые несколькими компонентами, в том чис­
ле программами, работающими под управлением MINIX 3. Файлы, находящиеся
в каталоге s r c / ke rne l / , содержат определения, необходимые только для ком­
пиляции ядра. Каталоги файловой системы, менеджера процессов и других ком­
понентов системы также включают файлы cons t . h и type . h, определяющие
константы и типы для локального использования. В главный заголовочный файл
включены еще два файла, p r o t o . h и g l o . h; их аналогов нет в главных катало­
гах include / , однако они присутствуют в файловой системе и менеджере про­
цессов. Последним локальным заголовочным файлом, включенным в k erne l . h,
является ipc . h.
Обратите внимание на начало файла kerne l / conf i g . h. В нем вы найдете после­
довательность # i fnde f . . . # de f i ne , предотвращающую проблемы при попыт­
ке неоднократного включения файла. Мы рассматривали это решение раньше,
однако обратите внимание, что на этот раз имя макроса, CONF IG_H, не начинает­
ся с символа подчеркивания. Это означает, что макрос CONF I G_H отличается от
макроса _CONF IG_H, определенного в файле inc lude /mi n i x / conf i g . h.
Локальная версия файла conf i g . h ядра объединяет определения, которые вам
вряд ли нужно изменять, если вы собираетесь использовать MINIX 3 для изуче­
ния операционной системы или просто в качестве операционной системы компь­
ютера общего назначения. Тем не менее представим себе, что вам необходимо
сделать систему MINIX 3 очень компактной, чтобы с ее помощью вести управле­
ние научным инструментом или самодельным мобильным телефоном. Опре­
деления, расположенные в строках 47 1 7-4743, позволяют исключить ненужные
вызовы ядра. Избавление от ненужной функциональности также снижает требо­
вания к памяти, поскольку код, осуществляющий обработку каждого вызова ядра,
условно компилируется при помощи упомянутых определений. При отключении
2.6. Реал изаци я п роцессов в M I N IX 3 1 75

функции соответствующий код просто не включается в системный двоичный


файл. К примеру, мобильный телефон не нуждается в запуске новых процессов,
поэтому код данной функции можно исключить и уменьшить объем потребляемой
памяти. Большая часть остальных констант, определенных в файле conf i g . h,
управляет базовыми параметрами. Например, при обработке прерываний ис­
пользуется специальный стек размером K_STACK_BYTE S . Значение этой кон­
станты задается в строке 4772. Память под стек резервируется в ассемблерном
файле mрхЗ 8 6 . s .
В файле cons t . h (строка 4800) определен макрос, преобразующий виртуаль­
ные адреса, заданные относительно базы пространства ядра, в физические адреса
(строка 4814). С-функция urnap_ l o c a l определена в другом месте кода ядра;
ядро может выполнять такое преобразование от имени других компонентов сис­
темы, однако внутри ядра использование макроса более эффективно. В файле
cons t . h определено несколько других полезных макросов, включая макросы,
предназначенные для работы с битовыми картами. Два определения макросов
активируют важный механизм безопасности, встроенный в аппаратное обеспече­
ние Intel. Слово состояния процессора ( Processor Status Word, PSW) представля­
ет собой регистр процессора, а биты уровня защиты ввода-вывода (1/ 0 Protection
Level, I O PL) в нем определяют разрешение или запрет доступа к системе преры­
ваний и портам ввода-вывода. В строках 4859 и 485 1 заданы различные значения
слова состояния процессора, определяющие режимы указанного доступа для
обычных и привилегированных процессов. Эти значения помещаются в стек при
подготовке процесса к запуску.
В следующем файле, который мы рассмотрим, typ e . h (строка 4900), структура
rnerno ry (строки 4925-4928) использует два значения, базовый адрес и размер,
однозначным образом определяющие область памяти.
Файл type . h содержит ряд других прототипов и структур, используемых в лю­
бой реализации операционной системы MINIX 3. Например, в нем определены
две структуры: krne s s ages для передачи ядром диагностических сообщений и
randornne s s для генератора случайных чисел. В typ e . h также имеется ряд ма­
шинно-зависимых определений типов. Чтобы сократить код и сделать его более
удобочитаемым, мы удалили из файла условные фрагменты и определения дру­
гих типов процессоров. Тем не менее вы должны понимать, что определения, по­
добные структуре s t ac k f rarne_s (строки 4955-4974 ) , задающей сохранение
машинных регистров в стеке, являются специфичными для 32-разрядных про­
цессоров Intel. В случае другой платформы структура s t ac k f rarne_s будет оп­
ределена согласно конструкции регистров используемого процессора. Еще один
пример - структура s egde s c_s (строки 4976-4983 ). Она является частью за­
щитного механизма, предотвращающего доступ процессов к не принадлежащим
им областям памяти. Для другого процессора такая структура вообще может ока­
заться ненужной - это зависит от применяемого им механизма защиты памяти.
Еще один аспект, касающийся подобных структур: наличие всех нужных данных
является необходимым, однако недостаточным для оптимальной производитель­
ности. Структура s t a c k f rarne_s должна обрабатываться ассемблерным кодом.
1 76 Глава 2. Процессы

Чем более простые чтение и запись на языке ассемблера обеспечивает определе­


ние этой структуры, тем меньше время переключения контекста.
Следующий файл, p r o t o . h (строка 5 1 00), содержит прототипы всех функций,
которые должны быть видимы за пределами тех файлов, где они объявлены. Все
прототипы описаны при помощи макроса _PROTOTYP E , о котором говорилось
в предыдущем разделе. Таким образом, ядро MINIX 3 может быть скомпилиро­
вано как классическим компилятором С Кернигана и Ритчи, так и более совре­
менным, соответствующим стандарту ANSI С (такой компилятор входит в ком­
плектацию MINIX 3). Некоторые прототипы являются системно-зависимыми,
включая обработчики прерываний и исключений, а также функции, написанные
на языке ассемблера.
В файле g l о . h (строка 5300) находятся глобальные переменные ядра. Макрос
EXTERN уже обсуждался при рассмотрении файла inc lude /rninix / cons t . h.
Обычно он преобразуется в ключевое слово exte rn. Обратите внимание на то,
что макрос EXTERN предваряет многие определения файла g lo . h. Макрос EXTERN
переопределяется, если этот файл включается в файл t аЫ е . с, где определяется
макрос _TABLE. То есть место для хранения переменных, определенных подоб­
ным образом, фактически резервируется, когда g l o . h включается в компиля­
цию t аЫ е . с. Включение g l o . h в другие исходные С-файлы делает перемен­
ные файла t аЫ е . с видимыми в других модулях ядра.
Некоторые структуры данных ядра, определенные в файле g l о . h, используются
при запуске операционной системы. Структура aout (строка 532 1 ) содержит ад­
рес массива заголовков всех компонентов системного образа MINIX 3. Обратите
внимание на то, что эти адреса являются физическими, то есть адресами, отно­
сительными всего адресного пространства процессора. Как мы увидим позже,
физический адрес структуры aout передается от монитора загрузки ядру при за­
пуске MINIX 3, чтобы процедуры запуска ядра могли получить адреса всех ком­
понентов операционной системы из пространства памяти монитора. Структура
k i n f o (строка 5322) также несет в себе важную информацию. Вспомните, что
одноименная структура была определена в файле i n c l u de / rn i n i x / typ e . h.
Как только монитор загрузки передает ядру информацию обо всех процессах
в загрузочном образе при помощи структуры aou t, ядро заполняет поля струк­
туры k i n f o данными о себе, которые могут оказаться нужными другим систем­
ным компонентам.
Следующий раздел файла g l о . h содержит переменные, относящиеся к управле­
нию исполнением процессов и ядра. Переменные p rev__p t r, proc__p t r и next_
p t r указывают на записи таблицы процессов, соответствующие предыдущему,
текущему и следующему исполняемым процессам. Переменная Ь i l l__p t r ука­
зывает на процесс, ответственный за процессор. Когда пользовательский процесс
вызывает файловую систему и она выполняется, proc__p t r указывает на про­
цесс файловой системы. Вместе с тем Ь i l l__p t r будет указывать на пользова­
тельский процесс, и время, которое работала файловая цrстема, учитывается во
времени работы пользовательского процесса. Нам не известны случаи, когда владе­
лец системы MINIX взимает с пользователей плату за израсходованное процес-
2 . 6 . Реализаци я п роцессов в M I N IX 3 1 77

сорное время, однако это можно сделать. Следующая переменная, k_reent l e r,


требуется для подсчета вложенных запусков кода ядра. Вложенный запуск имеет
место, например, если в момент прерывания процессором исполнялось ядро, а не
пользовательский процесс. Это важно, поскольку переключение контекста между
пользовательским процессором и ядром (а также наоборот) отличается от по­
вторного входа в ядро. По завершении процедуры обслуживания прерывания
необходимо определить, оставить управление ядру либо передать его пользова­
тельскому процессу. Переменная k_reent l e r также проверяется некоторыми
функциями, включающими и отключающими прерывания, например lock_
enqueue. Если такая функция выполняется, когда прерывания уже запрещены,
они не должны быть снова разрешены, если это является нежелательным. Нако­
нец, в данном разделе имеется счетчик потерянных тактовых сигналов. Вопросы
причины потери тактовых сигналов и реакции на нее рассматриваются при изу­
чении таймерного задания.
Последние переменные, определенные в файле g l o . h, бьши включены в него
потому, что доступ к ним необходим во всем коде ядра. Эти переменные объявле­
ны с ключевым словом ext e rn вместо EXTERN потому, что являются инициали­
зируемыми. Язык С поддерживает такую возможность. Использование макроса
EXTERN несовместимо с инициализацией в стиле С, поскольку инициализация
допустима лишь однократно.
Задания, выполняемые в пространстве ядра (в настоящий момент их две - тай­
мерное и системное), обладают собственными стеками внутри t_s t ack. При об­
работке прерывания ядро работает с отдельным стеком, который здесь не объяв­
лен, так как к нему обращается только код в языке ассемблера и глобального
доступа к этому стеку не требуется. Последний файл, включенный в kerne l . h
и, тем самым, используемый в любом варианте компиляции, - i p c . h ( стро­
ка 5400). Он определяет различные константы для взаимодействия между про­
цессами. Мы рассмотрим их позже, когда приступим к изучению содержащего
их файла, ke rne l /pro c . с .
Ядро содержит еще несколько заголовочных файлов, используемых широко, хо­
тя и не настолько, чтобы включать их в файл ke rne l . h. Первый из них, proc . h
(строка 5500), описывает таблицу процессов ядра. Состояние процесса полно­
стью описывается его данными, находящимися в памяти, и информацией в со­
ответствующей ему записи таблицы процессов. Последняя содержит данные
регистров процессора во время приостановки процесса. При возобновлении ис­
полнения процесса содержимое регистров восстанавливается. Именно таким
образом создается представление о �параллельном� выполнении и взаимодейст­
вии процессов, хотя, на самом деле, в любой момент времени процессор обраба­
тывает команды только одного процесса. Ядру необходимо затрачивать время на
сохранение и восстановление состояния процесса при каждом переключении кон­
текста, хотя, очевидно, что в это время работа процесса прерывается. По этой
причине структуры определяются так, чтобы обеспечить максимальную эффек­
тивность. Как отмечено при рассмотрении файла p r o c . h, многие процедуры,
написанные на языке ассемблера, имеют доступ к этим структурам, и в файле
1 78 Глава 2. Процессы

s cons t h заданы смещения полей в таблице процессов для использования


.

ассемблерным кодом. Таким образом, изменение определения в p r oc h может.

потребовать редактирования файла s cons t h. .

Перед тем как двигаться дальше, заметим, что из-за микроядерной структуры
MINIX 3 таблица процессов, которую мы будем рассматривать, имеет аналоги
в менеджере процессов и файловой системе. Последние используют таблицы, за­
писи которых характеризуют процессы, а содержащаяся в них информация отно­
сится к функциональности соответствующего системного компонента MINIX 3.
В совокупности все три таблицы эквивалентны таблице процессов операцион­
ной системы с монолитной структурой, однако обсуждая таблицу процессов здесь,
мы будем иметь в виду таблицу ядра. Остальные таблицы рассматриваются в со­
ответствующих главах.
Каждая запись в таблице процессов определена как структура p r o c (строки
55 1 6-5545). Каждый элемент таблицы процессов хранит регистры процесса,
указатель стека, состояние процесса, карту памяти, предельный размер стека,
идентификатор процесса, информацию о времени срабатывания таймера, сооб­
щениях и прочие сведения о процессе. Первая часть каждого элемента таблицы -
это структура s t ac k f rarne_s . Когда процесс переходит в состояние выполне­
ния, его указатель стека восстанавливается по адресу из записи в таблице про­
цессов, и все регистры процессора восстанавливаются из этой структуры.
Тем не менее состояние процесса - это больше, чем регистры процессора и дан­
ные памяти. В MINIX 3 каждый процесс имеет указатель на структуру привиле­
гий в своей записи в таблице процессов (строка 5522). Эта структура определяет
допустимых отправителей и получателей сообщений для данного процесса, а так­
же множество привилегий. Мы рассмотрим детали позднее, а сейчас просто
обратите внимание на следующее: каждый системный процесс содержит указа­
тель на уникальную копию этой структуры, однако привилегии пользователей
одинаковы, поскольку указатели пользовательских процессов ссылаются на од­
ну и ту же копию. Кроме того, имеется поле размером 1 байт, содержащее набор
битовых флагов, p_rt s_f l ags (строка 5523). Назначения битов описаны далее.
Установка любого бита в 1 означает, что процесс не может быть запущен; таким
образом, нулевое значение поля указывает на готовность процесса.
Каждая запись в таблице процессов предоставляет место для информации, ко­
торая может быть необходима ядру. Например, поле p_rnax_p r i o r i ty ( стро­
ка 5526) указывает, в какую очередь следует поместить процесс, когда он будет
готов к выполнению в первый раз. Поскольку приоритет процесса может быть
понижен при условии, что он препятствует выполнению других процессов, имеет­
ся поле p_p r i o r i ty, значение которого изначально совпадает с полем p_rnax_
p r i o r i ty. Поле p_p r i o r i ty определяет очередь, в которую процесс помеща­
ется при готовности.
Время, затраченное процессом, регистрируется в двух переменных c l o ck_t (стро­
ки 5532 и 5533). Ядро должно иметь доступ к этой информации, поэтому ее хра­
нение в пространстве памяти процесса было бы неэффективно, хотя и возможно
2 . 6 . Р еал и з а ци я п роцессов в M I NIX 3 1 79

логически. Структура p_next ready (строка 5535) используется для связыва­


ния процессов в очередях планировщика.
Следующие несколько полей содержат информацию, связанную с сообщениями,
которыми обмениваются процессы. Если процесс не может завершить вызов
s end потому, что получатель не находится в состоянии ожидания, отправитель
помещается в очередь, на которую ссылается указатель p_c a l l e r_q получате­
ля (строка 5536). Таким образом, когда получатель наконец выполняет вызов
re c e i v e , легко определить все процессы, желающие передать ему сообщение.
Поле p_q__l i nk (строка 5537) используется для связывания процессов, находя­
щихся в очереди.
Применение для передачи сообщений метода рандеву становится возможным
благодаря резервированию памяти для хранения в строках 5538-5540. Если про­
цесс выполняет вызов r e c e i ve, но не обнаруживает входящего сообщения, он
блокируется, а число процессов, от которых он желает получить сообщения, хра­
нится в переменной p_ge t f rorn. Аналогично, переменная p_s endt o содержит
число получателей, не ожидающих сообщения, когда процесс выполняет вызов
send. Адрес буфера сообщений хранится в переменной p_rne s sbu f . Предпослед­
ним полем в каждом элементе таблицы процессов является p_pending ( стро­
ка 5542) - битовая карта, служащая для наблюдения за сигналами, не передан­
ными менеджеру процессов из-за того, что он не ожидает сообщения.
Последнее поле элемента таблицы процессов представляет собой массив симво­
лов p_narne и содержит имя процесса. Это поле не является обязательным для
управления процессом со стороны ядра. MINIX 3 поддерживает разнообразные
отладочные дампьt, генерируемые по нажатию специальной клавиши на кла­
виатуре консоли. Некоторые дампы содержат информацию обо всех процессах,
включая их имена. Назначение процессам осмысленных имен упрощает понима­
ние и отладку ядра.
За определением элемента таблицы процессов следуют определения констант,
используемых в его полях. В строках 5548-5555 приведены и описаны значения,
задающие различные комбинации битовых флагов в p_r t s_f l ag s. Если элемент
не используется, устанавливается значение SLOT_FREE. После вызова fork уста­
навливается значение NО_МАР, препятствующее запуску дочернего процесса до
установки карты памяти. Значения S ENDING и RECEIVING указывают на то, что
процесс заблокирован при попытке передать или принять сообщение. Значения
S I GNALED и S I G_PEND ING устанавливаются, когда сигналы приняты, а P_STOP
обеспечивает поддержку трассировки. Значение NO_PRIV служит для задержки
запуска нового системного процесса до момента его окончательной установки.
Далее (строки 5562-5567) определено число очередей и допустимые значения
поля p__p r i o r i ty. В данной версии файла допускается назначение процессам
максимального приоритета; вероятно, это пережиток времен тестирования рабо­
ты драйверов в пользовательском пространстве, и константе МAX_USER_Q следу­
ет присвоить большее значение (соответствующее меньшему приоритету).
Затем следует несколько макросов, позволяющих определять адреса важных эле­
ментов таблицы процессов как константы во время компиляции - это ускоряет
1 80 Глава 2. П роцессы

доступ к ним при выполнении. Следующие макросы предназначены для вычисле­


ний и проверок на этапе исполнения. Макрос proc_addr (строка 5577) необходим
потому, что в С не поддерживаются отрицательные индексы. С логической точки
зрения, элементы массива proc должны нумероваться от -NR_TASKS до +NR_
PROC S. К сожалению, язык С позволяет нумеровать элементы лишь с нуля, поэто­
му proc [ О ] указывает на самую �отрицательную� задачу. Чтобы упростить со­
поставление процессов и элементов таблицы процессов, мы используем команду
rp = pro c_addr ( n ) ;

Она присваивает переменной rp адрес элемента, соответствующего процессу n,


где n может иметь как положительное, так и отрицательное значения.
Сама таблица процессов определена как массив структур proc, proc [ NR_TASKS
+ NR_PROCS ] (строка 5593). Обратите внимание на то, что величина NR_TASKS
определена в файле inc l ude /rninix/ corn . h (строка 3630), а NR_PROC S - в файле
inc lude /rninix / c on f i g . h (строка 2522). Сумма этих значений задет размер
таблицы процессов. При необходимости NR_PROC S можно изменить, чтобы сис­
тема (к примеру, большой сервер) могла обрабатывать большее число процессов.
Наконец, несколько макросов предназначены для ускорения доступа к таблице
процессов. Обращения к таблице процессов осуществляются часто, а вычисле­
ние адреса в массиве требует выполнения медленной операции умножения. П о
этой причине введен массив указателей на элементы таблицы процессов pproc_
addr (строка 5594). Два массива, rdy_head и rdy_t a i l , поддерживают очере­
ди процессов. Например, указатель rdy_he ad [ US ER_Q ] ссылается на первый
процесс в очереди пользовательских процессов, предлагаемых по умолчанию.
Как мы упомянули в начале рассмотрения файла proc . h, имеется файл s const . h
(строка 5600), который необходимо приводить в соответствие с proc . h при вне­
сении изменений в структуру таблицы процессов. Файл s c ons t . h содержит оп­
ределения констант, используемых ассемблерным кодом, в форме, доступной ас­
семблеру. Все константы задают смещения внутри структуры s t ac k f rarne_s ,
составляющей часть записи таблицы процессов. Поскольку ассемблерный код не
обрабатывается компилятором С, подобные определения лучше вынести в от­
дельный файл. Кроме того, эти определения являются машинно-зависимыми,
а, значит, их обособление упрощает перенос MINIX 3 в систему с другим процес­
сором (требуется создать новую версию файла s c on s t . h ) . Обратите внимание
на то, что многие смещения определены как сумма предшествующего смещения
и w - величины, равной длине слова (строка 560 1 ). Это позволяет компилиро­
вать 16- и 32-разрядные версии MINIX 3, используя один и тот же файл.
Потенциальным источником проблем могут стать повторяющиеся определения.
Изначально предполагалось, что заголовочные файлы будут содержать единст­
венный набор определений, который будет использоваться различными фрагмен­
тами системы без скрупулезного вникания в детали. Очевидно, что повторяю­
щиеся определения, присутствующие в файлах proc . h и s c on s t . h, нарушают
этот принцип. Разумеется, здесь мы имеем дело с исключением, однако, изменяя
один файл, мы должны тщательно проверить другой, чтобы обеспечить их вза­
имную непротиворечивость.
2 . 6 . Реализация процессов в M I NIX З 1 81

Структура системных привилегий, кратко упомянутая при обсуждении таблицы


процессов, полностью определена в файле p r i v . h (строки 5 7 1 8-5735). Она со­
держит набор битовых флагов s_f l ag s , а также поля s_t rap_rnas k, s_ipc_
f rorn, s_ipc_t o и s_c a l l_rnas k, определяющие, какие системные вызовы могут
быть инициированы, какие сообщения могут быть приняты и переданы, какие
вызовы ядра разрешены.
Структура привилегий не является частью таблицы процессов, но каждый эле­
мент таблицы процессов содержит указатель на ее экземпляр. Различные копии
имеются лишь у системных процессов; все пользовательские процессы задейст­
вуют один и тот же экземпляр. Таким образом, остальные поля структуры для
пользовательского процесса не играют роли, поскольку их совместное использо­
вание не имеет смысла. Эти поля содержат битовые карты активных уведомле­
ний и аппаратных прерываний, а также сигналы и таймер. Разумеется, эти поля
имеет смысл указывать для системных процессов; управление уведомлениями,
сигналами и таймерами пользовательских процессов осуществляется от имени
последних менеджером процессов.
Файлы p r i v . h и proc . h организованы по схожему принципу. За определением
структуры привилегий следуют определения битовых флагов, несколько важных
адресов, необходимых на этапе компиляции, а также макросы для вычисления
адресов во время выполнения. Далее определяется таблица структур привиле­
гий, p r i v [ NR_SYS_PROC S ] , а затем - массив указателей ppri v_addr [ NR_
SYS_PROC S ] (строки 5762 и 5763). Массив указателей обеспечивает быстрый
доступ к структуре. О значении константы STACK_GUARD (строка 5738) нетруд­
но догадаться; мы используем ее позднее. Мы предлагаем читателю поискать
в Интернете информацию об истории появления этой константы.
Оставшаяся часть файла p r i v . h - это проверка, является ли значение NR_
SYS_PROC S большим, чем число процессов в загрузочном образе. Если это так,
будет выведено сообщение об ошибке, указанное в строке # e rror. После этого
стандартный компилятор MINIX 3 прекратит компиляцию, хотя поведение дру­
гих компиляторов С может быть иным.
Клавиша F4 вызывает генерацию отладочного дампа, содержащего некоторую
информацию из таблицы привилегий. В листинге 2 . 1 3 показаны несколько ее
строк, соответствующих �представительным� процессам. Для флагов использо­
ваны следующие обозначения: Р - выгружаемый, в - с учетом времени, s -
системный. Ловушки обозначены так: Е - echo, s - send, R - receive, в - оба,
N - уведомление. Битовая карта содержит биты для каждого из разрешенных
системных процессов NR_S YS_PROC S (32) в порядке согласно идентификатору
(в листинге представлены только 1 6 бит). Все пользовательские процессы имеют
идентификатор О (крайний левый столбец). Согласно битовой карте, пользова­
тельские процессы, такие как i ni t , могут передавать сообщения только менед­
жеру процессов, файловой системе и серверу реинкарнации и, кроме того, долж­
ны задействовать вызов s endr e c . Серверы и драйверы, указанные в листинге,
могут пользоваться любыми примитивами взаимодействия между процессами,
а все, кроме rnernory, имеют возможность передачи сообщений любым процессам.
1 82 Глава 2. Процессы

Фрагмент отладочного дампа таблицы привилегий. Таймерное задание,


Листинг 2 . 1 3 .
файловая система, процессы tty и init имеют привилегии, типичные для заданий,
серверов, драйверов устройств и пользовательских процессов соответственно.
Битовая карта сокращена до 16 бит
- -nr- - id- - name - - f l ag s - - t raps - - ipc_t o ma s k - - - - -
( -4 ) (01) IDLE P-BS - 00000000 00001111
[ -3 ] ( 02 ) CLOCK ---s- - -R-- 00000000 00001111
[ -2 ] ( 03 ) SYSTEM ---s- --R-- 00000000 00001111
[-1 ] ( 04 ) KERNEL ---s- 00000000 00001111
о (05) pm P--S- ES RBN 11111111 11111111
1 (06) fs P--S- ES RBN 11111111 11111111
2 ( 07 ) rs P--S- ES RBN 11111111 11111111
3 (09) memo ry P--S- ES RBN 00110111 01101111
4 (10) log P--S- ES RBN 11111111 11111111
5 ( 08 ) t ty P--S- ES RBN 11111111 11111111
6 (11) driver P--S- ES RBN 11111111 11111111
7 (00) init Р-В-- Е--В- 00000111 00000000

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


шое число различных файлов с исходными кодами, prot e c t . h (строка 5800).
Почти все, что есть в этом файле, относится к архитектуре процессоров Intel с под­
держкой защищенного режима (это процессоры 80286, 80386, 80486 и семейство
Pentium). Детальное рассмотрение процессоров Intel не входит в круг задач этой
книги. Достаточно только сказать, что у них есть ряд внутренних регистров, ука­
зывающих на таблицу дескрипторов в памяти. Информация из таблицы деск­
рипторов позволяет узнать, как используются ресурсы системы, предотвращать
доступ процесса к памяти, принадлежащей другому процессу и др.
Архитектура 32-разрядных процессоров Intel предлагает четыре уровня приви ­
легий, из которых в MINIX 3 поддерживаются три. Они определены в строках
5843-5845. Центральная часть ядра, то есть код, относящийся к прерываниям
и переключению контекста, работает с полномочиями уровня INTR_PRIVI L EGE.
Процесс с таким уровнем доступа вправе обращаться к любым регистрам и лю­
бым ячейкам памяти. Задания работают с уровнем привилегий TASK_PRIVI LEGE,
который позволяет им обращаться к устройствам ввода-вывода, но не дает изме­
нять значения некоторых специальных регистров, например указатели на табли­
цы дескрипторов. Серверы и пользовательские процессы выполняются на уров­
не US ER_PRIVI LEGE. Этот уровень запрещает процессам выполнять некоторые
инструкции, например инструкции ввода-вывода, управления распределением
памяти и смены привилегий.
Для тех, кто изучал современные процессоры, концепция нескольких уровней
защиты по привилегиям наверняка понятна, но те, кто знакомился с архитекту­
рой компьютеров по маломощным микропроцессорам, могли не сталкиваться
с подобными ограничениями.
В каталоге ke rne l / имеется один заголовочный файл, который мы еще не рас­
сматривали sy s t ern . h. Мы и сейчас отложим знакомство с ним до изучения
-

системного задания (далее в этой главе), выполняющегося как независимый про­


цесс, хотя и внутри ядра. Пока же будем считать изучение заголовочных файлов
оконченным и перейдем к рассмотрению файлов с расширением .с, содержащих
2 . 6 . Реал изация процессов в M I N IX 3 1 83

исходный код на языке С. Первый из них называется tаЫе . с (строка 6000).


Его компиляция не приводит к генерации исполняемого кода, однако объектный
файл t аЫе . о содержит все структуры данных ядра. Определения многих из этих
структур мы встречали в файле g l о . h и других заголовочных файлах. В строке
6028 перед директивами # inc l ude определен макрос _ТАВLЕ. Как было отмече­
но ранее, это определение делает EXTERN пустой строкой и выделяет память для
всех данных, предшествующих EXTERN.
Помимо переменных, объявленных в заголовочных файлах, имеются два других
хранилища глобальных данных. Некоторые определения заданы прямо в файле
t аЫ е . с. В строках 6037-604 1 определен стек, необходимый компонентам ядра,
а общий стек для заданий зарезервирован как массив t_s t a c k [ TOT_STACK_
S PAC E ] (строка 6045).
Остальная часть файла t аЫе . с содержит определения большого числа констант,
относящихся к свойствам процессов, например, комбинаций битовых флагов,
ловушек вызовов и масок, задающих допустимых адресатов сообщений и уве­
домлений (строки 6048-607 1 ), которых мы видели в листинге 2 . 1 3 . Далее следу­
ют маски, определяющие разрешенные вызовы ядра для различных процессов.
Менеджеру процессов и файловой системе разрешены все вызовы, серверу реин­
карнации - тоже, но не для собственного использования, а потому, что он явля­
ется родителем всех остальных системных процессов, а его потомки могут насле­
довать лишь подмножества его привилегий. Драйверам назначены одинаковые
маски системных вызовов; исключение составляет лишь драйвер виртуального
диска, которому требуется особый доступ к памяти (в комментарии к строке
6075 упоминается термин •менеджер системных служб» ; это следует читать как
•сервер реинкарнации» - в процессе разработки имя компонента было измене­
но, но некоторые устаревшие комментарии сохранились).
Наконец, в строках 6095- 6 1 09 определена таблица irnage. Она помещена в файл
t аЫ е . с , а не в заголовочный файл потому, что метод предотвращения множе­
ственных объявлений с помощью макроса EXTERN неприменим к инициали­
зируемым переменным. Другими словами, вы не можете где угодно написать
extern i n t х = 3;

Таблица irnage содержит детальную информацию, необходимую для инициа­


лизации всех процессов, загружаемых из загрузочного образа. Эта информация
используется системой при запуске. Например, поле, •озаглавленное» в коммен­
тарии как qs (строка 6096), определяет размер кванта для каждого процесса.
Обычные пользовательские процессы, как потомки ini t, получают в свое рас­
поряжение 8 тактов таймера, а задания C LOCK и SYSTEM - по 64 такта. Считает­
ся, что последние обычно блокируются до истечения своего кванта, однако в от­
личие от пользовательских серверов и драйверов, системные задания не могут
быть перемещены в очередь с более низким приоритетом, если они препятству­
ют выполнению других процессов.
Если в загрузочный образ необходимо включить новый процесс, в таблицу irnage
следует ввести дополнительную строку. Несоответствие размера irnage значениям
1 84 Глава 2 . П роцессы

других констант недопустимо. В конце файла t аЫ е . с размещены команды


проверки ошибок, причем массив dummy объявлен здесь дважды. В каждом объ­
явлении размер массива недопустим и приводит к ошибке компилятора. По­
скольку массив dummy объявлен как ext e rn, память под него не выделяется ни
в этом, ни в других файлах. Более того, этот массив больше нигде не фигурирует,
а следовательно, не беспокоит компилятор.
В конце ассемблерного файла mрхЗ 8 6 . s выделяется дополнительная глобаль­
ная память. Несмотря на то что мы �перепрыгнули� на несколько страниц кода
вперед, это целесообразно, так как знакомство с глобальными переменными сей­
час весьма актуально. В строке 6822 директива ассемблера . s e c t . rorn записы­
вает сиzнатуру, идентифицирующую работоспособное ядро MINIX 3, в нача­
ло сегмента данных ядра. Директива . s e c t b s s и псевдокоманда . s p a c e ре­
зервируют место под стек ядра. Псевдокоманда . c omm именует несколько слов
вершины стека, чтобы дать возможность прямого обращения к ним. Мы вер­
немся к файлу mрхЗ 8 6 . s немного позже, завершив изучение начальной за­
грузки MINIX 3.

2 . 6 . 6 . Н ачал ьная загрузка M I N IX 3


Уже почти настало время перейти к рассмотрению исполняемого кода, но пе­
ред тем как мы им займемся, попытаемся разобраться, как MINIX 3 загружается
в память. Загрузка, конечно же, производится с диска, однако эту процедуру
нельзя назвать элементарной, а последовательность действий зависит от типа
диска. На рис. 2. 1 7 показано, как устроены дискеты и жесткие диски, разбитые
на разделы.

а б
Дисковые структуры, используемые при начальной загрузке:
Рис. 2 . 1 7 . а -диск
без разбиения на разделы, первый сектор является загрузочным блоком; б диск,
-

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


2 . 6 . Реализация процессов в M I N IX 3 1 85

При старте системы аппаратное обеспечение (а в действительности программа


из ПЗУ) считывает первый сектор загрузочного диска и исполняет считанный
код. На дискете, не разбитой на разделы, первый сектор содержит загрузоч­
ный блок, который, в свою очередь, загружает программу boot , как показано на
рис. 2. 17, а. В отличие от дискет жесткие диски разбиты на разделы, и в первом
секторе находится программа, которая считывает таблицу разделов (из того же
первого сектора), а затем загружает и исполняет программу из первого сектора
активного раздела, как это показано на рис. 2. 17, б (один и только один сектор
должен быть помечен как активный). Раздел, из которого загружается MINIX 3,
имеет такую же структуру, как и загрузочная дискета MINIX 3, и в первом сек­
торе находится загрузочный блок.
Реальная ситуация может быть сложнее, чем показано на рисунке, так как от­
дельные разделы могут быть разбиты на подразделы. В этом случае в первом
секторе раздела будет находиться еще одна загрузочная запись со своей табли­
цей подразделов. Как бы то ни было, в конце концов, управление получит про­
грамма из загрузочного сектора на устройстве, которое разделено на разделы. На
дискетах, например, загрузочным сектором всегда является первый. MINIX 3
допускает разбиение дискеты на разделы, но в этом случае для загрузки можно
использовать только первый раздел, остальные недоступны. Такое поведение уп­
рощает монтирование дискет, так как и обычные дискеты, и поделенные на раз­
делы монтируются одинаково. Разбиение на разделы применяется, например, на
установочных дискетах, чтобы разделить образ, копируемый в ОЗУ, и монтируе­
мый раздел, который при необходимости может быть размонтирован, с целью
освобождения прJrвода для продолжения процесса установки.
Изменение загрузочного сектора MINIX 3 осуществляется при его записи на диск
специальной программой ins t a l l bo o t . Она вписывает в загрузочный сектор
адрес файла с именем boot. В MINIX 3 стандартным местоположением програм­
мы boot является каталог / boot / , однако он может находиться где угодно - до
загрузки операционной системы невозможно найти местоположение нужного
файла по его имени. Именно поэтому в загрузочный сектор записывается адрес
файла, который будет использован для дальнейшей загрузки.
Программа boot является вторичным загрузчиком MINIX 3. Она не только за­
гружает саму операционную систему, но и сама является монитором заzрузки,
позволяя пользователю устанавливать, менять и сохранять различные парамет­
ры загрузки. Их boot ищет во втором секторе загрузочного раздела. Как требу­
ют стандарты UNIX, MINIX 3 резервирует первый килобайт каждого диска под
заzрузочный блок, но под загрузчик используется только 5 12 байт, поэтому еще
5 1 2 байт остается для сохранения параметров. Эти параметры управляют про­
цессом загрузки, кроме того, они передаются самой операционной системе. На­
стройка по умолчанию предлагает пользователю только один вариант - загрузку
MINIX 3, но можно сделать и более сложное меню, при помощи которого поль­
зователь сможет загрузить, например, другие операционные системы (передав
управление загрузочному блоку на другом разделе) или же загрузить MINIX 3,
но с другими параметрами. Кроме того, можно настроить загрузчик так, чтобы
он пропускал меню и немедленно начинал загрузку MINIX 3.
1 86 Глава 2. Процессы

Программа boot не является частью операционной системы, но способна оты­


скать на диске образ операционной системы, опираясь на структуры файловой
системы. По умолчанию ищется файл / bo o t / irnage, в противном случае -
файл, указанный в параметре загрузки irnage =. Если задано имя файла, то он
подлежит загрузке; если же указано имя каталога, загружается самый новый со­
держащийся в нем файл. Во многих операционных системах имя файла загру­
зочного образа жестко определено, однако пользователи MINIX 3 могут созда­
вать собственные образы и присваивать им другие имена. Выбор из нескольких
версий удобен, так как в случае неудачного эксперимента есть возможность вер­
нуться к более раннему варианту.
Ограниченный объем книги не позволяет нам углубиться в детальное изучение
монитора загрузки. Это сложная программа, почти миниатюрная операционная
система. Она функционирует совместно с MINIX 3, и когда последняя 1<0рректно
завершает работу, управление передается монитору загрузки. Ссылка на подроб­
ное описание исходного кода монитора загрузки имеется на веб-сайте MINIX 3.
Заzрузочный, или системный, образ операционной системы MINIX 3 представля­
ет собой совокупность файлов ряда программ: ядра, менеджера процессов, фай­
ловой системы, сервера реинкарнации, нескольких драйверов устройств и про­
граммы i n i t, как показано в табл. 2.3. Загрузочный образ версии MINIX 3,
описываемый в этой книге, включает единственный дисковый драйвер, хотя до­
пускается наличие нескольких драйверов, один из которых активный. Как и все
двоичные программы, файлы загрузочного образа включают заголовок, содержа­
щий объем памяти, которая резервируется после загрузки под неинициализиро­
ванные данные и стек. Это позволяет правильно определять адрес, по которому
будет загружена следующая программа.
Области памяти, в которые можно загружать монитор загрузки и компоненты
MINIX 3, зависят от аппаратного обеспечения. В некоторых архитектурах требу­
ется выравниващ�:е внутренних адресов исходного кода согласно фактическому
адресу, по которому загружена программа. Сегментная организация памяти про­
цессоров Intel избавляет от такой необходимости.
Детали загрузки зависят от типа компьютера. Важным является то, что в любом
случае операционная система так или иначе оказывается в памяти. Перед тем
как MINIX 3 сможет начать работу, требуется выполнить несколько подготови­
тельных действий. Сначала при загрузке образа программа boot считывает из
него несколько байтов, содержащих свойства образа. Главное из этих свойств -
разрядность, для которой скомпилирован образ - 1 6 или 32. Затем ядру предо­
ставляется дополнительная информация, необходимая для запуска системы. За­
головки компонентов а . аи t извлекаются в массив внутри пространства памяти
программы boo t , а базовый адрес массива передается ядру. По завершении
работы MINIX 3 может вернуть управление монитору загрузки, поэтому место,
с которого выполнение должно возобновиться, также подлежит передаче. Как
мы увидим позже, все эти параметры помещаются в стек.
Монитор загрузки должен передать операционной системе еще несколько важных
сведений - параметров загрузки. Некоторые из них необходимы ядру, другие
2 . 6 . Реал изация процессов в M I N IX 3 1 87

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


Параметры загрузки можно представить в виде пар имя значение, а адрес =

таблицы, содержащей такие пары, размещается в стеке. В листинге 2 . 1 4 показан


типичный набор параметров загрузки, выведенный командой sys env операци­
онной системы M INIX 3.
Листинг 2 . 1 4 . Параметры загрузки, передаваемые ядру в типичной системе MINIX З
r o o t dev= 9 0 4
ramimagedev= 9 0 4
rams i z e = O
proc e s s o r = 6 8 6
bu s = a t
video =vga
chrome = c o l o r
memory = 8 0 0 : 9 2 5 4 0 , 1 0 0 0 0 0 : 3 DF O O O O
l ab e l =AT
c o n t ro l l e r = c O
ima g e = b o o t / image

В этом примере особую важность представляет параметр memory, с которым мы


вскоре столкнемся. Здесь он указывает, что монитор загрузки определил в памя­
ти два сегмента, доступных для использования MINIX 3. Один из них начина­
ется по шестнадцатеричному адресу 800 (2048 в десятичной системе счисления)
и имеет размер Ох92540 байт (599 360 в десятичной системе). Второй сегмент
начинается по адресу 100000 ( 1 048 576), а его длина составляет Ox3DFOOOOO
(64 946 176) байт. Такая конфигурация типична для всех РС-совместимых ком­
пьютеров, за исключением самых старых. В архитектуре IBM РС память только
для чтения размещается в начале доступного диапазона адресов - для процессо­
ра 8088 последний ограничен значением 1 Мбайт. Современные РС-совмести­
мые компьютеры оснащены значительно большим объемом памяти, однако для
совместимости они имеют такую же область памяти для чтения, как и их пред­
шественники. В результате память, доступная и для чтения, и для записи, разде­
лена на две части, одна из которых находится ниже 640 Кбайт, а другая - выше
1 Мбайт. По возможности монитор загрузки помещает ядро в нижнюю область,
а серверы, драйверы и процесс i ni t - в верхнюю. В основном это делается для
удобства файловой системы, поскольку дает возможность определять кэш бло­
ков большего объема, не разделяемый памятью для чтения надвое.
Обратим ваше внимание на то, что операционные системы не всегда загружают­
ся с диска. Бездисковые рабочие станции могут загружаться с удаленной системы
через сетевое соединение. Конечно, это требует наличия в ПЗУ программного
обеспечения для работы с сетью. В таком случае процесс загрузки будет аналоги­
чен описанному, хотя детали могут быть другими. Программа в ПЗУ должна
уметь получить через сеть исполняемый код, который, в свою очередь, загрузил
бы всю систему. При таком способе загрузки MINIX 3 процесс инициализации,
начинающийся сразу после размещения системы в памяти, изменился бы очень
незначительно: потребовался бы сетевой сервер и модифицированная файловая
система, способная обращаться к файлам по сети.
1 88 Глава 2. Процессы

2 . 6 . 7 . Инициал изация системы


Ранние версии операционной системы MINIX могли компилироваться в 16-разряд­
ном режиме для совместимости со старыми процессорами, и в MINIX 3 сохране­
на часть кода для 16-разрядных процессоров. Однако версия, описанная в книге
и записанная на компакт-диск, совместима только с 32-разрядными компьютера­
ми, оснащенными процессором не ниже 80386. Она не работает в 16-разрядном
режиме, а в случае создания 1 6-разрядной версии некоторые возможности опе­
рационной системы могут оказаться недоступными. Кроме того, 32-разрядные
двоичные файлы имеют больший объем, чем 1 6-разрядные, и независимые поль­
зовательские драйверы не могут совместно использовать код, как при компиля­
ции драйверов в одну двоичную библиотеку. Тем не менее задействуется общая
база исходного кода на С, и компилятор генерирует надлежащие выходные дан­
ные в зависимости от собственной разрядности. Компилятор самостоятельно оп­
ределяет макрос _WORD_S I Z E из файла inc lude /rninix/ con f i g . h.
Первая часть исполняемого кода MINIX написана на языке ассемблера, поэтому
для 16- и 32-разрядных версий компилятора должен использоваться разный код.
В частности, 32-разрядная версия начального кода системы находится в файле
rnpx3 8 6 . s. Альтернативная версия для 1 6-разрядных систем - в файле rnpx8 8 . s .
Обе эти версии поддерживают различные низкоуровневые операции для ядра.
Выбор того, какой из файлов будет использоваться, производится автоматиче­
ски в файле rnpx . s. Этот файл состоит всего из нескольких строк кода и цели­
ком приведен в листинге 2. 1 5.
Л истинг 2 . 1 5 . Выбор одной из двух ал ьте рнати в н ых версий ассемблер ного кода
# inc lude <minix / c o n f i g . h>
# i f _WORD_S I Z E == 2
# inc lude " mpx3 8 6 . s "
#else
# inc lude " mpx 8 8 . s "
# end i f

Здесь показано необычное использование директивы препроцессора # inc lude.


Обычно эта директива применяется для подсоединения заголовочных файлов,
но, как показано на листинге, годится и для выбора одной из альтернативных
секций исходного кода. Оперируя лишь директивами # i f , весь код, как для 32 -
разрядной, так и для 16-разрядной версий, пришлось бы поместить в один файл.
Это не только громоздко, но и приводит к расточению дискового пространства,
так как в конкретных установочных пакетах может потребоваться только одна
из версий, в то время как вторая могла бы быть помещена в архив или удалена.
В последующем обсуждении мы будем рассматривать в качестве примера только
32-разрядную версию (rnpx3 8 6 . s ) .
Так мы приступаем к рассмотрению исполняемого кода впервые, начнем с того,
что скажем несколько слов о порядке подобного рассмотрения в оставшейся части
книги. Большие программы на языке С состоят из множества файлов, которые
трудно выстроить в какой-то последовательности. Как правило, мы будем рас­
сматривать файлы поочередно. Порядок размещения файлов на компакт-диске
2 . 6 . Реализация процессов в M I N IX З 1 89

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


каждого из компонентов M INIX 3 и, как правило, двигаться в порядке испол­
нения кода. Когда нам будет встречаться вызов вспомогательной функции, мы
скажем о ней несколько слов, но подробное разбирательство отложим до того
времени, когда начнем знакомство с соответствующим файлом. Важнейшие вспо­
могательные функции обычно объявляются в том же файле, в котором они
используются, но небольшие или многоцелевые функции чаще сгруппированы
в отдельных файлах. Мы не пытаемся описать внутреннее устройство каждой
функции, поэтому файлы, содержащие подобные функции, на компакт-диске мо­
гут отсутствовать.
Кроме того, чтобы упростить перенос системы на другие платформы, машинно­
зависимый и машинно-независимый фрагменты кода зачастую помещены в от­
дельные файлы. Чтобы сократить размер листингов и сделать исходный код бо­
лее понятным, большая часть условного кода для платформ, отличных от 32-раз­
рядных систем Intel, в компакт-диск не включена. Полные версии всех файлов
находятся на веб-сайте MINIX 3.
Немало усилий было затрачено на то, чтобы сделать код понятным. Тем не ме­
нее в больших программах всегда имеется множество ответвлений, и зачастую,
чтобы понять работу основной программы, требуется рассмотреть те функции,
которые она вызывает. По этой причине вам может быть полезно сделать не­
сколько закладок и временами отклоняться от порядка повествования книги.
Изложив общий порядок обсуждения кода, обратим внимание на важное ис­
ключение. Процесс запуска MINIX 3 требует нескольких передач управления
между ассемблерными процедурами файла mрхЗ 8 6 s и С-функциями из фай­
.

лов s t art . с и rna i n . с, поэтому рассмотрение будет вестись в той последова­


тельности, в которой вызываются функции.
После того как процесс начальной загрузки помещает в память код операцион­
ной системы, управление передается по метке MINIX (файл mрхЗ 8 6 s ). Первая
.

инструкция выполняет переход вперед на несколько байтов, чтобы обойти об­


ласть, занимаемую флагами, которые монитор загрузки использует для получе­
ния информации о ядре (строка 6423). К этому моменту флаги уже выполняют
свои функции - они считываются монитором в процессе загрузки ядра в па­
мять. Флаги расположены так потому, что их адрес легко задать. Они позволяют
монитору загруз1ш определить различные характеристики ядра, самая главная
из которых - его разрядность ( 1 6 или 32 ) . Монитор загрузки всегда стартует
в 1 6-разрядном режиме, но при необходимости перед передачей управления на
метку MINIX переключается в 32-разрядный.
Чтобы понять следующий далее код, нужно разобраться с состоянием стека. Мо­
нитор передает MINIX 3 через стек ряд параметров. Сначала он размещает в стеке
адрес переменной aout , содержащей адрес массива с заголовочной информацией
компонентов загрузочного образа, затем - размер и адрес таблицы параметров
загрузки. Все указанные значения являются 32-разрядными. Далее следует адрес
сегмента кода монитора и адрес возврата после завершения работы MINIX 3.
Эти значения 1 6-разрядные, поскольку монитор функционирует в 1 6-разрядном
1 90 Гла ва 2 . П р оцессы

защищенном режиме. Первые несколько команд файла mрхЗ 8 6 . s преобразуют


1 6-разрядный указатель стека монитора в 32-разрядную величину для исполь­
зования в защищенном режиме. Затем следующая команда копирует указатель
стека в регистр еЬр (строка 6436):
mov еЬр , esp

Это позволяет со смещениями использовать регистр для извлечения из стека


значений, помещенных туда монитором (строки 6464-6467). Поскольку в про­
цессорах Intel рост стека происходит вниз, значение 8 ( еЬр ) указывает на вели­
чину, помещенную вслед за величиной 1 2 ( еЬр ) .
Значительная часть работы должна делаться ассемблерным кодом: настройка
кадра стека, чтобы создать необходимую среду для С-кода, копирование таблиц,
описывающих сегменты памяти, и установка различных регистров процессора.
Когда эта работа завершается, управление передается процессу инициализации
вызовом С-функции c s t art (строка 648 1 ). Обратите внимание на то, что в ас­
семблерном коде ссьтка на нее выглядит как _c s t ar t , поскольку компилятор
каждое имя функции в таблице символов предваряет знаком подчеркивания,
и компоновщику при компоновке различных файлов требуются именно такие
имена. Так как при использовании ассемблера никаких дополнительных знаков
к именам не приписывается, здесь к именам всех С-функций необходимо явно
добавлять знак подчеркивания, иначе компоновщик не сможет правильно ском­
поновать объектные файлы.
В свою очередь, функция c s t art вызывает функцию для инициализации гло­
бальной таблицы дескрипторов ( Global Descriptor ТаЫе, GDT) - центральной
структуры данных, используемой 32-разрядными процессорами Intel для защиты
памяти, и таблицы дескрипторов прерываний ( Interrupt Descriptor ТаЫе, IDT),
с помощью которой выбирается обработчик для каждого конкретного прерыва­
ния. После возврата из c s t a r t сформированные таблицы активизируются при
помощи команд l gdt и l i dt (строки 6487 и 6488), загружающих выделенные
регистры. Следующая команда на первый взгляд выглядит как пустая операция,
так как она передает управление точно в ту же точку, куда оно бы попало после
серии инструкций nop :
j mpbf C S_S ELECTOR : c s in i t

Но в действительности это - важный элемент инициализации, поскольку пере­


ход принуждает к использованию только что инициализированных структур
данных. Далее следует ряд манипуляций с регистрами процессора, после чего
MINIX переходит к строке 6503 (без вызова), являющейся точкой входа функции
rna i n ядра (в файле rnai n . с ) . К этому моменту код инициализации в mрх З 8 6 . s
завершает свою работу. Остальная часть ассемблерного кода ответственна за за­
пуск или перезапуск процессов и заданий, обработку прерываний и прочие дей­
ствия, которые для эффективности должны быть написаны на языке ассемблера.
Мы вернемся к этим процедурам в следующем разделе.
Теперь перейдем к рассмотрению высокоуровневых С-функций инициализации.
Общая стратегия такова, чтобы как можно больше использовать высокоуровневый
2 . 6 . Реализация процессов в M I N IX 3 1 91

код на языке С. Практически первое, что делается в функции с s t art (файл


s t ar t . с , строка 6920), вызывается процедура prot_ini t , инициализирующая
механизмы защиты памяти и таблицы прерываний. Затем на очереди выпол­
няются перенос параметров загрузки в область памяти ядра и их сканирование
с использованием функции get_value (строка 6997). Сканирование представ­
ляет собой определение имен параметров и возврат соответствующих численных
значений в виде строк. В файле s t art . с определяется тип дисплея, шины и про­
цессора, а в 1 6-разрядном режиме - режим работы процессора (реальный или
защищенный). Вся эта информация помещается в соответствующие глобальные
переменные, чтобы ее можно было при необходимости задействовать в других
частях ядра.
Функция ma i n (файл ma i n . с, строка 7 1 30) завершает инициализацию и пере­
водит систему в режим нормальной работы. Для этого сначала настраиваются
средства управления прерываниями с помощью функции i nt r_i n i t . Данный
вызов помещен здесь потому, что он зависит от аппаратного обеспечения и не
может быть сделан до того, как определен тип машины. Значение передаваемого
функции int r_ini t аргумента, равное 1 , указывает, что инициализация выпол­
няется для MINIX 3. Если передать О, параметры возвратятся в исходное состоя­
ние, когда операционная система завершит выполнение и передаст управление
монитору загрузки. Функция i nt r_i n i t гарантирует, что любые прерывания
до окончания инициализации игнорируются. О том, как этого добиться, мы по­
говорим позже.
Большая часть кода функции ma in служит для создания таблиц процессов и при­
вилегий, чтобы к началу планирования первых заданий и процессов их карты
памяти, регистры и данные о привилегиях были установлены правильно. Все
ячейки таблицы процессов помечаются как свободные, а массив pproc_addr,
введенный для ускорения доступа к таблице процессов, заполняется в цикле
(строки 7 1 50-7 1 5 4 ) . Цикл в строках 7 1 55-7 1 59 очищает таблицу привилегий
и массив pproc_addr, а также таблицу процессов и массив доступа к ней. При
записи значения в поле таблицы процессов или привилегий соответствующий
элемент помечается как неиспользуемый. Тем не менее любой элемент таблицы,
независимо от того, используется он или нет, должен быть инициализирован
номером индекса.
Строка 7 153:
( ppro c_addr + NR_TASKS ) [ i ] = rp ;

Поскольку в С запись а [ i ] в точности эквивалентна записи * ( а+ i ) , то есть нет


большой разницы, прибавлять смещение к адресу массива или к индексу, эту
строку можно записать проще:
pproc_addr [ i + NR_TASKS ] = rp ;

Однако некоторые компиляторы генерируют лучший код, если постоянное сме­


щение прибавляется к адресу массива.
Самый большой фрагмент кода функции ma i n в рамках длинного цикла (строки
7 172-7242) заполняет таблицу процессов информацией, необходимой для запуска
1 92 Глава 2. П роцессы

всех процессов загрузочного образа (обратите внимание, что в устаревшем коммен­


тарии к строке 7 1 6 1 упоминаются только задания и серверы). Все эти процессы
обязаны существовать к моменту запуска системы, и ни один из них при нор­
мальной работе не должен завершаться. В начале цикла в ip помещается адрес
элемента таблицы irnage (строка 7 1 73). Это - указатель на структуру, поэтому
к полям самой структуры можно обратиться с помощью записи вида iр - >имя_
поля, как в строке 7 1 74. Подобная запись широко используется в коде MINIX 3.
Аналогично, rp представляет собой указатель на элемент таблицы процессов,
а pr i v ( rp ) указывает на элемент таблицы привилегий. Значительная часть ко­
да инициализации таблиц процессов и привилегий в цикле включает чтение зна­
чения из таблицы irnage и сохранения его в таблице процессов или привилегий.
В строке 7 1 85 проверяется, является ли процесс частью ядра, и в случае, если
это так, в базе области стека задания сохраняется последовательность STACK_
GUARD. Позднее с ее помощью можно убедиться в том, что стек не переполнен.
Далее для каждого задания инициализируется указатель стека. Каждому зада­
нию необходим собственный указатель стека. Поскольку рост стека происходит
в сторону меньших адресов, начальное значение указателя рассчитывается как
сумма базового адреса стека и его размера (строки 7 1 90 и 7 1 9 1 ). Существует од­
но исключение: процесс KERNEL (в некоторых местах идентифицируемый как
HARDWARE ) никогда не считается готовым, никогда не выполняется как обычный
процесс, а следовательно, не нуждается в указателе стека.
Двоичные коды компонентов загрузочного ядра компилируются так же, как и
любые другие программы MINIX 3, а компилятор создает в начале каждого фай­
ла заголовок согласно определению в i nc l ude / а . ou t . h. Загрузчик копирует
заголовки в собственное пространство памяти до запуска операционной системы,
а когда монитор передает управление на метку MINIX файла mрх З 8 6 . s, физиче­
ский адрес заголовочной области передается ассемблерному коду в стеке, как мы
уже видели. В строке 7202 один из этих заголовков копируется в локальную
ехес-структуру, ehdr, где hdr i ndex используется в качестве индекса массива
заголовков. Затем адреса сегментов данных и кода преобразуются в базовые еди­
ницы и вводятся в карту памяти процесса (строки 7205-72 14).
Перед тем как продолжить, следует подробнее рассмотреть несколько моментов.
Во-первых, для процессов ядра значение hdr index всегда устанавливается равным
нулю (строка 7 1 78). Все процессы ядра компилируются в один файл с самим
ядром, а информация об их стеках находится в таблице irnage. Поскольку задание
ядра способно вызывать любой его код и использовать внутренние данные, объем
отдельного задания становится неважным. Таким образом, ядру и его заданиям
соответствует один и тот же элемент массива aou t , а поля, определяющие объе­
мы заданий, заполняются значениями размеров самого ядра. Задания получают
информацию о своих стеках из таблицы irnage, инициализируемой в процессе
компиляции файла t аЫ е . с . После того как все процессы ядра обработаны, зна­
чение переменной hdri ndex инкрементируется на каждой итерации цикла (стро­
ка 7 1 96), поэтому все системные процессы, выполняемые в пользовательском
пространстве, получают надлежащие данные из своих собственных заголовков.
2 . 6 . Реализация процессов в M I NIX 3 1 93

Еще один аспект: в функциях, осуществляющих копирование данных, не обяза­


тельно соблюден привычный порядок указания источника и приемника. Изучая
описанный цикл, постарайтесь не запутаться. Порядок указания аргументов
стандартной библиотечной С-функции s t rncpy таков, что приемник задается
первым:
s t rncpy ( кyдa , откуда , с ч е тчик )

Это аналогично операции присваивания, в левой части которой находится пе­


ременная, а в правой - выражение, значение которого записывается. Функция
s t rncpy копирует имена процессов в элементы таблицы процессов для отла­
дочных и других целей (строка 7 1 79 ) . Обратное преобразование выполняет
функция phy s_c opy:
hу s_с ору ( откуд а , куда , сче тчик )

Она используется в строке 7202 для копирования заголовков пользовательских


процессов.
Продолжая изчение инициализации таблицы процессов, в строках 7220 и 722 1
мы видим установку начальных значений счетчика команд и слова состояния
процессора. Слово состояния процессора для заданий отличается от драйверов
устройств и серверов, поскольку привилегии заданий выше, чтобы обеспечить
им доступ к портам ввода-вывода. Далее, если процесс является пользователь­
ским, выполняется инициализация указателя его стека.
Один из элементов таблицы процессов не должен и не может планироваться.
Процесс HARDWARE существует только для учетных целей: считается, что он вы­
полняется лишь во время обслуживания прерываний. Все остальные процессы
помещаются в соответствующие очереди кодом строк 7234 и 7235. Функция
l o c k_enqueue запрещает прерывания перед изменением очередей, а затем, по
завершении изменения, включает их. Разумеется, пока ни один из процессов
не запущен, этого не требуется, однако подобный метод является стандартным,
и писать другой код, чтобы воспользоваться им лишь единожды, нецелесообразно.
Последний этап инициализации элемента таблицы процессов - вызов функции
a l l oc_s egrnent s (строка 724 1 ) . Это машинно-зависимая процедура, размещаю­
щая в соответствующих полях местоположение, размеры и уровни разрешений
сегментов памяти процесса. Для более старых процессоров Intel, не поддержи­
вающих защищенный режим, определяется только положение сегмента. Если
систему нужно будет перенести на процессор, выделяющий память иначе, проце­
дура a l l o c_s egrnent s должна быть переписана.
После инициализации в таблице процессов элементов всех заданий, серверов
и процесса i n i t система почти готова к работе. В переменной Ьi l l_p t r хра­
нится ссылка на процесс, которому в текущий момент передается процессор. Эту
переменную необходимо инициализировать, для чего подходит ссылка на про­
цесс I DLE (строка 7250). После этого ядро готово к обычной работе - управле­
нию и планированию процессов, как показано на рис. 2.2.
Еще не все компоненты системы готовы к нормальному функционированию, одна­
ко они выполняются как независимые процессы, считаются готовыми и поставлены
1 94 Глава 2 . П роцессы

в очередь. Процессы инициализируют себя во время выполнения. Все, что оста­


лось сделать ядру, - вызвать функцию announce, чтобы уведомить всех о своей
готовности, а затем - функцию r e s t art (строки 725 1 и 7252). Во многих С-про­
граммах функция rna i n представляет собой цикл, однако в MINIX 3 ядро за­
вершается, как только заканчивается инициализация. Вызов функции r e s t ar t
в строке 7252 запускает первый процесс, стоящий в очереди. Управление больше
никогда не возвращается функции rna i n.
Функция _r e s t art представляет собой ассемблерную подпрограмму, код кото­
рой находится в файле mрхЗ 8 6 . s. Фактически _r e s t ar t не является самостоя­
тельной функцией. Это промежуточная точка входа в более сложную процеду­
ру. Подробно мы рассмотрим ее в следующем разделе, а пока скажем лишь, что
_re s tart вызывает переключение контекста и управление получает процесс, на
который указывает переменная proc_p t r . В первый раз _r e s t art запускает
процесс. Затем эта функция вызывается снова и снова, по мере того как пользова­
тельские процессы, задания и серверы по очереди получают и теряют управление,
либо приостанавливаясь в ожидании ввода, либо по истечении кванта времени.
Разумеется, когда _r e s t a r t запускается впервые, завершенной оказывается
только инициализация ядра. Вспомним, что в таблице процессов MINIX 3 име­
ется три части. Вы можете спросить: каким же образом можно начинать выпол­
нение процессов, когда основные компоненты таблицы процессов не установле­
ны? Исчерпывающий ответ на этот вопрос вы получите в следующих главах
книги. Пока для краткости можно привести такое объяснение: счетчики команд
всех процессов загрузочного образа изначально указывают на код инициализа­
ции процессов, поэтому все процессы вскоре блокируются. В конечном счете,
управление получают менеджер процессов и файловая система, которые запус­
кают коды инициализации и заполняют свои области в таблице процессов. Да­
лее управление получает процесс i n i t , который запускает несколько дочерних
процессов get ty, по одному на каждый терминал. Эти процессы до ввода какой­
либо информации на терминал также остаются заблокированными. После этого
первый пользователь получает возможность войти в систему.
Мы рассмотрели процесс запуска MINIX 3, управляемый кодом из трех файлов,
два из которых написаны на С, один - на ассемблере. В ассемблерном файле,
mрхЗ 8 6 . s, есть дополнительный код, связанный с обработкой прерываний, об­
суждение которого мы отложим до следующего раздела. А пока давайте завер­
шим тему инициализации системы кратким описанием оставшихся функций из
С-файлов. В файле s t art . с это - функция get_va lue (строка 6997), обнару­
живающая копии параметров загрузки в окружении ядра. Она представляет со­
бой упрощенную версию стандартной библиотечной функции, переписанную во
избежание излишнего загромождения ядра.
В файле rnain . с остались нерассмотренными три процедуры. Процедура announce
отображает сообщение об авторских правах и указывает режим работы MINIX 3 -
реальный, 16-разрядный или защищенный 32-разрядный. Пример:
MINIX 3 . 1 Copy r i ght 2 0 0 6 Vri j e Un ive r s i t e i t , Ams t e rdam , The Nethe r l ands
Execut ing in 3 2 - b i t pro t e c t ed mode
2 . 6 . Реализация процессов в M I N IX 3 1 95

Если вы получили такое сообщение, это означает, что инициализация ядра за­
вершена. Функция prepare_shut down (строка 7272) посылает всем процессам
сигнал S I GKSTOP (системным процессам нельзя посьшать сигналы так же, как
пользовательским), а затем устанавливает таймер, выделяя всем системным про­
цессам время на «уборку� до вызова последней процедуры, shut down. Процеду­
ра shut down, как правило, возвращает управление монитору загрузки MINIX 3.
Для этого к контроллерам прерываний применяются параметры, восстанавли­
ваемые из BIOS с помощью функции i nt r_i n i t ( 0 ) (строка 7338).

2 . 6 . 8 . Обработка прерыван и й в M I N IX
Особенности обработки прерываний во многом зависят от аппаратной платфор­
мы, но у любой системы должны быть элементы с подобной функционально­
стью. В 32-разрядных процессорах Intel прерывания генерируются аппаратным
обеспечением и в виде электрических сигналов передаются сначала на контрол­
лер прерываний. Это - интегральная схема, которая умеет различать несколько
подобных сигналов и для каждого из них генерировать уникальный идентифи­
катор на шине данных процессора. Сам же процессор имеет лишь один вход для
всех устройств, а следовательно, не может определить, какое именно устройство
нуждается в обслуживании. У компьютеров с 32-разрядными процессорами Intel
обычно имеются два контроллера прерываний, каждый из которых обслуживает
8 устройств. Но один контроллер подчинен (slave) другому, то есть сигналы с его
выхода передаются на вход главного (master) контроллера. Таким образом, эта
комбинация контроллеров способна обслуживать 15 различных устройств, как
показано на рис. 2 . 1 8. Некоторые из 1 5 входов являются выделенными; напри­
мер, вход таймера, I RQ О , не подключен к какому-либо разъему, предназначенному
для адаптеров. Остальные же входы подключены к разъемам и могут использо­
ваться так же, как и обычные устройства.

Прерывание
------ IRQ О (часы)
INT Ji INT 14----- IRQ 1 (клавиатура)

::;;
Главный -.t----- IRQ 3 (tty 2)
CPU :s; контроллер -.t--- -- IRQ 4 (tty 1 )
u
прерываний �г----- IRQ 5 (винчестер ХТ)
INTA � ::t: АС К -.-1----- IRQ 6 (дисковод)
::t:
ltl
1:[
--.г----- IRQ 7 (принтер)
Подтверждение ltl
прерывания 3 ::t:
:s; IRQ 8 (часы реального времени)
INT IRQ 9 (перенаправление IRQ2)
Подчиненный IRQ 1 0
контроллер IRQ 1 1
\,------i
прерываний IRQ 12
i,.------.i
IRQ 1 3 (исключение FPU)
АСК IRQ 14 (винчестер АТ)
IRQ 1 5
Рис. 2. 1 8 . Обработка прерываний в 32-разрядных процессорах lntel
1 96 Глава 2. П роцессы

Здесь прерывания приходят по одной из линий I RQ п, нарисованных в правой


части схемы. Процессор получает сигнал о том, что произошло прерывание, по
контакту I NT. В ответ на это сообщение процессор посьшает по контакту I NTA
( INTerrupt Acknowledge - подтверждение прерывания ) сигнал, по которому
контроллер помещает на шину данных информацию, говорящую системе о том,
какой из обработчиков вызывать. Программируются контроллеры прерываний
при инициализации системы в момент, когда функция rna i n вызывает функцию
intr_ini t. При программировании определяется, какие данные контроллер
сформирует на шине данных процессора при поступлении сигнала по каждой из
входных линий контроллера, а также устанавливается ряд других параметров
контроллера. Передаваемые на шину данные представляют собой 8-разрядное
число, используемое далее как индекс в массиве максимум из 256 элементов.
В MINIX этот массив содержит 56 элементов, из которых реально задействуют­
ся 35. Остальные зарезервированы для будущих версий процессоров Intel или
расширений в MINIX 3. В 32-разрядной версии системы в записях таблицы со­
держатся дескрипторы шлюзов прерываний (interrupt gate descriptor). Каждый
дескриптор является 8-байтовой структурой с несколькими полями.
Есть несколько режимов обработки прерываний. В том, который принят в MINIX 3,
из нескольких полей дескриптора наибольшее значение для нас будут иметь ад­
рес сегмента памяти, где размещена функция-обработчик, и адрес этой функции
внутри сегмента. Получив сигнал прерывания, процессор выполняет код, на ко­
торый ссьшается запись в таблице. Результат полностью эквивалентен ассемб­
лерному вызову
int <nnn>

Разница только в том, что при аппаратном прерывании адрес процедуры берется
не из памяти, а из регистра контроллера.
Механизм переключения задач в 32-разрядных процессорах Intel, как ответ на
прерывание, довольно сложен, и изменение значения счетчика команд - только
часть этого процесса. Когда процессор, уже обрабатывающий некоторый процесс,
получает прерывание, он сначала выделяет новый стек, который будет использо­
ваться для выполнения обработчика. Положение стека определяется значением
записи в сегменте состояния задания (Task State Segrnent, TSS). Эта структура
едина для всей системы, инициализируется при вызове prot_ini t и модифи­
цируется при запуске каждого процесса. В результате каждый новый стек, со­
здаваемый прерыванием, всегда начинается от конца структуры s t a c k f rarne_s
в записи в таблице процессов прерванного процесса. Затем процессор автомати­
чески помещает в новый стек значения нескольких регистров, включая нужные
для восстановления стека и счетчика команд прерванного процесса. Обработчик
прерывания заносит в стек значения дополнительных регистров, заполняя кадр
стека, после чего переключается на другой стек, предоставляемый ядром. Этот
стек и используется для всех действий по обработке прерывания.
Завершив свою работу, обработчик прерывания переключается обратно на кадр
стека в таблице процессов (не обязательно на тот, который использовался для
предыдущего прерывания), затем самостоятельно извлекает из стека значения
2.6. Реализация процессов в M I N IX 3 1 97

дополнительных регистров и выполняет инструкцию i r e t d (возврат из преры­


вания). Эта инструкция восстанавливает значения регистров, помещенные в стек
процессором, и переключается на исходный (до прерывания) стек. Таким образом,
прерывание останавливает текущий процесс, а по завершении обработки процесс
запускается, причем не обязательно тот, который был приостановлен. Данная схема
отличается от более простых алгоритмов, часто встречающихся во многих ассемб­
лерных программах, тем, что в стеке прерванного процесса не сохраняется никаких
данных. Более того, так как стек пересоздается в заранее заданном месте (которое
определяется записью в TSS), упрощается управление несколькими параллельно
работающими процессами. Все, что нужно для запуска нового процесса, - по­
местить в указатель стека адрес нового кадра стека, извлечь из стека значения
регистров, помещаемые туда программно, и выполнить инструкцию i r e t d.
Получив прерывание, процессор блокирует все остальные прерывания. Это га­
рантирует, что с кадром стека во время выполнения обработчика не произойдет
ничего, что может вызвать переполнение жестко ограниченного кадра стека.
Блокировка происходит автоматически, хотя существуют и ассемблерные инст­
рукции, которые могут запрещать или разрешать обработку прерываний. Преры­
вания остаются блокированными, пока используется стек ядра, расположенный
за пределами таблицы процессов. Существует механизм, позволяющий запустить
в это время обработчик исключений (с помощью которого процессор реагирует
на обнаружение ошибок). Исключения аналогичны прерываниям, однако их не­
возможно блокировать. Таким образом, исключения делают возможной обработ­
ку вложенных прерываний. В подобной ситуации новый стек не создается - не­
обходимые регистры сохраняются в существующем стеке. Предполагается, что
во время работы ядра исключение не может возникнуть; это вызвало бы хаос.
Когда при выполнении ядра процессор встречает команду i r e t d, он использует
более простой механизм возврата, нежели в случае прерванного процесса. С помо­
щью селектора сегмента кода, извлекаемого из стека при обработке инструкции
i r e t d, процессор может определить, какой из ее вариантов следует выполнить.
Различные варианты реакции процессора на прерывания при выполнении кода
ядра определяются упомянутыми ранее уровнями привилегий. Когда уровень при­
вилегий прерванного кода совпадает с уровнем обработчика, действует упрощен­
ный механизм. Тем не менее прерванный код чаще всего имеет более низкий
уровень привилегий, чем код обработки прерывания. В этом случае используется
механизм, ориентированный на TSS и создание нового стека. Уровень привиле­
гий сегмента кода запоминается в его селекторе, который сохраняется в стеке,
что и позволяет при возврате из прерывания определить, какой механизм будет
применяться.
При создании нового стека аппаратно решается еще одна задача. Проверяется,
достаточно ли новый стек велик для того, чтобы вместить необходимый минимум
информации. Это предотвращает случайный (или намеренный) крах ядра в слу­
чае, когда пользовательский процесс делает системный вызов при несоответст­
вующем размере стека. Такая функциональность встроена в процессор специаль­
но для того, чтобы реализовывать мультипрограммные операционные системы.
1 98 Глава 2. П роцессы

Описанное поведение может показаться непонятным непосвященным в архитек­


туру 32-разрядных процессоров Intel. Обычно мы будем стараться избегать по­
добных деталей, но чтобы понять, что происходит при возникновении прерыва­
ния и исполнении команды i r e t d, нужно разобраться в том, как ядро управляет
переходом процесса в состояние выполнения и выходом из него (см. рис. 2.2).
То, что значительная часть работы выполняется аппаратно, упрощает жизнь про­
граммиста и, по видимости, делает систему более эффективной. В то же время
такая помощь со стороны аппаратного обеспечения усложняет понимание кода.
Познакомившись с механизмом прерываний, вернемся к файлу mрхЗ 8 6 . s. В нем
содержится крохотная часть ядра MINIX 3, непосредственно взаимодействую­
щая с прерываниями. Для каждого прерывания в этом файле имеется точка входа.
Так, обработчики от _hwi nt O O до _hwi nt 0 7 (строки 653 1 -6560) делают вызов
hwint_rna s t er (строка 65 15), а обработчики от _hwi nt 0 8 до _hwi nt 1 5 (стро­
ки 6583-66 1 2 ) - вызов hwi nt_s l ave (строка 6566). Каждая точка входа пере­
дает вызову аргумент, указывающий, какое из устройств требует обслуживания.
Фактически эти •вызовы� являются не вызовами, а макросами: имеется восемь
копий макроопределения hwi nt_rna s t e r , различающихся только параметром
i rq. Аналогично дело обстоит и с макросом hwi nt_s l ave. Возможно, идея ка­
жется экстравагантной, однако в результате ассемблированный код оказывается
очень компактным. Объектный код каждого из макросов занимает менее 40 байт.
При обслуживании прерывания важна скорость, а использование макросов из­
бавляет от трех действий - загрузки параметра, вызова подпрограммы и извле­
чения переданного параметра.
Далее в обсуждении мы будем рассматривать вызов hwi nt_rnas t er так, как буд­
то это функция, а не восемь отдельных макросов. Вспомним, что перед исполне­
нием hwi nt_rnas t er процессор создает новый стек в структуре s t a c k f rarne_s
прерванного процесса внутри его элемента в таблице процессов. В нем уже со­
хранены семь ключевых регистров, а все прерывания запрещены. Первое дейст­
вие, выполняемое hwint_rnas t er, - вызов s ave (строка 65 16). Эта подпрограм­
ма помещает в стек значения вех регистров, необходимых для восстановления
прерванного процесса. Ее можно было бы переписать как часть макроса, чтобы
повысить скорость, но это увеличило бы размер макроса более чем в два раза,
и, кроме того, подпрограмма s ave необходима для вызовов, осуществляемых дру­
гими функциями. Как мы увидим, s ave занимается тем, что хитро манипулиру­
ет со стеком. После возврата из hwi nt_rna s t e r используется стек, выделенный
ядром, а не кадр стека в таблице процессов.
Теперь подошла очередь использования двух таблиц, объявленных в файле g lo . h.
Таблица _i rq_handl e r s содержит информацию об обработчиках прерывания,
включая их адреса. Номер обрабатываемого прерывания подлежит преобразова­
нию в адрес, определяемый таблицей _ i r q_hand l e r s . Этот адрес передается
в стек как аргумент функции _int r_hand l e , которая затем и вызывается. Мы
рассмотрим код этой функции позднее. Пока мы лишь скажем, что она вызывает
процедуру обработки прерывания, а кроме того, устанавливает или сбрасывает
флаг в массиве _i rq_a c t i d s в зависимости от того, была ли обработка преры-
2.6. Реализация процессов в M I N IX 3 1 99

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


висимости от конкретного обработчика, после возврата из вызoвa _i nt r_handl e
контроллер прерываний может быть как готовым, так и н е готовым к приему
следующего прерывания. Это определяется соответствующей записью в массиве
_i rq_a c t i ds .
Ненулевое значение в _irq_a c t i ds указывает н а то, что процедура обработки
данного прерывания не завершена. Если это так, контроллеру запрещается реа­
гировать на прерывания, поступающие по той же линии (строки 67722-6724).
Приведенные команды маскируют возможность контроллера прерываний отве­
чать на определенные входные данные; процессор имеет внутренний запрет реа­
гировать на все прерывания с момента первого появления сигнала прерывания,
и этот запрет продолжает действовать.
Скажем несколько слов для читателей, не знакомых с программированием на
языке ассемблера. Следующая команда в строке 652 1 не задает количество бай­
тов, на которое нужно совершить скачок:
j z Of

Значение О f н е является н и шестнадцатеричным числом, н и меткой (имена


обычных меток не могут начинаться с цифры). Мы имеем дело с определением
локальной мemt<:U, принятым в ассемблере MINIX 3. Аргумент О f означает пере­
ход вперед на первую численную метку О в строке 6525. Байт, записанный в этой
строке, позволяет контроллеру прерываний возобновить нормальное функцио­
нирование, возможно, с запретом прерываний текущей линии.
Интересное и, вероятно, странное наблюдение относительно метки О в строке
6525: такая же метка присутствует в том же файле в строке 6576 внутри макроса
hwi nt_s l av e . Ситуация осложняется еще и тем, что обе метки расположены
в макросах, а подстановка последних выполняется до того, как ассемблер про­
сматривает код. В результате ассемблер обнаруживает в одном коде шестнадцать
меток О ! Подобное тиражирование меток при их использовании в макросах ста­
ло причиной ввода механизма локальных меток. При разрешении последних ас­
семблер ищет первую метку в указанном направлении, игнорируя все остальные.
Процедура _int r_handl e является аппаратно-зависимой, и детали, касающие­
ся ее кода, будут рассмотрены нами при изучении файла i 8 2 5 9 . с. Тем не менее
несколько общих слов о ее функционировании здесь вполне уместны. Процеду­
ра _int r_hand l e сканирует связанный список структур, содержащих адреса
функций, обрабатывающих прерывания устройств, и номера процессов, соответ­
ствующих драйверам устройств. Связанный список требуется потому, что одна
линия прерываний может совместно использоваться несколькими устройствами.
Обработчик прерывания сначала проверяет, нуждается ли, на самом деле, в об­
служивании его устройство. Разумеется, в случае таймера (прерывание О) такая
проверка не нужна, поскольку линия прерывания физически связана с микро­
схемой, генерирующей тактовые сигналы, и она является единственным устрой­
ством, способным вызвать прерывание.
Код обработчика должен быть написан так, чтобы его выполнение занимало как
можно меньше времени. Если не требуется выполнять какие-либо действия
200 Глава 2. П роцессы

либо обслуживание прерывания завершается немедленно, обработчик возвращает


значение TRUE. Обработчик может выполнять такие действия, как чтение данных
входного устройства и передача их в буфер для последующего доступа к ним со
стороны соответствующего драйвера. Обработчик может послать драйверу сообще­
ние, заставляющее планировать его как обычный процесс. Если работа не завер­
шена, обработчик возвращает значение FAL SE. Элемент массива _i rq_act_ids
представляет собой битовую карту, регистрирующую результаты работы всех
обработчиков списка так, что нулевое значение имеет место в том и только в том
случае, если все обработчики вернули TRUE. Иначе команды строк 6522-6524
запрещают прерывание до повторного включения контроллера в строке 6536.
Данный механизм гарантирует, что ни один из обработчиков в цепочке, принад­
лежащей прерыванию, не будет активирован до того, как все соответствующие
драйверы устройств закончат свою работу. Очевидно, что необходим альтерна­
тивный способ разрешения прерываний; такой способ предоставляет функция
enaЫ e_i rq, рассмотрением которой мы займемся позднее. Пока достаточно
сказать, что эта функция должна вызываться по завершении работы каждого
драйвера устройства. Кроме того, очевидно, что функция enaЫ e_i rq должна
сначала сбросить собственный бит в элeмeнтe _i rq_a c t_i d s , соответствующий
прерыванию драйвера, а затем проверить, все ли биты сброшены. Лишь после
этого прерывание можно заново разрешить на контроллере.
Приведенное описание в его простейшей форме можно применить лишь к драй­
веру часов, поскольку часы - единственное устройство, генерирующее прерыва­
ния и являющееся частью библиотеки ядра. Адрес обработчика прерываний дру­
гого процесса не важен в контексте ядра, и функция еnаЫ e_i rq ядра не может
бьrгь вызвана отдельным процессом в собственном пространстве памяти. Для всех
драйверов устройств, находящихся в пользовательском пространстве (то есть
всех драйверов, реагирующих на аппаратные прерывания, за исключением драй­
вера часов), адрес общего обработчика generi c_handl er хранится в связанном
списке обработчиков. Исходный код generi c_handl er размещен среди файлов
системного задания, однако, поскольку последнее компилируется вместе с ядром
и полученный код выполняется в ответ на прерывание, функцию gener i c_
handl er нельзя считать частью системного задания. Помимо адреса общего об­
работчика, в каждом элементе связанного списка хранится номер процесса со­
ответствующего драйвера устройства. При вызове функция gene r i c_handl er
посылает драйверу сообщение, запускающее определенные его функции. Сис­
темное задание поддерживает другой конец описанной выше цепочки событий.
Когда драйвер устройства завершает работу, он выполняет вызов sys_i rqc t l
ядра, в ответ на который системное задание вызывает функцию enaЫ e_i rq от
имени драйвера, чтобы подготовиться к следующему прерыванию.
Возвращаясь к функции hwint_rna s t er, обратим внимание на то, что она завер­
шается командой ret (строка 6527). Здесь происходит нечто не вполне очевид­
ное. Если процесс был прерван, то активным является стек ядра, а не стек, со­
зданный аппаратно в таблице процессов перед запуском hwi nt_rna s t er. В этом
2.6. Реализация процессов в M I N IX 3 201

случае обработка стека подпрограммой s ave оставит адрес функции _re s t art
в стеке ядра, что вызовет повторный запуск задания, драйвера, сервера или поль­
зовательского процесса. С большой вероятностью это будет процесс, отличный
от того, который выполнялся в момент прерывания. Все зависит от того, привело
ли к изменению планирования процессов обслуживание сообщения, созданного
процедурой обработки прерывания. Как правило, в случае аппаратного прерыва­
ния очереди процессов изменяются. Обработчики прерываний чаще всего пере­
дают драйверам устройств сообщения, а драйверы размещаются в очередях с бо­
лее высоким приоритетом, чем пользовательские процессы. Таким образом, мы
видим самое сердце механизма, обеспечивающего иллюзию нескольких одновре­
менно выполняющихся процессов.
Для полноты упомянем, что в том случае, когда прерывание происходит в мо­
мент выполнения кода ядра, стек ядра уже используется. Поэтому функция s ave
помещает в стек адрес r e s t ar t l . Тогда после завершения hwi nt_mas t er ко­
мандой r e t работа ядра восстанавливается. Это позволяет делать вложенные
прерывания, которые запрещены в MINIX 3, поскольку прерывания недопусти­
мы во время исполнения кода ядра. Тем не менее, как было отмечено, данный
механизм необходим для обработки прерываний. Когда выполнение низкоуров­
невых процедур завершается, вызывается _r e s t a r t . В результате реакции на
исключение при исполнении кода ядра с высокой вероятностью следующим
будет запущен процесс, отличный от прерванного. Исключение в ядре приведет
к сбою, после которого система попытается завершить свою работу с минималь­
ными повреждениями.
Процедура hwi nt_s l ave отличается от hwint_ma s t er только тем, что в ней
необходимо восстанавливать два контроллера, и подчиненный, и главный, так
как они оба блокируются при приеме сигнала подчиненным контроллером.
Теперь двинемся дальше и рассмотрим процедуру s ave (строка 6622), уже не­
сколько раз упомянутую. Ее имя описывает ее предназначение, заключающееся
в том, чтобы сохранить контекст прерванного процесса в выделенном процессо­
ром стеке (в кадре стека внутри таблицы процессов). Кроме того, для поддержки
вложенных прерываний s ave использует переменную _k_r e enter, чтобы под­
считать число вложений и определить их наличие. Если процесс был прерван,
следующая инструкция (строка 6635) переключается на стек в ядре:
mov esp , k_s t kt o p

Следующая же за ней инструкция помещает в стек адрес подпрограммы _r e ­


s t a r t . В противном случае, если уже используется стек ядра, в него заносится
адрес r e s t art l (строка 6642). Разумеется, прерывания здесь запрещены, одна­
ко данный механизм предназначен для обработки исключений. Независимо от
того, какая ветвь алгоритма была выполнена, для возврата из процедуры не
годится . обычная инструкция r e turn, так как положение стека могло быть из­
менено, а адрес возврата •похоронен� под помещенными в стек регистрами. По­
этому для возврата применяется инструкция
j mp RETADR - P_S TACKBAS E ( eax )
202 Глава 2. П роцессы

Ее вы можете увидеть в двух точках выхода процедуры (строки 6638 и 6643).


Эта инструкция восстанавливает тот адрес, который был помещен в стек перед
вызовом s ave, и осуществляет переход по нему.
Повторный вход в ядро вызывает множество проблем, и избавление от него
упростило некоторые фрагменты кода. В MINIX 3 переменная _k_r e ent er не
теряет смысла: хотя обычные прерывания во время исполнения ядра недопусти­
мы, исключения возможны. При нормальном функционировании команда пере­
хода в строке 6634 никогда не выполняется, однако она необходима для обработ­
ки исключений.
В качестве отступления заметим, что при разработке MINIX 3 в процессе избав­
ления от повторного входа в ядро программирование опередило документирова­
ние. Документирование в некоторых аспектах сложнее, чем написание программ.
Если ошибки в программе можно выявить во время ее выполнения или с помо­
щью компилятора, то обнаружить некорректные комментарии аналогичным об­
разом невозможно. Так, в начале файла mрхЗ 8 6 . s приведен длинный коммен­
тарий, который, к сожалению, неверен. В его части, расположенной в строках
63 1 0-63 1 5 , должно быть сказано, что повторный вход в ядро возможен лишь
при появлении исключения.
Далее в файле mрхЗ 8 6 . s следует процедура _s_c a l l (строка 6649) . Прежде
чем вдаваться в детали, посмотрим, как она заканчивается. Здесь вы не увидите
в конце инструкции ret или j rnp. Фактически, выполнение продолжается с метки
_re s t art (строка 668 1 ). Процедура _s_c a l l является двойником механизма
обработки прерываний из числа системных вызовов. Она получает управление
при возникновении программного прерывания, то есть в результате срабатыва­
ния инструкции int <nnn>. Программные прерывания обрабатываются так же,
как и аппаратные, за исключением того, что индекс записи в таблице дескрипто­
ров прерываний берется из инструкции, а не передается контроллером. Таким
образом, когда _s_c a l l получает управление, процессор уже переключен на стек
в таблице процессов и в него помещены значения нескольких регистров. Так как
у подпрограммы _s_c a l l нет в конце команды выхода, выполняется процедура
_r e s t ar t , после чего подпрограмма окончательно завершается инструкцией
i r e t d. В результате, как и в случае с аппаратным прерыванием, запускается про­
цесс, на который в данный момент ссылается указатель proc_p t r. На рис. 2 . 1 9
сравниваются механизмы обработки аппаратного прерывания и системного вы­
зова, использующего программные прерывания.
Рассмотрим процедуру _s_c a l l более подробно. Еще одна метка, _p_s_ca l l , -

специфика 16-разрядной версии MINIX 3, в которой имеются раздельные проце­


дуры для защищенного и реального режимов. В 32-разрядной версии обращение
по любой из меток приводит к одному результату. Когда программист на С про­
граммирует системный вызов, он пишет код, выглядящий как обычный вызов
функции, локальной или библиотечной. Поддерживающая системные вызовы биб­
лиотечная подпрограмма подготавливает соответствующее сообщение, помещает
идентификатор процесса и адрес сообщения в регистры процессора и вызывает
инструкцию int SYS 3 8 6_VECTOR. Как бьmо отмечено ранее, такая инструкция
2 . 6 . Реализация процессов в M I N IX 3 203

инициирует программное прерывание и управление передается подпрограмме


_s_c a l l , перед вызовом которой в стек (в таблице процессов) помещается ряд
регистров.

Устройство:
Передает контроллеру п рерываний
электрический сигнал

Контроллер:
1 Программа:
1.Записывает указатель на сообщение
и указатель на пункт назначения
1.
Преры вает централ ьный процессор в регистры процессора
2.
Отправляет цифровой идентификатор 2.Выполняет инструкцию программного
устройства, вызвавшего прерывание прерывания

Ядро:
l Ядро:
l
1.
Сохраняет состоя ние регистров 1.Сохраняет состоя ние регистров
2.
Отп равляет уведомление драйверу 2.Отправляет и/или получает сообщение
3. Перезапускает процесс 3. Перезапускает процесс (не обязател ьно
(возможно драйвер) вызывающий)

а б
Рис . 2 . 1 9 . Сравнение механизмов обработки а п п а ратного п р е р ы вания и системного вызова:
а - обработка ап паратного п р е р ы вания ; б - так происходит систе м н ы й вызов

Первая часть процедуры _s_ca l l напоминает код s ave с раскрытыми макросами.


Как и в коде s ave, процессор переключается на стек в ядре инструкцией
mov евр , k_s t kt op

Аналогично разрешаются прерывания. ( Сходство программных и аппаратных пре­


рываний проявляется и в том, что в обоих случаях прерывания перед входом в об­
работчик запрещаются.) Далее следует вызов процедуры _sуs_саl l (строка 6672),
которую мы рассмотрим разделом позже. Сейчас мы скажем только то, что она при­
водит к доставке сообщения и, следовательно, запускает планировщик. Таким обра­
зом, когда _sys_ca l l выполняет возврат, указатель proc_ptr может не указьmать
на процесс, сделавший системный вызов. Затем управление передается r e s t art.
Мы видим, что вызов _r e s t ar t (строка 668 1 ) происходит в трех случаях:
1 . В функции ma i n при запуске системы.
2. При выполнении перехода в функции hwint_ma s t er или hwi nt_s l ave по­
сле аппаратного прерывания.
3. После выполнения системного вызова, за счет того, чтo _s_ca l l не содержит
завершающей инструкции.
На рис. 2.20 показана упрощенная схема передачи управления между процессами
и ядром через вызов _r e s t ar t .
204 Глава 2 . П роцессы

- - -
-

<Zхолодна;»>' 1
/ Процесс
\ загрузка
.... ,...
/ или задача
- -
г - - - - - - - -

Пространство
пользователя

Внешнее прерывание Программное прерывание


(от устройства или часов) (системный вызов)

Рис . 2 . 20. Restart код, исполняемый как после запуска системы, так и после прерываний
-

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


(как правило, отличный от прерванного). На диаграмме не показаны прерывания,
происходящие во время выполнения ядра
В любом из трех случаев прерывания перед вызовом _r e s t ar t запрещаются.
К моменту, когда выполнение кода достигает строки 6690, следующий процесс
уже выбран и не может быть изменен, поскольку прерывания запрещены. Табли­
ца процессов разработана так, чтобы начинаться с кадра стека, поэтому следую­
щая инструкция заносит в регистр указателя стека ссылку на кадр стека в табли­
це процессов:
mov esp , ( _proc_p t r )

Далее выполняется инструкция


l l dt P_LDT_SEL ( e s p )

Эта инструкция загружает значение регистра локальной таблицы дескрипторов


из кадра стека, заставляя процессор использовать сегменты памяти, принадлежа­
щие тому процессу, который будет запущен. Следующая инструкция загружает
из записи очередного процесса адрес кадра стека, который будет задействован
при возникновении следующего прерывания, и помещает этот адрес в сегмент
состояния задания (TSS).
Первая часть подпрограммы _r e s t ar t не нужна в том случае, если прерывание
произошло в момент работы кода ядра, поскольку стек ядра уже используется,
и после завершения обработки выполнение кода должно продолжиться с пре­
рванного места. Тем не менее в MINIX 3 повторное вхождение в ядро не допус­
кается, и обычные прерывания не могут происходить во время его работы. Метка
r e s t ar t l (строка 6694) отмечает инструкцию, с которой в данном случае необ­
ходимо продолжить выполнение при появлении исключения во время работы
ядра (будем надеятся, что это никогда не произойдет). В этом месте декременти­
руется значение переменной k_r e ent er, обозначая тем самым новый уровень
вложения прерываний, а последующие команды восстанавливают состояние про­
цессора. Завершающие инструкции модифицируют стек так, чтобы игнорировался
2 .6 . Реализация п роцессов в M I N IX 3 205

адрес возврата, помещенный в стек при вызове s ave. Если прерывание произош­
ло во время выполнения пользовательского процесса, завершающая инструкция
i r e t d передает управление следующему процессу в очереди планировщика, при
этом восстанавливаются его оставшиеся регистры, в том числе указатель и адрес
сегмента стека. Но если управление было передано через r e s t ar t l , то есть за­
действован не кадр стека, а стек ядра, это означает, что после завершения совсем
не нужно переходить к новому процессу, а требуется завершить выполнение пре­
рванного кода ядра. Процессор отслеживает подобную ситуацию, когда извлека­
ет дескриптор сегмента кода из стека при выполнении i r e t d, и, обнаружив ее,
оставляет использоваться стек ядра.
Теперь самое время сказать несколько слов об исключениях. Исключения вы­
зываются различными ошибками выполнения, однако это не всегда плохо. Ис­
ключения полезны, чтобы побудить систему предоставить некоторые допол­
нительные услуги, например выделить программе дополнительную память или
загрузить в оперативную память страницу, перемещенную в область подкачки
(хотя в MINIX 3 подобные услуги не предусмотрены). Иногда исключения обу­
словлены ошибками в программах. Когда исключение возникает внутри ядра,
это вызывает серьезный сбой системы. Если исключение происходит в поль­
зовательской программе, ее можно завершить, но такой подход неприменим
к операционной системе - она должна выполняться постоянно. Обрабаты­
вают исключения так же, как и прерывания, то есть через дескрипторы в таб­
лице дескрипторов прерываний. В этой таблице имеется шестнадцать запи­
сей, содержащих указатели на точки входа обработчиков исключений, начиная
с _di vi de_error и заканчивая _copr_error, которые можно увидеть в кон­
це файла rnpx3 8 6 . s (строки 6707-6769). Каждая из этих точек входа передает
управление процедуре exc ep t i on ( строка 6774) или e r r e x c ep t i on (стро­
ка 6785 ) в зависимости от того, помещается ли при исключении в стек код
ошибки или нет. Обработка выполняется командами ассемблера и во многом на­
поминает уже рассмотренный нами код: значения регистров сохраняются в сте­
ке, и вызывается С-функция _exc ep t i on (обратите внимание на знак под­
черкивания перед именем). Последствия исключений могут быть различными:
некоторые игнорируются, некоторые вызывают сообщение о сбое ядра, другие
посылают сигналы процессам. Самой функцией _excep t i on мы займемся в сле­
дующем разделе.
Существует еще одна точка входа, которая обрабатывается как прерывание, -
это _l eve l O_c a l l (строка 6714). Она используется, когда код необходимо ис­
полнить с нулевым (максимальным) уровнем привилегий. Эта точка входа нахо­
дится в файле mрхЗ 8 6 . s вместе с прерываниями и исключениями потому, что
она также вызывается при помощи инструкции int <nnn>. Как и обработчики
исключений, она выполняет вызов s ave, а завершается инструкцией r e t , веду­
щей к _r e s t ar t . Ее мы рассмотрим в следующем разделе, когда мы познако­
мимся с кодом, нуждающемся в обычно недоступных (даже ядру) привилегиях.
Наконец, в конце ассемблерного файла выделяется место для хранения некото­
рых данных. Определяются два различных сегмента данных.
206 Глава 2. П роцессы

. s e c t . rom

Это объявление в строке 6822 гарантирует, что указанная область памяти нахо­
дится в самом начале сегмента данных ядра. Сюда компилятор помещает сигна­
туру (магическое число), чтобы программа boot могла убедиться, что загружает
действительно ядро. При компиляции всей системы за магическим числом будут
размещены различные строковые константы. Еще одна область данных задается
следующей директивой (строка 6825):
. s e c t . bs s

Эта директива резервирует в неинициализированном пространстве ядра место


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

2 . 6 . 9 . Взаимодействие м ежду
процессам и в M I N IX 3
В MINIX 3 процессы взаимодействуют друг с другом при помощи сообщений по
принципу рандеву. Когда процесс делает системный вызов s end (отправка сооб­
щения), нижний уровень ядра проверяет, ожидает ли получатель сообщение от
отправителя (или от любого процесса - неопределенный отправитель имеет спе­
циальное имя ANY). Если это так, сообщение копируется из буфера отправителя
в буфер получателя, и оба процесса помечаются как готовые к выполнению. Если
получатель не ждет сообщений, отправитель блокируется и помещается в оче­
редь процессов, ожидающих отправки сообщения.
Когда процесс делает системный вызов r e c e i ve, ядро проверяет, есть ли в очере­
ди ожидающих отправки процессов процессы, пытающиеся отправить сообщение
текущему процессу. Если таковые есть, сообщение передается из буфера отпра­
вителя в буфер получателя, и оба процесса выходят из состояния блокировки.
Если ожидающих отправителей нет, процесс-получатель приостанавливается до
прибытия сообщения.
В операционной системе MINIX 3, где компоненты выполняются как полностью
независимые процессы, метод рандеву не всегда хорош. Именно для таких ситуа­
ций предназначен примитив no t i fy, рассьmающий «базовые� сообщения. От­
правитель не блокируется, если адресат такого сообщения не находится в ожида­
нии приема, но при этом сообщение не теряется. При ближайшем совершении
получателем вызова r e c e i ve активные уведомления доставляются прежде обыч­
ных сообщений. Уведомления применяются в ситуациях, где использование обыч­
ных сообщений привело бы к взаимным блокировкам. Ранее мы указывали на
то, что необходимо избежать ситуации, когда процесс А блокируется при попыт­
ке передать сообщение процессу В, а процесс В блокируется при попытке пере­
дать сообщение процессу А. Если одно из сообщений является неблокирующим
уведомлением, проблема исчезает.
2 .6 . Реализация процессов в M I N IX 3 207

В большинстве случаев отправитель с помощью уведомления информирует ад­


ресата о чем-либо. Есть две особые ситуации, когда уведомление несет в себе до­
полнительные сведения, однако адресат всегда может послать отправителю сооб­
щение, чтобы запросить подробности.
Высокоуровневый код для обмена информацией между процессами находится
в файле proc . с. Задача ядра состоит в том, чтобы преобразовать аппаратное
или программное прерывание в сообщение. Первые генерируются аппаратным
обеспечением компьютера, а вторые служат для передачи ядру запросов, в дан­
ном случае - системных вызовов. Оба варианта достаточно похожи, и для их об­
работки можно было бы использовать одну функцию, но более эффективно со­
здать две специализированные.
В начале файла находятся два макроопределения и один комментарий, заслужи­
вающие отдельного рассмотрения. Для управления списками интенсивно исполь­
зуются указатели на указатели, и в строках 7 420-7 436 описаны их преимущества
и применение. Макрос Bu i l dМe s s (строки 7458-747 1 ) требуется при формиро­
вании сообщений для примитива no t i fy. Единственной вызываемой функцией
является get_up t ime, которая считывает переменную, поддерживаемую таймер­
ным заданием для установки временн:Ь1х меток. • Вызовы� другой •Функции�,
pr iv, являются подстановками макроса, определенного в файле priv . h:
# d e f ine p r i v ( rp ) ( ( rp ) - >p_p r i v )

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


лерной процедуре cp_me s s , расположенной в файле k l i Ь 3 8 6 . s .
Н а макросе Bu i l dМe s s стоит остановиться подробнее. Макрос p r i v использу­
ется в двух особых случаях. Если отправителем является HARDWARE , уведомле­
ние содержит дополнительную информацию - копию битовой карты активных
прерываний процесса-получателя. Если отправителем является SYS TEM, уве­
домление включает битовую карту активных сигналов. Обе карты размещены
в элементах таблицы привилегий получателя и доступны в любой момент. Если
получатель не блокирован в ожидании уведомления на момент его отправки,
уведомление может быть доставлено позднее. В случае обычных сообщений это
потребовало бы буфера для размещения недоставленных сообщений. Для хране­
ния уведомлений же достаточно битовой карты, в которой биты соответствуют
процессам, способным отсылать уведомления. Если уведомление не было достав­
лено, в битовой карте адресата устанавливается бит, соответствующий процессу­
отправителю. При вызове r e c e i ve получателем битовая карта анализируется,
и при обнаружении установленного бита уведомление восстанавливается. Бит
позволяет установить отправителя, и если им является HARDWARE или SYS TEM,
к уведомлению добавляется дополнительная информация. Кроме того, при вос­
становлении сообщения в него добавляется временная метка. ВременнЬ1е метки
не нужны при первой попытке доставки уведомления, поскольку важным явля­
ется время успешной доставки.
Первой функцией в файле proc . с является sys_ca l l (строка 7480). Она преоб­
разует программное прерывание (инструкция int SYS 3 8 6_VECTOR инициирует
системный вызов ) в сообщение. Существует большое количество возможных
208 Глава 2. П роцессы

источников и приемников, и вызов может потребовать отправки сообщения или


приема либо того и другого. Необходимо выполнить ряд проверок. В строках
7480 и 748 1 извлекается код функции ( S END, REC E IVE и др. ) и флаги из первого
аргумента вызова. Первая проверка устанавливает, разрешено ли вызывающему
процессу совершать вызов. Вызов i s kerne l (строка 750 1 ) является макросом,
определенным в файле proc . h (строка 5584). Следующая проверка позволяет
убедиться в том, что указанный отправитель или получатель является допусти­
мым процессом. Далее проверяется, что указатель сообщения ссьmается на до­
пустимую область памяти. Привилегии MINIX 3 определяют, каким процессам
может посылать сообщения данный процесс, и именно эта проверка выполняет­
ся следующей (строки 7537-754 1 ) . Наконец, последний тест позволяет убедить­
ся в том, что процесс-получатель выполняется, а не инициировал завершение
(строки 7543-7547). Если все проверки успешно пройдены, начинается выпол­
нение реальной работы с помощью одной из трех функций: mi ni_s end, mini_
-

r e c e ive и mini_no t i fy. Если переменная func t i on имеет значение ЕСНО,


используется макрос CopyMes s с одинаковыми источником и приемником. Как
было отмечено ранее, ЕСНО применяется исключительно с целью тестирования.
Ошибки, проверяемые тестами в функции sys_c a l l , маловероятны, однако
соответствующие проверки выполнить не сложно, поскольку, в конечном счете,
они сводятся к сравнению небольших целых чисел. На самом базовом уровне
операционной системы желательно проверять даже самые невероятные ошибки.
В периоды активности системы ее код выполняется многократно.
Функции mini_s end, mini_rec и mini_no t i fy составляют основу стандартно­
го механизма передачц сообщений MINIX 3 и должны быть тщательно изучены.
У mi ni_s end (строка 759 1 ) имеется три входных параметра: отправитель, полу­
чатель и указатель на буфер, в котором расположено сообщение. После много­
численных тестов, проводимых в функции sys_c a l l , в mi n i _s end выполня­
ется лишь один, призванный предотвратить взаимную блокировку по записи.
В строках 7606-7610 проверяется, что отправитель и получатель не пытаются
отправить сообщения друг другу. Ключевая проверка в mi ni_s end реализована
в строках 7615-76 1 6. Она устанавливает, не находится ли адресат в состоянии
блокировки после вызова r e c e i v e . Для этого анализируется значение бита
RECE IVING в поле p_rt s_f l ags элемента таблицы процессов получателя. Если
получатель ждет сообщение, то на следующем шаге выясняется, от кого. Если
выясняется, что ожидаемым источником является процесс-отправитель или про­
цесс ANY, срабатывает макрос C opyMe s s , копирующий данные в буфер получа­
теля и разблокирующий его сбросом бита RECEIVING. Далее, чтобы дать полу­
чателю возможность выполниться, совершается вызов enqueue (строка 7620).
Если же адресат не блокирован или блокирован, но ждет сообщение от другого
процесса, выполняется код, расположенный в строках 7623-7632 и переводящий
уже отправителя в состояние блокировки и вывода из очереди. Все процессы,
ожидающие отправки сообщения одному и тому же получателю, связываются
вместе в список, на начало которого указывает поле p_c a l l e r q получателя.
Пример на рис. 2 . 2 1 , а показывает, что произойдет, если процесс 3 не сможет
2.6. Реал изация процессов в M I N IX 3 209

отправить сообщение процессу О. Если при этом процесс 4 также не сможет от­
править сообщение процессу О, возникает ситуация, продемонстрированная на
рис. 2.2 1 , б.

·� ·�

4 p_q_link = О
3 p_q_link = О � p_q_link
2
1
о p_caller_q - >-- p_caller_q
т т т т
а б
Рис. 2 . 2 1 . Очереди процессов, ожидающих отправления сообщения процессу О
Функция rnini_rec e i ve (строка 7642) вызывается функцией sys_cal l в случае,
если значение переменной func t i on равно RECE IVE или вотн. Как мы упомяну­
ли ранее, уведомления имеют более высокий приоритет, чем обычные сообщения.
Тем не менее уведомление никогда не передается в ответ на вызов s end, поэтому
битовые карты проверяются на наличие активных уведомлений лишь в случае,
если флаг S ENDREC_BUSY сброшен. При обнаружении уведомления оно помеча­
ется как неактивное и доставляется (строки 7670-7685 ). При доставке использу­
ются макросы Bu i l d.Мe s s и C opyMe s s , определенные в начале файла proc . с .
На первый взгляд может показаться, что, поскольку временная метка является
частью уведомления, он несет в себе полезную информацию. Например, если ад­
ресат не мог выполнить вызов rec e i ve в течение какого-то времени, временная
метка поможет определить, как долго сообщение ожидало доставки. Однако
временная метка устанавливается при генерации уведомления, а именно - в мо­
мент его доставки, а не отправки. Для этого есть причина. Дело в том, что для
сохранения уведомлений, которые не могут быть доставлены немедленно, не
нужно никаких специальных действий. Все, что требуется, - это установить бит,
указывающий на необходимость восстановления уведомления в момент, когда
доставка станет возможной. Таким путем достигается максимально экономичное
хранение уведомлений - 1 бит на одно активное уведомление.
Есть и еще одно основание записывать в качестве временной метки момент дос­
тавки. Пусть, к примеру, с помощью уведомления менеджеру процессов переда­
ется сообщение SYN_ALARМ. Если бы временная метка была проставлена при от­
правке, менеджеру процессов пришлось бы запросить у ядра правильное время
перед проверкой очереди таймера.
Обратим внимание на то, что уведомления доставляются по одному за раз, после
чего функция rni ni_s end завершается (строка 7684 ) . Тем не менее отправи­
тель не блокируется и сразу после получения уведомления может выполнить
еще один вызов r e c e i ve. Если уведомлений не обнаруживается, очереди от­
правителя проверяются на наличие активных сообщений любых других типов
21 О Глава 2 . П роцессы

(строки 7690-7699). Если сообщение присутствует, оно доставляется макросом


CopyMe s s , а источник разблокируется вызовом enqueue (строка 7694). В этом
случае отправитель не блокируется.
Если ни уведомлений, ни других сообщений нет, отправитель блокируется вызо­
вом dequeue (строка 7708).
Функция rnini_not i fy (строка 7719) используется для выполнения уведомле­
ний. Она аналогична rnini_s end, и нам не потребуется долго изучать ее. Если
получатель блокирован и ожидает сообщения, уведомление генерируется мак­
росом Bui l dМe s s и доставляется. Флаг REC E I VI NG получателя сбрасывается,
и функция enqueue ставит его в очередь (строки 7738-77 43). Если получатель
не находится в состоянии ожидания, в его карте s_not i fy_p endi ng устанавли­
вается бит, указывающий на наличие активного уведомления и определяющий
отправителя. Далее отправитель продолжает свою работу, и, если необходимо
отправить получателю еще одно уведомление раньше, чем он получит предыду­
щее, указанный бит попросту переписывается. Фактически, все уведомления од­
ного источника объединяются в одно сообщение, что избавляет от необходимости
управления буферами и в то же время обеспечивает возможность асинхронной
передачи сообщений.
Когда функция rnini_not i fy вызывается из-за программного прерывания и по­
следующего вызова sys_c a l l , прерывания запрещаются. Тем не менее тай­
мерному, системному или другому заданию, поддержка которого может быть
реализована в MINIX 3 в будущем, может потребоваться передача уведомления
в момент, когда прерывания разрешены. Функция l o c k_no t i fy (строка 7758)
является безопасным проводником к rnini_no t i fy. Она проверяет переменную
k_reenter, чтобы убедиться, что прерывания уже запрещены, и если это так,
просто вызывает rnini_not i fy. Если прерывания разрешены, то сначала они за­
прещаются вызовом l ock, затем вызывается rnini_no t i fy, а после этого пре­
рывания вновь разрешаются вызовом unlock.

2 . 6 . 1 О. Планирование п роцессов в M I N IX 3
В MINIX 3 применяется многоуровневый алгоритм планирования. Процессам
назначаются начальные приоритеты, однако, как показано на рис. 2 . 1 4 , число
уровней больше, и приоритет процесса может меняться по ходу его выполнения.
Таймерные и системные задания выполняются на уровне 1 и имеют наивысший
приоритет. Драйверы устройств на уровне 2 имеют более низкие, но не равные
между собой приоритеты. Приоритет серверов уровня 3 ниже, чем у драйверов,
но выше, чем у некоторых других процессов. Пользовательские процессы запус­
каются с одинаковым приоритетом, уступающим всем системным процессам, од­
нако приоритет отдельного процесса может быть понижен или повышен при по­
мощи команды n i c e.
Планировщик поддерживает 1 6 очередей готовых процессов, хотя не все из них
могут использоваться в конкретный момент времени. На рис. 2.22 показаны
2.6. Реал иза ц ия процессов в M I N IX З 21 1

очереди и помещенные в них процессы сразу после того, как ядро закончило
инициализацию и запустилось, то есть при вызове r e s t art в файле rnain . с ( стро­
ка 7252). Массив rdy_head содержит по одному элементу для каждой очереди,
указ ывающему на первый процесс в очереди. Аналогично, элементы массива
rdy_t a i l указывают на последние процессы в каждой очереди. Оба массива оп­
ределены с макросом EXTERN в файле proc . h (строки 5595 и 5596). Начальное
формирование очередей процессов при запуске системы определяется таблицей
irnage в файле t аЫ е . с (строки 6095-6 1 09).

rdy_head rdy_tail

15 IDLE_Q �141о1(1---1 IDLE_Q \ 15

1
I USER_Q �14о1(1------1 USER_Q \ 1

4 fs 4

3 rs 3

2 disk 2

tty

о TASK_Q system TASK_Q О

Рис. 2.22. Планировщик подцерживает 1 6 очередей, по одной на каждый уровень приоритета.


Здесь показано начальное планирование процессов при запуске M I N IX З

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


Если текущий процесс исчерпал свой квант времени, он перемещается в хвост
очереди и получает очередной квант. Однако при разблокировании процесс по­
ступает в начало очереди, если на момент блокирования его квант не истек. Ко­
гда такой процесс выбирается на исполнение, вместо нового кванта ему выделя­
ется время, оставшееся в кванте, который он не израсходовал. Массив rdy_t a i l
делает постановку процессов в конец очереди более эффективной. При блокиро­
вании текущего процесса или завершении готового процесса по сигналу такой
процесс удаляется из очередей планировщика. Очереди состоят только из про­
цессов, готовых к исполнению.
Алгоритм планирования с описанной структурой очередей прост. Берется непус­
тая очередь с наивысшим приоритетом, в которой выбирается первый процесс.
Процесс I DLE всегда готов и находится в очереди с самым низким приоритетом.
Если все 4Вышестоящие� очереди пусты, система запускает процесс I DLE.
212 Глава 2 . П роцессы

В предыдущем разделе мы неоднократно упомина.т�: функции enqueue и dequeue .


Теперь самое время познакомиться с ними подробнее. Функция enqueue вызы­
вается с указателем на элемент таблицы процессов в качестве аргумента ( стро­
ка 7787). Она вызывает другую функцию, s ched, с указателями на перемен­
ные, определяющие, в какую очередь следует поместить процесс и куда именно -
в конец или начало. Открываются три возможности. Если выбранная очередь
пуста, то в оба массива, rdy_head и rdy_t ai l, записывается указатель на .добав­
ляемый массив, а полю связи p_next r eady присваивается специальное значе­
ние NI L_PROC, означающее, что данный элемент списка является последним (за
ним не следует никакой другой процесс). Если процесс добавляется в начало суще­
ствующей очереди, ее указателю p_next re ady присваивается текущее значение
rdy_he ad, а затем массиву rdy_he a d присваивается адрес, указывающий на
новый процесс. Если процесс добавляется в конец очереди, полю p_next re ady
текущего хвостового процесса, а также указателю rdy_t ai l присваивается ад­
рес нового процесса. Полю p_nextready нового процесса присваивается значе­
ние N I L_PROC. Далее вызывается функция p i c k_proc, определяющая следую­
щий процесс для запуска.
Если процесс необходимо вь1вести из состояния готовности, вызывается функ­
ция dequeue (строка 7823). Для того чтобы процесс перешел в состояние бло­
кировки, необходимо, чтобы он выполнялся, поэтому с большой вероятностью
он будет находиться в начале очереди. Тем не менее сигнал может быть послан
и невыполняемому процессу. Тогда для поиска <�:жертвы» выполняется обход
очереди, причем с большой вероятностью искомый процесс находится в ее нача­
ле. Затем значения указателей изменяются так, что процесс удаляется, а если
процесс находился в состоянии выполнения, дополнительно вызывается функ­
ция p i c k_proc.
Касательно функции dequeue есть еще один интересный момент. Поскольку за­
дания, выполняемые в ядре, используют общий аппаратный стек, рекомендуется
время от времени проверять целостность их стековых областей. В начале тела
dequeue выясняется, находится ли удаляемый процесс внутри ядра. Если это
так, то далее проверяется, была ли перезаписана характерная последовательность,
помещенная в конец стека (строки 7835-7838).
Затем следует функция s c hed, выбирающая очередь, в которую следует по­
местить новый готовый к выполнению процесс, а также место в ней - начало
или конец. В таблице процессов для каждого процесса определена длительность
кванта, время, оставшееся до истечения кванта, текущий и максимальный прио­
ритеты. В строках 7880-7885 проверяется, полностью ли израсходован квант.
Если нет, то процесс перезапускается с количеством времени, оставшимся до
окончания кванта. Если же квант исчерпан, то проверяется, был ли процесс
запущен дважды подряд. Положительный ответ расценивается как возможное
зацикливание или, как минимум, слишком долгое выполнение процесса, и уро­
вень его приоритета увеличивается на единицу. Если же у других процессов бы­
ла возможность получить ресурсы процессора, уровень приоритета, напротив,
уменьшается на единицу. Разумеется, такая мера бессмысленна, если в системе
2 . 6 . Реал изация процессов в M I NIX 3 213

циклически выполняются два процесса, сменяющие друг друга. Выявление по­


добных ситуаций - актуальная проблема.
Далее выбирается очередь для обработки. Очередь О имеет наивысший приори­
тет, а очередь 15 - самый низкий. Кто-то может возразить и сказать, что должно
быть наоборот, однако такая интерпретация соответствует UNIX, где повышение
уровня приоритета означает снижение самого приоритета. Процессы ядра (тай­
мерное и системное задания) неприкосновенны, а вот приоритет всех остальных
процессов может быть понижен помещением их в очередь с большим номером.
В момент запуска все процессы имеют наивысший возможный для них приори­
тет, поэтому попытка уменьшения уровня приоритета ни к чему не приводит.
Приоритет процесса может стать выше лишь в случае, если он бьm хотя бы едино­
жды понижен после запуска. У приоритетов обычных процессов есть и нижняя
граница - они никогда не попадают в одну очередь с процессом I DLE.
Вернемся к функции p i c k_proc (строка 79 1 0). Главное ее назначение - уста­
новить указатель next_p t r. Любое изменение в очередях, способное повлиять
на выбор следующего процесса для выполнения, требует нового вызова p i ck_
p r o c . Если текущий процесс блокируется, p i c k_p roc вызывается для перепла­
нирования процессора. Фактически, функция p i ck_proc и есть планировщик.
Функция p i ck_proc проста. Она проверяет каждую очередь, причем очередь
TASK_Q проверяется первой. Если в этой очереди имеется готовый процесс,
p i c k_proc сразу устанавливает указатель proc_p t r и завершается. В против­
ном случае проверяется соседняя очередь с более низким приоритетом и т. д. -
вплоть до очереди I DLE_Q. Указатель Ь i l l_p t r изменяется и задает пользова­
тельский процесс, время которого расходуется системой (строка 7694). В качест­
ве такого процесса используется последний процесс, выполнявшийся системой.
В файле p r o c . с имеются еще три процедуры - l o c k_ s e nd, l o c k_enqueue
и l o c k_dequeue. Все они обеспечивают доступ к своим основным функциям
при помощи вызовов l o c k и un l o c k по аналогии с рассмотренной нами проце­
дурой l o c k_no t i fy.
И так, подведем итоги. В алгоритме планирования поддерживаются несколько
очередей с различными приоритетами. На выполнение всегда выбирается пер­
вый процесс очереди с наивысшим приоритетом. Таймерное задание ведет учет
времени, расходуемого всеми процессами. Если пользовательский процесс из­
расходовал свой квант, он помещается в конец своей очереди. Таким образом,
планирование равноправных процессов осуществляется по классическому цикли­
ческому алгоритму. Предполагается, что выполнение заданий, драйверов и сер­
веров обычно прерывается блокировками, поэтому им предоставляются более
длинные кванты. Тем не менее они могут быть вытеснены, если занимают ресур­
сы процессора слишком долго. При нормальной работе системы это происходит
редко, однако квантование высокоприоритетных процессов предотвращает за­
висание системы по их вине. Процесс, препятствующий выполнению других
процессов, также может быть временно перемещен в очередь с более низким
приоритетом.
21 4 Глава 2 . Процессы

2 . 6 . 1 1 . Аппаратная поддержка ядра


В системе есть несколько С-функций, которые очень существенно зависят от ап­
паратной платформы. Чтобы способствовать переносу MINIX 3 на разные плат­
формы, такие функции выделены в отдельные файлы: except i on . c , i 8 2 5 9 . с
и prot e c t . с , а не помещены в тех же файлах с высокоуровневым кодом, в кото­
рых они используются.
Файл except i on . с содержит обработчик исключений, функцию except i on
(строка 80 1 2 ) , которая вызывается из ассемблерной программы обработки ис­
ключений в rnpx 3 8 6 . s (вызывается как _exc ept i on ) . Когда источником ис­
ключения является пользователь, оно преобразуется в сигнал, так как ошибки
в пользовательских программах ожидаемы. Но когда источником исключения
является операционная система - это признак того, что произошло нечто дейст­
вительно серьезное. Такое исключение приводит к сообщению о сбое ядра. Сооб­
щение, которое при этом появляется на экране, или сигнал, отправляемый при­
ложению, задаются массивом ex_da ta (строки 8022 -8040). Третье поле задает
минимальный номер модели процессора, поддерживающего данное исключение,
поскольку круг исключений в ранних моделях процессоров Intel довольно узок.
Этот массив является интересным индикатором развития семейства процессоров
lntel, на которых развертывается система MINIX 3. В строке 8065 печатается аль­
тернативное сообщение, если сбой вызван неожиданным прерыванием процессора.

А ппаратная по,дде ржка прерывани й


Три функции файла i 8 2 5 9 . с вызываются при запуске системы для инициали­
зации контроллеров прерываний на микросхемах lntel 8259. Макрос в строке 8 1 1 9
определяет пустую функцию ( настоящая нужна лишь в 1 6-разрядной версии
MINIX 3). Функция int r_i n i t (строка 8 1 24 ) инициализирует контроллеры.
Два шага гарантируют отсутствие прерываний до полного завершения инициа­
лизации. Первый - вызов функции i n t r_di s aЫ e (строка 8 1 34). Это С-вызов
ассемблерной библиотечной функции, выполняющей единственную команду c l i ,
которая запрещает процессору реагировать на прерывания. Далее в регистры ка­
ждого контроллера прерываний записывается последовательность байтов, отклю­
чающая реакцию контроллеров на внешние воздействия. Байт, записываемый
в строке 8 1 45, состоит из единиц и одного нуля, соответствующего каскадному
входу главного контроллера, связывающего его с подчиненным контроллером
(см. рис. 2 . 1 8). Нулевое значение бита включает вход, единичное - отключает
его. Байт, записываемый во вторичный контроллер в строке 8 1 5 1 , состоит цели­
ком из единиц.
Таблица, хранимая в контроллере прерываний i8259, генерирует 8-разрядный
индекс, используемый процессором с целью поиска соответствующего дескрип­
тора шлюза прерываний для каждого возможного входа (сигналы в правой части
рис. 2 . 1 8). Таблица инициализируется BIOS при запуске компьютера, и почти
все ее значения можно оставить неизменными. По мере запуска драйверов,
нуждающихся в прерываниях, в таблицу могут быть внесены необходимые изме­
нения. Каждый драйвер может запросить сброс бита контроллера, включающий
2.6. Реализация процессов в M I NIX 3 21 5

требуемый вход прерываний . Параметр rn i n e функции i n t r_i n i t использу­


ется для того, чтобы определить, завершает MINIX 3 работу или запускается.
Функция i n t r_ini t может быть использована как для инициализации на за­
пуске, так и для сохранения параметров BIOS при завершении работы операци­
онной системы.
После инициализации аппаратного обеспечения функция int r_i n i t выполня­
ет последнее действие - копирует векторы прерываний BIOS в таблицу векто­
ров прерываний MINIX 3.
Вторая функция в файле i 8 2 5 9 . с - put_i rq_handl e r (строка 8 1 62 ) . При
инициализации она вызывается для каждого процесса, который должен реаги­
ровать на прерывания. Она помещает адрес процедуры-обработчика в таблицу
прерываний i r q_hand l e r s , определенную со словом EXTERN в файле g l o . h.
В современных компьютерах пятнадцати линий прерываний не всегда достаточ­
но, поскольку устройств ввода-вывода может быть больше. Это означает, что не­
которые линии прерывания совместно используются двумя устройствами. Такая
ситуация недопустима для базовых устройств, поддерживаемых рассматривае­
мой версией MINIX 3, однако совместное использование линии прерывания мо­
жет потребоваться при оснащении системы сетевыми интерфейсами, звуковыми
платами или более экзотическими устройствами. Чтобы обеспечить подобную
поддержку, таблица векторов не должна состоять из одних лишь адресов. Мас­
сив i rq_hand l e r s [ NR_I RQ_VECTORS ] представляет собой массив указателей
на структуры i rq_hook - тип, определенный в файле kerne l / typ e . h. Эти
структуры содержат поле, в котором хранится указатель на другую структуру того
же типа. Таким образом, строится связанный список с началом в одном из эле­
ментов массива i rq_hand l e r s . Функция put_i rq_handl er добавляет элемент
подобных списков. Самое важное поле такого элемента - указатель на обработ­
чик прерываний, представляющий собой функцию, выполняемую при возникно­
вении прерывания, например при завершении запрошенного ввода-вывода.
Некоторые детали функции put_irq_hand l e r заслуживают внимания. Пере­
менная id устанавливается в значение 1 перед началом цикла wh i l e, сканирую­
щего связанный список (строки 8 1 76-8 1 80). Каждый раз при прохождении цикла
значение i d сдвигается на один бит влево. Проверка в строке 8 1 8 1 ограничивает
длину цепочки разрядностью i d - 32 обработчиками в случае 32-разрядной сис­
темы. Нормальным результатом сканирования является обнаружение конца
цепочки, с которым можно связать новый обработчик. После этого значение i d
сохраняется в одноименном поле нового элемента цепочки. Функция put_i rq_
hand l e r также устанавливает бит в глобальной переменной i r q_u s e , указы­
вая, что для соответствующего прерывания имеется обработчик.
Если вы усвоили основную цель разработки MINIX 3 - перенос драйверов уст­
ройств в пользовательское пространство, - то предшествующая дискуссия о вызове
обработчиков прерываний должна была несколько спутать ваши представления.
Адреса обработчиков прерываний, хранимые в структурах в i rq_hook, бесполез­
ны, если они не указывают на функции, находящиеся внутри адресного простран­
ства ядра. Единственным устройством, использующим прерыв ания и заключенным
21 6 Глава 2 . Процессы

внутри области ядра, являются часы. А как же обстоит дело с драйверами уст­
ройств, располагающих собственными адресными пространствами?
Ответ на данный вопрос состоит в том, что их обработкой занимается системное
задание. На самом деле, системное задание занимается практически всем, что ка­
сается взаимодействия между ядром и процессами, находящимися в пользова­
тельском пространстве. При необходимости регистрации обработчика прерыва­
ний <�:пользовательский» драйвер устройства обращается к системному заданию
с вызовом sys_i rqc t l . Системное задание вызывает функцию put_i r q_
hand l e r, однако в поле обработчика прерываний вместо адреса, принадлежаще­
го пространству драйвера, сохраняется адрес процедуры gene r i c_hand l e r, яв­
ляющейся частью системного задания. Поле номера процесса в структуре irq_
hook используется процедурой gene r i c_handl e r для доступа к записи драй­
вера в таблице привилегий и установки соответствующего бита в карте актив­
ных прерываний. Затем gene r i c_hand l er посылает драйверу уведомление от
имени HARDWARE, в которое включается битовая карта активных уведомлений
драйвера. Таким образом, если драйверу необходимо отвечать на прерывания бо­
лее чем одного источника, он может определить, какое из прерываний вызвало
передачу уведомления. Фактически установка битовой карты предоставляет
драйверу информацию обо всех активных прерываниях. Еще одно поле в струк­
туре i rq_hook поле политики, определяющее, следует разрешить прерывание
-

немедленно или оставить его запрещенным. Во втором случае драйвер сам дол­
жен будет выполнить вызов ядра sys_i rqenaЫ e по завершении обработки
прерывания.
Одна из целей создания операционной системы MINIX 3 поддержка реконфи­
-

гурирования устройств ввода-вывода в процессе выполнения. Следующая функ­


ция, rrn_i rq_handl er, удаляет обработчик, что является необходимым при
удалении и замене драйвера устройства. Фактически rrn_i rq_hand l e r выпол­
няет действия, противоположные put_i rq_handl e r.
Последняя функция файла i 8 2 5 9 . с - функция i n t r_handl e (строка 822 1 );
она вызывается из макросов hwint_rna s t e r и hwi nt_s l ave, знакомых нам по
файлу rnpx3 8 6 . s . Элемент массива i rq_a c t i ds битовых карт, соответствую­
щих обслуживаемому прерыванию, используется для регистрации текущего со­
стояния каждого обработчика в списке. Для каждой функции списка int r_
hand l e устанавливает соответствующий бит irq_ac t i ds и вызывает обработ­
чик. Если обработчик не выполняет каких-либо действий либо завершает обслу­
живание прерывания немедленно, он возвращает значение TRUE, и бит в i rq_
ac t i ds очищается. В конце макросов hwint_rna s t er и hwi nt_s l ave битовая
карта прерывания, рассматриваемая как целое число, проверяется, чтобы опре­
делить, можно ли разрешить прерывание перед запуском следующего процесса.

По.одержка за щ ищенного режима процессоров l ntel


В файле pro t ec t . с находятся несколько функций, необходимых для работы
защищенного режима процессоров Intel. В памяти выделяются глобальная таблица
дескрипторов (Global Descriptor ТаЫе, GDT), локальная таблица дескрипторов
2.6. Реал изация п роцессов в M I NIX 3 217

( Local Descriptor ТаЫе, LDT) и таблица дескрипторов прерываний ( Interrupt


Descriptor ТаЫе, IDT), необходимые для предоставления защищенного доступа
к системным ресурсам. GDT- и I DT-aдpeca хранятся в специальных регистрах
процессора, а GDТ-записи содержат ссылки на отдельные локальные таблицы
дескрипторов. Таблица GDT должна быть доступна для всех процессов и содер­
жит дескрипторы сегментов для областей памяти, используемые операционной
системой. Как правило, каждому процессу соответствует одна таблица LDT, со­
держащая дескрипторы сегментов его памяти. Дескрипторы представляют собой
структуры объемом 8 байт, состоящие из нескольких компонентов. Важнейшие
из полей хранят базовый адрес и размер области памяти. Таблица дескрипторов
прерываний также состоит из 8-байтовых дескрипторов, в которых важнейшее
поле хранит адрес кода, который будет выполнен при возникновении прерывания.
Файл s t art . с содержит функцию pro t_ini t (строка 8368), устанавливающую
GDT (строки 842 1 -8438). Система B I O S I В М РС требует, чтобы эта таблица
была упорядочена определенным образом, для чего в файле protect . h описаны
необходимые значения индексов. Область памяти для LDT выделяется в таблице
процессов. Каждая такая таблица содержит два дескриптора: один для сегмента
кода и один для сегмента данных. Заметьте: сегменты, которые мы обсуждаем,
это сегменты, как их воспринимает аппаратное обеспечение системы. Это - не
те сегменты, с которыми работает операционная система. Так, заданный аппарат­
но сегмент данных далее разбивается на сегменты данных и стека. В строках
8444-8450 для каждой из таблиц L D T создаются дескрипторы, помещаемые
в GDT. За это ответственны функции ini t_da t a s eg и ini t_codes eg. Записи
в самой таблице LDT инициализируются в тот момент, когда меняется карта па­
мяти процесса (то есть когда делается системный вызов е х е с ) .
Еще одна системная структура данных, которую необходимо инициализировать, -
это сегмент состояния задания (Task State Segment, TSS). Его структура опреде­
ляется в начале файла (строки 8325-8354) и включает в себя области для хране­
ния регистров процессора, а также другой информации, которую необходимо за­
поминать при переключении заданий. В MINIX 3 используются только те поля,
которые определяют, где будет создан новый стек в момент возникновения пре­
рывания. Вызов i n i t_da t a s eg (строка 8460) гарантирует, что этот стек может
быть найден через GDT.
Чтобы понять, как MINIX 3 работает на нижнем уровне, важнее всего разобраться,
как прерывания, исключения и инструкции in t <nnn> приводят к исполнению
различных процедур, написанных для их обработки. Для этого необходима табли­
ца дескрипторов шлюзов прерываний. Массив ga t e_t aЫ e (строки 8383 -84 18)
заполняется компилятором адресами процедур, которые обрабатывают исключе­
ния и аппаратные прерывания. Затем значительная часть этого массива в цикле
(строки 8464-8468) инициируется, вызывая функцию i nt_ga t e .
Существует несколько причин, в силу которых данные определенным образом
структурированы в виде дескрипторов. Основная мотивация - особенности
аппаратного обеспечения и необходимость поддерживать совместимость между
современными и старыми 1 6-разрядными процессорами. К счастью, детали мы
21 8 Глава 2. П роцессы

можем оставить разработчикам процессоров Intel. Язык программирования С


позволяет по большей части избегать таких мелочей. Тем не менее при разработ­
ке операционной системы так или иначе придется с ними столкнуться. В нут­
ренняя структура одного дескриптора сегмента приведена на рис. 2.23. Обратите
внимание на то, что базовый адрес, к которому С -программы обращаются как
к простому 32-разрядному целому числу, разбит на три части, две из которых
разделены 1 - , 2- и 4 -разрядными блоками. Размер области является 20 -раз­
рядным числом, которое хранится в виде пары блоков из 16 и 4 бит. Этот размер
может интерпретироваться либо как количество байтов, либо как количество
страниц объемом 4096 байт, в зависимости от значения бита G. У других деск­
рипторов другая структура, не менее сложная. Более подробно мы обсудим эти
структуры в главе 4.

G D О Предел 1 6-1 9 Р DPL S Тип


�-----L--1.__..J'---'""''------+---'--_.___.____..Jc_______----1
База 1 6-23 4
База 0-1 5 База 0-1 5 О
'------L---'
32 бита -----
Относительный
адрес

Рис. 2 . 2 3 . Формат дескри птора сегме нта lntel


Большинство остальных функций из файла pr o t e c t . с предназначены для пре­
образования данных между переменными в С-программах и тем сложным пред­
ставлением, которое принято в дескрипторах. Функции ini t_c ode s eg (строка
8477) и ini t_dat aseg (строка 8493) работают сходным образом. Их назначе­
ние в том, чтобы преобразовать переданные им данные в дескрипторы сегментов.
Обе они, чтобы завершить свою работу, в свою очередь, вызывают другую функ­
цию, sde s c (строка 8508). Именно она имеет дело с запутанной структурой, кото­
рая показана на рис. 2.23. Функции i n i t_c ode s e g и i n i t_ dat a s e g вызы­
ваются не только при инициализации системы. Помимо этого они вызываются
системой каждый раз , когда запускается новый процесс, чтобы выделить новому
процессу необходимые сегменты памяти . Вызов функции seg2phys (строка 8533)
выполняется только из файла s t art . с, ее действие обратно действию s de s c -
извлечение базового адреса сегмента из дескриптора. Функция phy s 2 s e g (стро­
ка 8556) больше не нужна, поскольку доступ к удаленным сегментам памяти (на­
пример, к зарезервированной памяти в пространстве между 640 Кбайт и 1 Мбайт)
обеспечивает вызов sys_s egc t l ядра. Функция int_gat e (строка 857 1), ана­
логичная ini t_c ode s e g и ini t_da t a s eg, заполняет ячейки таблицы дескрип­
торов прерываний.
Функция enaЫ e_i op (строка 8589) файла prot e c t . с необходима для одного
�грязного хака�. Она изменяет уровень привилегий операций ввода-вывода, позво­
ляя текущему процессу исполнять команды считывания и записи портов ввода­
вывода. Объяснить назначение этой функции сложнее, чем описать саму функ­
цию - она всего лишь устанавливает два бита в слове записи кадра стека вызы­
вающего процесса, которое будет загружено в регистр состояния процессора при
следующем выполнении процесса. Эта функция в настоящее время не применя­
ется, и способов запустить ее из пользовательского пространства не существует.
2 . 6 . Реал изация процессов в M I N IX 3 21 9

Последняя функция в файле pro t e c t . с - функция a l l o c_s egment s (строка


8603), которая вызывается do_newmap, а также процедурой ядра rnain во время
инициализации. Ее определение в значительной степени зависит от аппаратной
платформы. Она работает с регистрами и дескрипторами, предназначенными для
поддержки защищенных сегментов на аппаратном уровне процессором Pentium,
используя для этого назначения сегментов в элементах таблицы процессов. Мно­
жественные присваивания (строки 8629-8633) характерны для языка С.

2 . 6 . 1 2 . Утил иты и библиотека ядра


У ядра есть библиотека вспомогательных функций, написанных на языке ассемб­
лера. Эти функции подключаются при компиляции файла k l ib . s. Также имеет­
ся несколько вспомогательных программ на языке С, которые находятся в файле
rni s c . с. Сначала мы рассмотрим ассемблерные файлы. Файл k l i b . s ( стро­
ка 8700) представляет собой короткий ассемблерный файл, сходный с rnpx . s .
В нем в зависимости о т значения WORD_S I Z E выбирается одна из версий кода.
Код, который мы будем обсуждать, расположен в файле k l i Ь3 8 6 . s . В этом
файле содержится около двух дюжин вспомогательных функций, которые напи­
саны на ассемблере либо по соображениям эффективности, либо потому, что они
вообще не могут быть реализованы на С.
Функция _rnon i t o r (строки 8844) позволяет вернуться из системы в монитор
загрузки. С точки зрения монитора загрузки, вся операционная система - не более,
чем подпрограмма, и когда MINIX 3 запускается, адрес монитора остается в сте­
ке. Функции _rnoni t o r необходимо только восстановить значения селекторов
сегментов и указателя стека, после чего выполнить возврат из подпрограммы.
Функция i nt 8 6 (строка 8864) поддерживает вызовы BIOS. BIOS используется
для предоставления альтернативных дисковых драйверов, которые в книге не
описываются. Эта функция передает управление монитору загрузки, который
управляет переходом из защищенного режима в реальный для выполнения вы­
зова BIOS, а затем возвращается в защищенный режим для передачи управления
32-разрядной системе MINIX 3. Монитор загрузки также возвращает длитель­
ность вызова BIOS в виде числа тактов таймера. Зачем это нужно, мы узнаем
при изучении таймерного задания.
Хотя функция _phy s_c opy (см. далее) могла бы использоваться для копиро­
вания сообщений, для этого есть более быстрая специализированная функция
_cp_rne s s . Она вызывается следующим образом:
cp_me s s ( sour c e , s r c_c l i c ks , s rc_de s t , d e s t_c l i c ks , de s t_o f f s e t ) ;

Здесь s ou r c e - это номер процесса отправителя, который копируется в поле rn_


s ource буфера получателя. Адреса буферов получателя и отправителя пред­
ставлены в виде заданного «числа кликов�, обычно базового адреса сегмента, со­
держащего буфер, и смещения. Это - более эффективная форма представления
адресов, чем 32-разрядные целые числа, как в _phy s_c opy.
Функции _exi t , _ex i t и ex i t (строки 9006-9008) необходимы потому,
__

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


220 Глава 2. П роцессы

компиляции MINIX 3, способны делать вызовы стандартной С-функции exi t .


Она вызывается, когда необходимо выйти из программы, но выход из ядра не
имеет смысла, здесь просто некуда выходить. Эта проблема решена так: функция
разрешает прерывания и входит в бесконечный цикл. В определенный момент
возникает прерывание от таймера или операции ввода-вывода, и система возвра­
щается к нормальной работе. Точка входа с именем __rna i n (строка 90 1 2 ) -
еще один пример борьбы с таким режимом работы компилятора, который имеет
смысл при компиляции пользовательской программы, но бесполезен для ядра.
Эта точка входа указывает на ассемблерную инструкцию r e t (возвращение из
подпрограммы).
Функции _phy s_insw (строка 9022), _phys_insb (строка 9047), _phys_out sw
(строка 9072) и _phy s_ou t sb (строка 9098) предоставляют доступ к портам
ввода-вывода, которые в архитектуре процессоров Intel занимают отдельное адрес­
ное пространство, поэтому для работы с ними требуются инструкции, отличные
от обычных инструкций работы с памятью ( i n s , i nsb, out s и out sb ) . Послед­
ние специально предназн:ачены для эффективной обработки массивов (строк),
1 6-разрядных слов и 8-разрядных байтов. Дополнительные инструкции в каж­
дой функции устанавливают все параметры, необходимые для перемещения за­
данного числа байтов или слов между физически адресуемыми буфером и пор­
том. Такой метод обеспечивает скорость, достаточную для обслуживания дисков,
а диски должны обслуживаться быстрее, чем выполняются обычные операции
ввода-вывода над отдельными байтами или словами.
Разрешение и запрещение откликов процессора на все прерыв ания можно выпол­
нить одной командой. Функции _enaЫ e_i rq (строка 9 1 26) и _di s aЫ e_i rq
(строка 9 1 62) устроены несколько сложнее. Они работают на уровне контролле­
ра прерываний и разрешают или запрещают отдельные аппаратные прерывания.
Функция _phy s_c opy (строка 9204) вызывается на С следующим образом:
_phy s_c opy ( s our c e_addre s s , de s t i na t i on_addre s s , Ьy t e s ) ;

Она копирует блок данных из одной области физической памяти в любое другое
место. Оба передаваемых этой функции адреса абсолютные, то есть О означает
первый байт адресного пространства. Все три аргумента функции являются по
типу беззнаковыми длинными целыми.
В целях безопасности вся используемая программой память должна быть очищена
от любых данных, оставленных в ней предьщущей программой. В MINIX 3 эта за­
дача решается с помощью системного вызова ех е с , в конечном счете, обращаю­
щегося к функции phys_rnernset (строка 9248) - следующей в файле k l iЬЗ 8 6 . s .
Две следующие короткие функции специфичны для процессоров Intel. Функция
_rnern_rdw (строка 929 1 ) возвращает 1 6-разрядное слово из произвольного места
памяти. Результат дополняется нулями и помещается в 32 -разрядный регистр
еах. Функция _re s e t (строка 9307) сбрасывает процессор. Для этого в деск­
риптор прерываний процессора загружается нулевой указатель, после чего вы­
зывается программное прерывание. Результирующий эффект равносилен аппа­
ратному сбросу.
2 . 7 . Системное задание в M I N IX З 22 1

Функция i d l e_t a s k (строка 9318) вызывается в случае отсутствия какой-либо


работы. Эта функция содержит бесконечный цикл, однако она не просто нагру­
жает процессор (хотя и это подошло бы для данной цели), а использует команду
hl t , переводящую процессор в экономичный режим до появления прерывания.
Однако hl t является привилегированной командой, и ее исполнение при ненуле­
вом уровне привилегий вызовет исключение. По этой причине функция i d l e_
t a s k помещает в стек адрес подпрограммы, содержащей h l t , и делает вызов
l ev e l O (строка 9322). Функция получает адрес подпрограммы hal t и копирует
его в зарезервированную область памяти (объявленную в файле g l о . h и факти­
чески зарезервированную в t аЫ е . с ) .
Функция _l eve l О воспринимает загруженный в указанную область адрес как
функциональную часть процедуры обработки прерываний, которую нужно за­
пустить с максимальным (нулевым) уровнем привилегий.
Последние две функции в файле k l iЬ3 8 6 . s - read_t s c и read_f l ags. Пер­
вая считывает регистр процессора, исполняющего команду rdt s c (считать счет­
чик временной метки). Этот счетчик подсчитывает циклы процессора и приме­
няется для определения производительности и отладки. Указанная команда не
поддерживается ассемблером MINIX 3 - ее код операции записан как шестна­
дцатеричное число. Функция read_f l ags считывает флаги процессора и воз­
вращает их как С-переменную. К сожалению, программный комментарий о пред­
назначении этой функции ошибочен.
Последний файл, который мы рассмотрим в этой главе, - u t i l i ty . с. В нем на­
ходятся три важные функции. Если в ядре происходят серьезные неполадки, вы­
зывается функция p ani c (строка 9429). Она печатает сообщение и вызывает
функцию prepare_shut down. Когда ядру необходимо напечатать сообщение,
оно не может использовать функцию стандартной библиотеки print f ; по этой
причине в файле ut i l i ty . с определена специальная функция kp rint f (стро­
ка 9450). Полный набор возможностей форматирования не нужен ядру, однако
большая часть функциональности print f поддерживается в kprint f . Посколь­
ку для доступа к файлу или устройству ядро не может воспользоваться файловой
системой, оно передает каждый символ другой функции, kpu t c (строка 9525),
которая добавляет символы в буфер. П олучив код END_O F _KME S S , функция
kpu t c информирует процесс, ответственный за обработку сообщений. Этот про­
цесс определяется в файле inc lude / mi ni x / c on f i g . h и может быть драйве­
ром журнала или драйвером консоли. В случае драйвера журнала сообщение пе­
редается и в журнал, и на консоль.

2 . 7 . Систем ное задание в M I N IX 3


В результате перемещения за пределы ядра и разделения на независимые про­
цессы основные системные компоненты лишились возможности самостоятель­
но осуществлять ввод-вывод, манипулировать таблицами ядра и выполнять
другие действия, присущие операционной системе. К примеру, системный вызов
222 Глава 2 . П роцессы

fork обрабатывается менеджером процессов. Когда новый процесс создается,


ядро должно узнать о его существовании, чтобы запланировать его выполнение.
Как же менеджеру процессов сообщить ядру о новом процессе?
Решение заключается в следующем. Ядро предоставляет драйверам и серверам
набор служб, недоступных обычным пользовательским процессам. Эти службы
позволяют драйверам и серверам осуществлять ввод-вывод, получать доступ
к содержимому таблиц ядра и выполнять другие действия, находясь вне ядра.
Управление перечисленными службами осуществляет системное задание. На
рис. 2.14 оно показано на уровне 1. Хотя его двоичный код объединен с ядром,
системное задание является отдельным процессом и планируется соответствую­
щим образом. Назначение системного задания - прием от драйверов и серверов
запросов на особые услуги ядра и предоставление этих услуг. Поскольку систем­
ное задание находится в адресном пространстве ядра, имеет смысл заняться его
изучением именно сейчас.
В этой главе мы уже сталкивались с примером услуг, предоставляемых системным
заданием. Описывая обработку прерываний, мы видели, как драйвер устройства
использовал вызов sys_i rqc t l для передачи сообщения системному заданию,
запрашивающего установку обработчика прерываний. Драйвер не имеет доступа
к структуре данных ядра, хранящей адреса процедур обработки, однако системное
задание может выполнить нужную работу. Кроме того, поскольку процедура об­
работки прерывания должна находиться в адресном пространстве ядра, адрес об­
работчика указывает на функцию gene r i c_hand l er системного задания. Эта
функция реагирует на прерывание, посылая уведомление драйверу устройства.
Сейчас самое время дать несколько терминологических пояснений. В обычной
операционной системе с монолитным ядром термин системный вызов относится
ко всем вызовам служб ядра. В современных операционных системах семейства
UNIX системные вызовы доступны всем процессам и следуют стандарту P OSIX.
Тем не менее иногда имеют место расширения, выходящие за рамки P O S I X ,
и пользующийся системным вызовом программист, как правило, обращается к биб­
лиотечной С-функции, предоставляющей простой программный интерфейс. Кро­
ме того, некоторые библиотечные функции, кажущиеся программисту систем­
ными вызовами, на самом деле используют такой же механизм доступа к ядру.
Система MINIX 3 устроена иначе: компоненты операционной системы выполня­
ются в пользовательском пространстве, хотя и имеют некоторые привилегии.
Под словосочетанием •системный вызов� мы будем понимать все стандартные
системные вызовы POSIX (а также несколько расширений MINIX), перечислен­
ные в табл. 1 . 1 , однако пользовательские процессы не обращаются к службам
ядра впрямую. В MINIX 3 системные вызовы пользовательских процессов пре­
образуются в сообщения, адресуемые серверам. Серверы, в свою очередь, при
помощи сообщений взаимодействуют друг с другом, с драйверами устройств
и с ядром. Предмет обсуждения этого раздела, системное задание, принимает все
запросы на предоставление услуг ядра. В первом приближении мы можем на­
звать такие запросы системными вызовами, но более точным является термин
вызовы ядра. Вызовы ядра не могут совершаться пользовательскими процессами.
2. 7 . Системное задание в MI NIX 3 223

Во многих случаях системный вызов пользовательского процесса от имени сер­


вера инициирует вызов ядра с тем же именем. Это объясняется тем, что запро­
шенная услуга предоставляется только ядром. К примеру, системный вызов
f o rk поступает от пользовательского процесса менеджеру процессов, который
выполняет часть необходимой работы. Однако вызов f o rk требует изменений
в области ядра таблицы процессов. Для завершения действия менеджер процес­
сов обращается с вызовом sys_fork к системному заданию, которое и осущест­
вляет нужные манипуляции с таблицей. Не все вызовы ядра имеют столь оче­
видную связь с единственным системным вызовом. Например, вызов sys_
dev i o ядра предназначен для чтения и записи портов ввода-вывода и поступает
от драйвера устройства. Больше половины системных вызовов, перечисленных
в табл. 1 . 1 , способны активизировать драйвер устройства для выполнения одно­
го или нескольких вызовов sy s_dev i o .
С технической точки зрения, в дополнение к системным вызовам и вызовам
ядра следует выделить еще одну категорию вызовов - примитивы сообщений.
Примитивы сообщений, применяемые для взаимодействия между процессами,
такие как s end, r e c e ive и no t i fy, напоминают системные вызовы, и именно
так мы неоднократно называли их в книге - эти вызовы действительно обраще­
ны к системе. Тем не менее они отличаются как от системных вызовов, так и от
вызовов ядра, и было бы более корректным использовать для них другие терми­
ны. Иногда можно встретить словосочетание примитив взаимодействия между
процессами, или же термин ловушка, причем оба упоминаются в комментариях
к исходному коду. Примитив сообщений подобен радиоволне в системе бес­
проводной связи. Чтобы радиоволна стала полезной, как правило, необходима
модуляция; тип сообщения и другие компоненты его структуры позволяют на­
полнить вызов информационным содержанием. Иногда полезен и немодули­
рованный радиосигнал, к примеру, в радиомаяках аэропортов. По аналогии,
примитив not i fy практически не содержит никакой другой информации, кроме
своего источника.

2 . 7 . 1 . Обзор системного задания


Системное задание принимает 28 типов сообщений, перечисленных в табл. 2.5.
Каждый из них воспринимается как результат вызова ядра, однако позже мы
увидим, что в некоторых случаях несколько макросов, определенных под разны­
ми именами, приводят к созданию сообщения одного и того же типа. Иногда об­
работка сообщений нескольких типов выполняется одной и той же процедурой.
Структура главной программы системного задания подобна другим заданиям -
после необходимой инициализации следует цикл, принимающий сообщение, вы­
зывающий надлежащую процедуру обслуживания и отсылающий результат. Не­
которые функции общей поддержки находятся в файле sy s t ern . с, однако для
обработки каждого из вызовов ядра основной цикл вызывает процедуру в отдель­
ном файле каталога k e rne l / sy s t ern. Причину организации такой структуры
и ее работу мы разберем позже, при рассмотрении реализации системного задания.
224 Глава 2. П роцессы

Таблица 2.5. Типы сообщений, принимаемых системным заданием. Тип Апу означает
любой системный процесс. Пользовательские процессы не могут обращаться
к системному заданию напрямую
Тип сообщени й Источник Описание
sys_fork pm Процесс запущен
sys_exec pm Установить указатель стека после вызова ехес
sys_exit pm Процесс завершен
sys_пice pm Задать приоритет планирования
sys_privct� rs Задать или изменить привилегии
sys_trace pm Выполнить операцию вызова ptrace
sys_kill pm, fs, tty Послать сигнал процессу после вызова kill
sys_getksig pm Проверка активных сигналов менеджером процессов
sys_eпdksig pm Завершение обработки сигнала менеджером процессов
sys_sigseпd pm Послать сигнал процессу
sys_sigreturп pm Очистка после завершения сигнала
sys_irqctl Драйверы Разрешить, запретить или сконфигурировать
прерывание
sys_devio Драйверы Выполнить операцию чтения или записи над портом
sys_sdevio Драйверы Выполнить над портом операцию чтения или записи
строки
sys_vdevio Драйверы Выполнить вектор запросов ввода-вывода
sys_iпt86 Драйверы Выполнить вызов BIOS в реальном режиме
sys_пewmap pm Установить карту памяти процесса
sys_segctl Драйверы Добавить сегмент и получить его селектор (доступ
к удаленным данным)
sys_memset pm Записать символ в область памяти
sys_umap Драйверы Преобразовать виртуальный адрес в физический
sys_vircopy fs, драйверы Выполнить копирование с использованием
виртуальной адресации
sys_physcopy Драйверы Выполнить копирование с использованием
физической адресации
sys_virvcopy Апу Вектор запросов vcopy
sys_physvcopy Апу Вектор запросов physcopy
sys_times pm Получить время работы системы и процессов
sys_setalarm pm, fs, драйверы Запланировать сигнал синхронизации
sys_abort pm, tty Сбой: MINIX не может продолжать работу
sys_getiпfo Апу Запросить системную информацию
Для начала опишем назначение каждого вызова ядра. Типы сообщений в табл. 2.5
можно разделить на категории. Первые несколько сообщений относятся к управле­
нию процессами. Очевидно, что вызовы sys_fork, sy s_exec, sy s_exi t и sys_
t ra c e тесно связаны с системными вызовами стандарта POSIX. Хотя n i c e не
относится к числу обязательных вызовов POSIX, изменение приоритета процес­
са, в конечном счете, приводит к вызову sys_n i c e ядра. Единственным незна­
комым вам вызовом из данной группы может быть лишь вызов sy s_p r i vc t l .
2 . 7 . Системное задание в M I N IX З 225

Он используется сервером реинкарнации (r s ) - компонентом MINIX 3, ответ­


ственным за перевод обычных пользовательских процессов в разряд системных.
Вызов sys_p r i vc t l изменяет привилегии процесса, позволяя ему, к примеру,
обращаться с вызовами к ядру. Вызов sys_privc t l используется, если драйве­
ры и серверы, не являющиеся частью загрузочного образа, запускаются сцена­
рием / e t c / rc . Драйверы MINIX 3 также могут запускаться и перезапускаться
в любое время; во всех этих случаях необходимо изменять их привилегии.
Следующая группа вызовов ядра относится к сигналам. Вызов sys_k i l l связан
с доступным пользователю (и неправильно называемым) системным вызовом
k i l l . Остальные вызовы группы, sys_g e t k s ig, sy s_endk s ig, sys_s i g s end
и sys_s igreturn, используются менеджером процессов для обработки сигна­
лов с поддержкой ядра.
Вызовы sys_irqc t l , sy s_devi o , sys_s devi o и sy s_vdevi o ядра являются
специфичными для MINIX 3. Они обеспечивают поддержку драйверам устройств,
находящимся в пользовательском пространстве. В начале этого раздела мы уже
упомянули вызов sys_irqc t l ; одна из его задач состоит в установке обработ­
чика аппаратных прерываний и разрешения прерываний от имени драйвера.
С помощью вызова sy s_dev i o драйвер может попросить систему считать или
записать байт в порт ввода-вывода. Это действие, очевидно, необходимо; очевид­
но также, что, если бы драйверы выполнялись в пространстве ядра, а не пользо­
вателя, сопутствующие задержки были бы меньше. Следующие два вызова ядра
обеспечивают более высокоуровневую поддержку устройств ввода-вывода. Вы­
зов sys_s dev i o позволяет считать или записать последовательность байтов
или слов (например, строку) по одному адресу ввода-вывода (например, через
последовательный порт). Вызов sys_vdev i o пересылает системному заданию
вектор запросов ввода-вывода. Под вектором понимается набор пар (порт-значе­
ние). Ранее в этой главе мы описали функцию i n t r_ini t , инициализирующую
контроллеры прерываний Intel i8259. В строках 8 140-8 1 52 последовательность
команд выполняет запись нескольких байтовых значений. В каждом контролле­
ре i8259 имеется порт управления, задающий режим, и еще один порт, прини­
мающий 4 -байтовую инициализирующую последовательность. Разумеется, этот
код выполняется внутри ядра и, следовательно, не требует никакой поддержки
со стороны системного задания. Однако если бы это делал пользовательский
процесс, то передача одного сообщения, содержащего адрес буфера с 10 парами
(порт-значение), была бы значительно эффективнее, чем передача 10 сообще­
ний, содержащих по одному адресу порта и одному значению, которое нужно
в него записать.
Следующие три вызова ядра из табл. 2.5 явно задействуют память. Вызов sys_
newmap делается менеджером процессов при изменении памяти, используе­
мой процессом. В этом случае требуется обновить область ядра таблицы про­
цессов. Вызовы sys_s egc t l и sys_rnern s e t обеспечивают безопасный доступ
процесса к памяти за пределами его пространства данных. Как отмечалось при
рассмотрении вопросов запуска MINIX 3, область памяти с адресами OxaOOOO-Oxffff
226 Глава 2. П роцессы

зарезервирована для устройств ввода-вывода. Некоторые устройства использу­


ют эту память для ввода и вывода данных, например, чтобы вывести изображе­
ние на видеокарту, нужно записать данные в определенную область, располо­
женную в указанном диапазоне адресов. Вызов sys_s eg c t l предоставляет
драйверу устройства селектор сегмента, позволяющий обращаться к зарезер­
вированной памяти, а sys_mem s e t используется сервером для записи данных
в память за пределами его адресного пространства. С помощью вызова sys_
mems e t менеджер процессов обнуляет память при запуске нового процесса, что­
бы предотвратить возможную попытку чтения данных, оставленных предыду­
щим процессом.
Следующая группа вызовов ядра предназначена для копирования памяти. Вызов
sys_umap преобразует виртуальные адреса в физические, а sys_vi rcopy и sys_
physcopy копируют области памяти с использованием виртуальных и физиче­
ских адресов соответственно. Вызовы sys_v i rvc opy и sys__phy svc opy пред­
ставляют собой векторные версии предыдущих двух. Подобно векторам запро­
сов ввода-вывода, векторные вызовы позволяют с помощью системного задания
выполнить последовательность операций копирования памяти.
Как следует из названия, вызов sys_t ime s ядра работает с временем и свя­
зан с системным вызовом t ime s стандарта POSIX. Аналогично, вызов sys_
s e t a l arm связан с системным вызовом a l arm стандарта POSIX, однако связь
относительно слабая. Большая часть действий по обслуживанию РОSI Х-вызова
выполняется менеджером процессов, поддерживающим очередь таймеров от
имени пользовательских процессов. Менеджер процессов задействует вызов
sys_s e t a l arm ядра тогда, когда ему нужно установить таймер от имени ядра.
Как правило, это необходимо лишь при изменении начала очереди таймеров ,
и не каждый вызов a l a rm от пользовательского процесса сопровождается вызо­
вом sys_s e t a l arm ядра.
Последние два вызова табл. 2.5 предназначены для управления системой. Вызов
sy s_abo rt инициируется менеджером процессов после нормального запроса
на завершение работы системы или после сбоя либо драйвером устройства t ty
в ответ на нажатие пользователем комбинации клавиш Ctrl+Alt+Del.
Наконец, вызов sy s_ge t i n f o обслуживает множество разнообразных запро­
сов информации, содержащейся в ядре. Если вы поищете в исходном С-коде
MINIX 3 этот вызов непосредственно по его имени, вы наверняка найдете всего
лишь несколько. Совсем другие результаты подобный поиск принесет в заголо­
вочных файлах: так, в inc l ude /minix / sys l ib . h имеется не меньше 1 3 макро­
сов, задающих sy s_ge t i n f o альтернативные имена. Например:
sys_g e t k in f o ( ds t ) sys_ge t in f o ( GET_KINFO , dst , 0, 0, 0)

Этот макрос используется для возврата менеджеру процессов структуры k in f o ,


определенной в файле inc l ude / m i n i x / type . h (строки 2875-2893) , в о время
запуска системы. Та же информация может потребоваться и в другие моменты.
Например, пользовательской команде ps нужно знать местоположение области
ядра таблицы процессов, чтобы отобразить сведения о состоянии всех процессов
2 . 7 . Системное задание в M I N IX 3 227

системы. Команда посылает запрос менеджеру процессов, который, в свою оче­


редь, получает информацию с помощью варианта sy s_ge t k i n f o вызова sys_
ge t in f o .
Перед тем как закончить обзор типов вызовов ядра, м ы хотели б ы обратить вни­
мание на то, что вызов sys_ge t i n f o является не единственным вызовом, для
которого в файле inc lude /rninix / sy s l ib . h определен набор макросов, задаю­
щих альтернативные имена. К примеру, обращение к вызову sys_s devi o, как
правило, выполняется через макросы sy s_insb, sys_insw, sy s_out sb и sys_
out sw. С этими именами проще понять, что делает операция (читает или записы­
вает) и с чем она это делает (байт или слово). Аналогично, вызов sys_irqc t l
обычно выполняется макросами sys_i rqenaЫ e , sys_i rqdi s aЫ e и рядом
других. Подобные макросы упрощают чтение кода, а также облегчают работу про­
граммистам, автоматически подставляя в вызовы постоянные аргументы.

2 . 7 . 2 . Реал изация систем ного задания


Системное задание компилируется и з заголовочного файла sys t ern . h и файла
sy s t ern . с с исходным С-кодом; оба файла расположены в каталоге kerne l / .
Кроме того, исходные файлы подкаталога ke rne l / sy s t ern / компилируются
в специальную дополнительную библиотеку. Такая структура вполне обоснован­
на. Несмотря на то что рассматриваемая в книге версия MINIX 3 является уни­
версальной, данная операционная система может иметь и специальное примене­
ние, к примеру, обеспечивать встроенную поддержку портативного устройства.
В этом случае имеет смысл урезать операционную систему. Так, если в устройст­
ве отсутствует жесткий диск, необходимость в файловой системе отпадает. Как
мы видели при рассмотрении файла kerne l / conf i g . h, компиляция ядра может
быть выборочной. Подключение кода, поддерживающего вызовы ядра, на по­
следнем шаге компиляции при компоновке с библиотекой упрощает создание за­
казной системы.
Реализация поддержки каждого вызова ядра в отдельном файле упрощает рабо­
ту с программным обеспечением. Однако эти файлы в определенной степени из­
быточны, и включение их в книгу потребовало бы не менее 40 дополнительных
страниц. Все файлы из каталога / kerne l / sy s t ern вы можете найти на компакт­
диске и веб-сайте MINIX 3.
Мы начнем с изучения файла kerne l / sy s t ern . h (строка 9600). В нем находят­
ся прототипы функций, соответствующих большинству вызовов ядра из табл. 2.5.
Кроме того, имеется прототип do_unu s e d это прототип функции, выполняе­
-

мой при обращении к вызову, не поддерживаемому ядром. Некоторые типы сооб­


щений, перечисленные в табл. 2.5, относятся к определенным в файле макросам
(строки 9625-9630). Это примеры функций, обслуживающих несколько вызовов.
Перед тем как переходить к коду файла sys t ern . с, обратите внимание на век­
тор вызовов c a l l_vec и определение макроса rnap в строках 9745-9749. Вектор
c a l l_vec представляет собой массив указателей на функции. Из него выбира­
ется функция, обслуживающая сообщение, с использованием в качестве индекса
228 Глава 2 . Процессы

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


кратно применяется в MINIX 3. Макрос rnap - удобный инструмент инициали­
зации такого массива. Определение rnap выполнено так, что попытка раскрытия
макроса с некорректным значением аргумента приводит к объявлению массива
отрицательного размера. Разумеется, это является недопустимым и вызывает
ошибку компилятора.
Верхним уровнем системного задания является процедура sy s_t a s k. После вы­
зова, инициализирующего массив указателей на функции, процедура sys_t a s k
выполняется циклически. Она ожидает сообщения, выполняет несколько прове­
рок его корректности, выбирает функцию, обслуживающую вызов в зависимости
от типа сообщения, возможно, генерирует ответное сообщение и повторяет цикл
до тех пор, пока работает MINIX 3 (строки 9768-9796). Проверка включает две
части: сначала функция просматривает запись в таблице привилегий и убежда­
ется в том, что вызывающему процессу разрешено использовать данный вызов,
а затем определяет допустимость типа вызова. Выбор обслуживающей функции
выполняется в строке 9783. В качестве индекса массива c a l l_vec используется
номер вызова, вызываемая функция определяется адресом, содержащимся в соот­
ветствующей ячейке массива, аргументом функции является указатель на сооб­
щение, а возвращаемое значение представляет собой код состояния. Код со­
стояния EDONTREPLY указывает на отсутствие необходимости генерировать
ответное сообщение, в противном случае отправка ответного сообщения выпол­
няется в строке 9792.
Как видно из рис. 2.22, при запуске MINIX 3 системное задание находится в на­
чале очереди с самым высоким приоритетом. По этой причине имеет смысл ини­
циализировать массив обработчиков прерываний и список таймеров сигналов
с помощью функции i n i t i a l i z e системного задания ( строки 9808-98 1 5 ) .
Поскольку системное задание, как было отмечено ранее, используется для разре­
шения прерываний от имени драйверов, расположенных в пользовательском
пространстве и нуждающихся в реагировании на прерывания, подготовка табли­
цы будет вполне уместной. Кроме того, системное задание устанавливает тай­
меры, если другим системным процессам необходимы сигналы синхрониза­
ции. По этой причине инициализировать списки таймеров здесь так же логично.
В продолжение инициализации в строках 9822-9824 все элементы массива c a l l_
vec заполняются адресами процедуры do_unu s ed, используемой при неподдер­
живаемом вызове ядра. Строки 9827-9867 включают несколько расширений
макроса rnap, каждое из которых записывает адрес функции в соответствующий
элемент массива c a l l_vec.
В оставшейся части файла sys t ern . с находятся функции, объявленные откры­
тыми (рuЫ i с ) и доступные для различных подпрограмм, обслуживающих вы­
зовы ядра, а также для других областей ядра. Например, первая такая функция,
ge t_pr iv (строка 9872), используется функцией do_pr ivc t l , поддерживаю­
щей вызов sys_privc t l ядра. Кроме того, ge t_priv вызывается самим ядром
при формировании элементов таблицы процессов для процессов загрузочного об­
раза. Возможно, имя get_priv несколько сбивает с толку: функция не получает
2 . 7 . Системное задан ие в M I N IX 3 229

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


привилегий и присваивает ее вызывающему процессу. Возможны два варианта.
Первый - каждый системный процесс получает собственную запись в табли­
це привилегий. Второй - если таблица недоступна, процесс не может стать сис­
темным. Всем пользовательским процессам соответствует одна общая запись
в таблице.
Функция get_randomne s s (строка 9899) используется для получения исход­
ных чисел для генератора случайных чисел, реализованного в MINIX 3 в виде
символьного устройства. Новейшие процессоры Pentiurn включают внутренний
счетчик циклов и поддерживают команду ассемблера, считывающую его значе­
ние. По возможности применяется эта команда, в противном случае вызывается
функция, считывающая регистр микросхемы часов.
Функция s end_s i g генерирует уведомление системному процессу после уста­
новки бита в битовой карте s_s i g_pe nding процесса-получателя сигнала. Бит
устанавливается в строке 9942. Поскольку s_s i g_p end i ng является частью
структуры привилегий, этот механизм может применяться только для уведомле­
ния системных процессов. Все пользовательские процессы имеют общую запись
в таблице привилегий, а, следовательно, поля, подобные s_s i g_pending, не мо­
гут быть совместными и не используются. Перед вызовом s end_s i g выполняет­
ся проверка, что адресат является системным процессом. Обращение к функции
s end_s i g возможно лишь в результате вызова sys_k i l l либо при передаче
символьной строки функцией kpr int f ядра. В первом случае отправитель оп­
ределяет, является ли получатель системным процессом, во втором ядро просто
передает данные для печати одному из процессов - драйверу консоли или драй­
веру журнала; оба процесса являются системными.
Функция cau s e_s i g (строка 9949) посылает сигнал пользовательскому процессу.
Она выполняется, когда пользовательский процесс является получателем сис­
темного вызова sys_k i l l . Функция c au s e_s i g помещена в файл sy s t em . с
потому, что она может быть вызвана непосредственно ядром в ответ на исключе­
ние, вызванное пользовательским процессом. Как и в случае с s end_s i g, в бито­
вой карте получателя должен быть установлен бит активного сигнала, однако
для пользовательского процесса эта карта находится не в таблице привилегий,
а в таблице процессов. Кроме того, следует вывести получателя из состояния
готовности с помощью вызова l o c k_de queue, а его флаги (в том числе и в таб­
лице процессов) должны быть обновлены и указывать на то, что процессу будет
подан сигнал. Затем посылается сообщение, однако не пользовательскому про­
цессу, а менеджеру процессов, в ведении которого находится все, что касается
сигнализации, связанной с пользовательскими процессами.
Далее следуют три функции, поддерживающие вызов sys_umap ядра. Процессы
обычно оперируют виртуальными адресами, относительными базового адреса оп­
ределенного сегмента. Тем не менее иногда возникает необходимость использо­
вания абсолютных (физических) адресов, например, при копировании областей
памяти, принадлежащих различным сегментам. Виртуальный адрес можно задать
тремя способами. Наиболее употребительный - в виде смещения относительно
230 Глава 2. Процессы

одного из сегментов памяти (текста, данных или стека) , выделенных процессу


и записанных в таблице процессов. Преобразование такой формы виртуального
адреса в физический выполняется функцией umap_l o c a l (строка 9983).
Второй вид адресации памяти - обращение к области, находящейся за предела­
ми сегментов процесса, однако за которую процесс несет определенную ответст­
венность. Примеры - видеодрайвер и драйвер Ethernet, с которыми связывается
область памяти в диапазоне OxaOOOO-Oxffff, зарезервированном для устройств
ввода-вывода. Еще один пример - драйвер памяти, управляющий виртуальным
диском и обеспечивающим доступ к любой области памяти через устройства
/ dev / mem и dev / kmem. Преобразование описанных адресов в физические вы­
полняется функцией umap_r emo t e (строка 1 0025).
Наконец, адрес может указывать на память B I O S , которая включает область
ниже значения 2 Кбайт (под областью, куда загружается MINIX 3) и диапазон
Ox90000-0xfffff (над областью MINIX 3) плюс зарезервированное пространство
ввода-вывода. Преобразование такого адреса может, как и в предыдущем слу­
чае, выполняться функцией umap_r emo t e , однако функция umap_Ь i o s (стро­
ка 10047) проверяет, принадлежит ли на самом деле адрес диапазону BIOS.
Последняя функция, определенная в файле sys t em . с , - vi r tua l_c opy (стро­
ка 1007 1 ). Большую часть ее тела составляет инструкция swi t ch, использующая
описанные функции umap_ * для преобразования виртуальных адресов в физи­
ческие. Преобразование выполняется как адреса источника, так и адреса прием­
ника. Фактическое копирование осуществляется вызовом ассемблерной функ­
ции phy s_copy (строка 1 0 1 2 1 ) из файла k l iЬ3 8 6 . s .

2 .7 . 3 . Реал изация системной библ иотеки


Исходный код функции с именем вида do_xy z находится в файле kerne l /
sy s t em/ do_xy z . с . Файл Make f i l e из каталога kerne l / содержит строку
cd sys t em && $ ( МАКЕ ) - $ ( МAKEFLAG S ) $@

Эта строка компилирует все файлы каталога kerne l / sy s t em / в библиотеку


sy s t em . a каталога kerne l / . Когда управление передается в главный каталог
ядра, следующая строка Make f i l e ищет эту локальную библиотеку первой при
компоновке объектных файлов ядра.
На компакт-диске мы привели два файла из каталога kerne l / sy s t em / . Они
были выбраны потому, что соответствуют двум основным видам поддержки,
предоставляемым системным заданием. Первый вид - это доступ к структурам
данных ядра от имени системного процесса, выполняемого в пользовательском
пространстве. В качестве примера такой поддержки мы рассматриваем файл
sys t em/ do_s e t a l arm . с. Второй вид - обслуживание системных вызовов, в ос­
новном обрабатываемых процессами пользовательского пространства, однако
нуждающихся в выполнении некоторых действий внутри ядра. Примером для
нас служит файл sy s t em / do_exe c . с .
Вызов sys_s etal arm ядра аналогичен вызову sys_i rqenaЬle, который упоми­
нался нами при обсуждении обработки прерываний ядром. Вызов sys_i rqenaЫe
2 . 7 . Системное задание в M I N IX 3 23 1

устанавливает адрес обработчика, который вызывается при появлении соответ­


ствующего прерывания. В роли обработчика выступает функция gene r i c_
hand l e r системного задания, генерирующая уведомление драйверу устройства,
который должен отреагировать на прерывание. Файл sys t ern / do_s e t a l arrn . с
(строка 1 0200) содержит код, управляющий таймерами по принципу, аналогич­
ному прерываниям. Вызов sys_s e t a l a rrn ядра инициализирует таймер для
системного процесса в пользовательском пространстве, которому необходимо
получать сигналы синхронизации, и предоставляет функцию, уведомляющую
пользовательский процесс при истечении таймера. Вызов также может отменить
ранее запланированную передачу сигнала, указав ноль в поле сообщения, соот­
ветствующему времени истечения. Операция проста - в строках 10230- 10232
извлекается информация, переданная в сообщении. Наиболее важными ее эле­
ментами являются время срабатывания таймера и процесс, который должен
узнать об этом. У каждого системного процесса имеется собственная структура
таймера в таблице привилегий. В строках 1 0237- 1 0239 осуществляется доступ
к структуре данных, и в нее вводится номер процесса и адрес функции, которая
будет запущена по истечении таймера.
Если таймер уже запущен, вызов sys_s e t a l arrn возвращает в ответном сооб­
щении оставшееся до истечения время. Если возвращенное значение равно ну­
лю, таймер неактивен. Допустимо несколько вариантов. Первый - таймер может
быть деактивирован: для этого в его поле exp_t irne необходимо записать специ­
альное значение, TMR_NEVER. С точки зрения С-кода оно представляет собой
большое целое число, поэтому его проверка выполняется во время проверки ис­
течения таймера. Таймер может указывать прошедшее время; это маловероятно,
но легко проверяется. Таймер может также указывать время в будущем. В любом
случае ответ содержит нулевое значение, иначе возвращается оставшееся до ис­
течения время (строки 1 0242 - 1 0247).
Наконец, выполняется запуск или перезапуск таймера. На данном уровне для
этого в нужное поле структуры таймера записывается желаемое время истечения
и вызывается другая функция. Разумеется, для перезапуска таймера записывать
значение не нужно. Скоро мы познакомимся с функциями s e t и re s e t ; они
расположены в файле исходного кода таймерного задания. Поскольку и систем­
ное, и таймерное задания находятся внутри образа ядра, доступны все функции,
объявленные в них открытыми (рuЫ i с ) .
В файле do_s e t a l arrn . с определена еще одна функция - c au s e_a la rrn. Это
сторожевая подпрограмма, адрес которой хранится в каждом таймере. При исте­
чении таймера сторожевая функция вызывается. Все, что она делает - генериру­
ет уведомление процессу, номер которого хранится в структуре таймера. Таким
образом, сигнал синхронизации внутри ядра преобразуется в сообщение систем­
ному процессу, нуждающемуся в этом сигнале.
Немного отвлечемся и вспомним, что несколькими страницами ранее (в том числе
и в этом разделе) мы говорили о сигналах синхронизации, необходимых про­
цессам. Если сейчас это не вполне ясно и вы не понимаете, что такое сигнал
синхронизации и как обстоит дело с таймерами несистемных процессов, вам
232 Глава 2 . Процессы

поможет следующий раздел, посвященный таймерному заданию. В операционной


системе так много взаимосвязанных компонентов, что практически невозможно
выстроить их описание, избежав ссылок на еще неизученные части. Особенно
это относится к реализации. Если бы мы не имели дело с реальной операционной
системой, мы бы, вероятно, не стали вдаваться в такие детали. В чисто теоретиче­
ском описании вы едва ли столкнулись бы с таким понятием, как системное зада­
ние. Об ограничении доступа системных компонентов, расположенных в пользо­
вательском пространстве, к привилегированным ресурсам, таким как прерывания
и порты ввода-вывода, можно было бы попросту забыть.
Последним файлом каталога kerne l / sy s t ern / , который мы рассмотрим под­
робно, является do_exec . с (строка 1 0300). Большая часть работы системного
вызова ехес выполняется менеджером процессов. Сначала менеджер процессов
устанавливает для новой программы стек, содержащий аргументы и окружение,
а затем передает указатель стека ядру при помощи вызова sy s_exe c ядра, кото­
рый обрабатывается функцией do_exec (строка 10618). Указатель стека записы­
вается в область таблицы процессов, принадлежащую ядру, и, если новый процесс
использует дополнительный сегмент, вызывается ассемблерная функция phys_
rnerns e t из файла k l iЬ3 8 6 . s , затирающая все данные, которые могли сохранить­
ся в памяти после предыдущего процесса (строка 1 0330).
С вызовом ехес связана одна необычная деталь. Процесс, выполнивший этот
вызов, передает сообщение менеджеру процессов и блокируется. Другой систем­
ный вызов мог бы снять блокировку с процесса, послав ему ответ, однако вызов
ехес этого не делает, поскольку загруженный образ не ожидает ответа. В ре­
зультате разблокирование процесса выполняется самой функцией do_e x e c
(строка 1 0333). В следующей строке функция l o c k_enqueue готовит новый об­
раз к запуску, предотвращая возможные условия гонок. Наконец, выполняется
сохранение командной строки, чтобы пользователь смог увидеть процесс, вос­
пользовавшись командой р s или нажав функциональную клавишу для отобра­
жения данных таблицы процессов.
Завершая обсуждение системного задания, рассмотрим его роль в типичной ус­
луге, предоставляемой операционной системой, - доставке данных по системно­
му вызову read. Когда пользователь выполняет вызов r e ad, файловая система
проверяет свой кэш на наличие требуемого блока. Если блок обнаружить не
удается, она посылает сообщение драйверу диска, чтобы загрузить его в кэш,
а затем - сообщение системному заданию, чтобы скопировать блок пользова­
тельскому процессу. В худшем случае для чтения блока понадобится 1 1 сообще­
ний, в лучшем случае -4, как показано на рис. 2.24. На рис. 2.24, а сообще­
ние З запрашивает у системного задания исполнить инструкции ввода-вывода,
сообщение 4 является подтверждением. При появлении аппаратного прерыва­
ния системное задание уведомляет об этом ожидающий драйвер сообщением 5.
Сообщения 6 и 7 это запрос на копирование данных в кэш файловой системы
-

и ответ на него. Сообщение 8 говорит файловой системе о готовности данных,


сообщения 9 и 10 -это запрос на копирование данных из кэша в пользователь­
ский процесс и ответ на него. Наконец, сообщение 11 это ответ пользователю.
-
2 . 8 . Таймерное задание в M I N IX 3 233

На рис. 2 . 24, б данные уже находятся в кэше, сообщения 2 и 3 представляют


собой запрос на их копирование пользовательскому процессу и ответ на него.
Эти сообщения вносят задержки и являются •расплатой� за высокую степень
модульности архитектуры MINIX 3 .

-------' з ------- ,
(...... Прерывание (...... Прерывание /
- _ _ _ _ _ ......

а б
Рис. 2 . 24. Доставка дан н ых по систем ному в ызову read: а - худш и й вариант чтения блока
требует 11 сообще н и й , б - луч ш и й вариант чтения блока требует 4
сообщения

Вызовы ядра для копирования данных, вероятно, используются в MINIX 3 наи­


более интенсивно. Мы уже знакомы с функцией v i r tu a l_copy - частью опе­
рационной системы, фактически выполняющую эту работу. Один из методов
борьбы с неэффективностью механизма передачи сообщений - упаковка в одно
сообщение нескольких запросов. Именно так и делают вызовы sys_v i rvc opy
и sys_phy svc opy ядра. В обоих случаях сообщение содержит указатель на век­
тор, задающий несколько блоков, которые необходимо скопировать из одной об­
ласти памяти в другую. Оба сообщения обрабатываются функцией do_vc opy.
Она выполняет цикл, где извлекает адреса источника и получателя, размеры
блоков, а затем последовательно вызывает функцию phys_c opy до тех пор, пока
все операции копирования не будут завершены. В следующей главе мы увидим,
что дисковые устройства способны аналогично обрабатывать несколько опера­
ций передачи данных по единственному запросу.

2 . 8 . Та ймерное задан ие в M I N IX 3
Часы, также называемые таймерами, необходимы любой системе разделения
времени в силу ряда причин . Например, они хранят время и препятствуют мо­
нопольному захвату ресурсов процессора одним процессом. Таймерное задание
в M INIX 3 имеет определенное сходство с драйвером устройства, поскольку
управляется аппаратно генерируемыми прерываниями. Тем не менее часы не отно­
сятся ни к блочным устройствам, таким как диски, ни к символьным устройствам,
234 Глава 2. Процессы

таким как терминалы. В каталоге / dev / MINIX 3 не предлагает интерфейса для


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

2 . 8 . 1 . Ап паратное обеспечение часов


В компьютерах применяют два типа часов. Оба радикально отличаются от привыч­
ных нам наручных или настенных часов. Простейшие часы подключены к элек­
трической линии с напряжением 1 1 О или 220 В и генерируют прерывание в каждом
цикле изменения напряжения (с частотой 50 или 60 Гц). Такие часы практиче­
ски невозможно встретить в современном компьютере.
Другой тип часов состоит из трех компонентов - кварцевого осциллятора, счет­
чика и регистра временного хранения (рис. 2.25). Кварцевый кристалл, разрезан­
ный надлежащим образом и помещенный под напряжение, способен генериро­
вать периодический сигнал с очень высокой точностью, как правило, в диапазоне
5-200 МГц (это зависит от самого кристалла). Как минимум один такой осцил­
лятор присутствует в каждом компьютере. С его помощью генерируются сигна­
лы синхронизации для различных схем компьютера. Этот сигнал поступает на
счетчик, заставляя его значение уменьшаться. При достижении нулевого значе­
ния генерируется прерывание процессора. Если заявлено, что частота часов ком­
пьютера превышает 200 МГц, то, чаще всего, в нем используются более медлен­
ные часы с умножителем частоты.

Кварцевый осциллятор

--1 D I1-------.

1.-.,...1-..,1--г-1-тl---.-1--.1--.-1.,..l-,l,-.,.1 -,. 1---.-1-.1---.-1--.1-., °'""'" ' д•'Р"••�°""""' ори """"°" " " """""'

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
t Временный регистр используется
для загрузки счетчика

Рис. 2 . 2 5 . П рограм м и руемые часы

Программируемые часы, как правило, имеют несколько режимов работы. В ре:ж:и­


ме однократного срабатывания при запуске часов значение регистра временного
хранения копируется в счетчик, а затем счетчик уменьшается на 1 при каждом
импульсе от кристалла. Когда значение счетчика становится нулевым, генерируется
прерывание, и часы останавливаются до тех пор, пока снова не будут запущены
программно. В режиме тактового меандра после генерации прерывания значение
регистра временного хранения автоматически копируется в счетчик, и процесс
циклически повторяется до бесконечности. Периодические прерывания называ­
ются тактами часов, или тиками.
2 . 8 . Таймерное задание в M I N IX 3 235

Преимущество программируемых часов заключается в том, что частотой генери­


руемых ими прерываний можно управлять программно. При использовании
кристалла с частотой 1 МГц импульсы подаются на счетчик с периодом 1 мкс.
Если разрядность регистров часов равна 1 6, прерывания могут генерироваться
с интервалом от 1 до 65 536 мкс. Микросхемы программируемых часов, как пра­
вило, содержат двое или трое часов с возможностью независимого программиро­
вания, а также поддерживают множество других возможностей (например, уве­
личение значения счетчика вместо уменьшения, отключение прерываний и т. д.).
Чтобы текущее время не стиралось при отключении питания, большинство ком­
пьютеров оснащено резервными часами, питающимися от аккумулятора. Резерв­
ные часы используют схемы с низким питанием, типичные для цифровых часов.
Данные резервных часов считываются во время запуска компьютера. При отсут­
ствии резервных часов система может попросить пользователя ввести текущее
время. Кроме того, существует стандартный протокол считывания времени с уда­
ленного сервера. Каким бы способом время ни было введено в систему, оно пе­
реводится в число секунд, прошедших с момента всеобщеzо ск:оординированноzо
времени ( Universal Coordinated Time, UTC) - полуночи 1 января 1970 года (рань­
ше употреблялся термин время по Гринвичу - Greenwich Mean Time) или с како­
го-либо другого начала отсчета. В UNIX и MINIX используется всеобщее скоор­
динированное время. Такты часов генерируются самой системой, и по истечении
секунды реальное время увеличивается на единицу. В MINIX 3 и большинстве
UNIХ-подобных систем не принимаются в расчет 23 секундных скачка, произо­
шедших с 1970 года. Эта неточность не считается существенной. Как правило,
системы оснащены прикладными программами, позволяющими устанавливать
часы вручную, считывать время резервных часов и синхронизировать системные
часы с другими.
Отметим, что во всех I В М-совместимых компьютерах, за исключением самых
ранних, имеется отдельная схема часов, обеспечивающая тактовыми сигналами
процессор, внутренние шины данных и прочие компоненты. Частоту именно
этих сигналов имеют в виду люди, когда говорят о быстродействии процессора,
измеряя его в мегагерцах для устаревших компьютеров и в гигагерцах для со­
временных систем. Базовая схемотехника кварцевых кристаллов, осцилляторов
и счетчиков осталась неизменной, однако требования изменились настолько, что
современные компьютеры оснащают независимыми часами для управления про­
цессором и учета времени.

2 . 8 . 2 . П рограмм ное обеспечение часов


Все, что делает аппаратное обеспечение часов, - генерирует прерывания через
известные промежутки времени. Остальные действия, связанные со временем,
должны выполняться программно драйвером часов. Конкретные обязанности
драйвера часов зависят от системы, однако чаще всего включают следующее:
+ хранение даты и времени;
+ прекращение выполнения процессов при истечении отведенного им времени;
236 Глава 2. Процессы

+ учет коэффициента использования процессора;


+ обработка системного вызова a l arm от пользовательских процессов;
+ предоставление сторожевых таймеров компонентам системы;
+ профилирование, наблюдение и сбор статистики.
Первая функция часов, хранение даты и времени (также называемых реальным
временем), проста. Все, что она делает, - увеличивает счетчик на единицу каж­
дый такт часов, как было сказано ранее. Единственная проблема кроется в раз­
рядности счетчика. Если тактовая частота часов составляет 60 Гц, переполнение
32-разрядного счетчика наступит уже через два года. Таким образом, 32-раз­
рядного значения недостаточно для хранения числа тактов с 1 января 1970 года.
К разрешению этой проблемы имеется три подхода. Первый - воспользоваться
64-разрядным счетчиком. Это увеличивает накладные расходы на поддержку
таймера, поскольку его обслуживание выполняется много раз в секунду. Второй
подход - использование вспомогательного счетчика. Время хранится в виде чис­
ла секунд, а не тактов часов и увеличивается только тогда, когда вспомогатель­
ный счетчик насчитывает полную секунду. Поскольку 2 32 секунд - это больше,
чем 136 лет, такой метод будет исправно работать до 22 века.
Третий подход предлагает считать такты не с фиксированной точки отчета, а с мо­
мента загрузки системы. После чтения резервных часов или ввода текущего време­
ни пользователем время загрузки подсчитывается и сохраняется в любой подхо­
дящей форме. Чтобы получить текущее время, достаточно сложить сохраненное
время со значением счетчика. Все три подхода иллюстрирует рис. 2.26.

I• 64 бита •I k- 32 бита -+! k- 32 бита -+!


1 Время суток в тиках 1
� 1 � 1 Счетчик в тиках 1
Время суток Число тиков
в секундах в текущую
секунду Время загрузки
системы в секундах
а б в

Рис. 2 . 26 . Три варианта помержки текущего времени


Вторая функция часов - прекращение выполнения процессов по истечении от­
веденного им времени. Каждый раз при запуске процесса планировщик должен
инициализировать счетчик значением, равным кванту и выраженным в тактах
часов. При каждом прерывании от часов их драйвер уменьшает текущее значение
счетчика на единицу. Как только счетчик становится нулевым, драйвер часов об­
ращается к планировщику с тем, чтобы тот выбрал другой процесс.
Третья функция часов - учет коэффициента использования процессора. Самый
точный способ сделать это - запустить вместе с процессом второй таймер, от­
личный от основного таймера системы. При остановке процесса значение тайме­
ра можно считать и определить, как долго выполнялся процесс. Во избежание
искажений, значение второго таймера должно сохраняться каждый раз при пре­
рывании и восстанавливаться с возобновлением работы процесса.
2. 8 . Тай мерное задание в M I N IX 3 237

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


процессора - ввести глобальную переменную, содержащую указатель на запись
текущего процесса в таблице процессов. С каждым тактом часов поле записи теку­
щего процесса увеличивается на единицу и такт <�:присваивается� использовавшему
его процессу. У такого подхода есть небольшой недостаток: если во время вы­
полнения процесса произойдет большое количество прерываний, они будут за­
считаны как такты использованного процессорного времени, хотя в момент пре­
рывания процесс, по сути, не делает полезной работы. Точный учет потребления
процессора при прерываниях слишком накладен и применяется редко.
В MINIX 3 и многих других операционных системах процесс может попросить
систему послать ему предупреждение через определенный интервал времени.
Как правило, предупреждение имеет форму сигнала, прерывания, сообщения
и т. д. Пример приложения, нуждающегося в предупреждениях, - программы,
ведущие передачу данных по сети: если подтверждение о приеме пакета удален­
ной стороной не получено в течение заданного времени, следует повторить пере­
дачу пакета. Еще один пример - электронные системы обучения: если студент
не отвечает на вопрос, через определенный интервал времени правильный ответ
появляется на экране.
Если бы в распоряжении драйвера часов было достаточно таймеров, он мог бы
использовать отдельный таймер для каждого запроса. Однако это невозможно
на практике. В результате драйверу приходится поддерживать множество вир­
туальных таймеров на основе единственных физических часов. Один из спо­
собов такой поддержки - ведение таблицы, содержащей время срабатывания
всех активных таймеров, и переменной, хранящей время <�:ближайшего� таймера.
Каждый раз при обновлении реального времени драйвер проверяет, истекло ли
время у <�:ближайшего� таймера. Если да, из таблицы выбирается следующий
<�:ближайший� таймер.
Если число активных сигналов велико, эффективнее объединить все активные
запросы таймера в связанный список, упорядоченный по времени, как показано
на рис. 2.27. Каждый элемент списка определяет, через сколько тактов часов
после срабатывания предыдущего таймера должен сработать текущий таймер.
В приведенном примере сигналы должны быть сгенерировать в моменты вре­
мени 4203, 4207, 42 13, 42 1 5 и 4 2 1 6.

Текущее время Следующий сигнал


Начало 1 4200 1 0
списка таймеров

Рис. 2 . 27 . Имитация нескольких таймеров с помощью единственных часов


В момент, изображенный на рис. 2.27, только что сработал один из таймеров.
Следующее прерывание произойдет через 3 тика, поэтому в поле следующего
сигнала загружено число 3. С каждым тиком значение этого поля уменьшается
238 Глава 2. П роцессы

на единицу. Когда оно становится равным нулю, генерируется сигнал, соответст­


вующий первому элементу списка, после чего элемент удаляется. Затем в по­
ле следующего сигнала загружается значение нового первого элемента списка -
в данном случае 4. Во многих случаях использовать абсолютное время удобнее,
чем относительное, и именно такой поход применяется в MINIX 3.
Обратите внимание на то, что при прерывании от часов драйвер выполняет це­
лый ряд действий - увеличение реального времени, уменьшение кванта и его
проверка на равенство нулю, учет коэффициента использования процессора,
уменьшение счетчика сигналов. Каждая из этих операций должна быть тщатель­
но продуманна и выполняться очень быстро, поскольку все они повторяются
много раз в секунду.
Компонентам операционной системы также нужны таймеры, называемые сторо­
жевыми. При изучении жесткого диска мы увидим, что пробуждающий вызов
планируется каждый раз, когда контроллеру диска подается команда - в случае
сбоя это позволяет сделать попытку восстановления. Дисковые драйверы дискет
используют таймеры для ожидания разгона и остановки механизма. Некоторые
принтеры с перемещаемой печатающей головкой способны печатать со скоро­
стью 1 20 символов в секунду (8,3 миллисекунды на символ), однако не всегда
успевают возвратить головку к левой границе за 8,3 миллисекунды, поэтому
драйвер терминала вынужден ждать после ввода символа возврата каретки.
Механизм, используемый драйвером часов для поддержки сторожевых таймеров,
тот же, что и для пользовательских сигналов. Единственное различие состоит
в том, что при срабатывании таймера драйвер часов не генерирует сигнал, а об­
ращается к процедуре, поддерживаемой вызывающим процессом и являющейся
частью его кода. При разработке MINIX 3 это стало проблемой, поскольку целью
операционной системы было изъятие драйверов из адресного пространства ядра.
Проблему решили, возложив на системное задание, находящееся внутри ядра,
функции по установке сигналов от имени некоторых процессов пользователь­
ского пространства и уведомлению этих процессов. В будущем мы планируем
усовершенствовать этот механизм.
Последняя функция часов в нашем списке - профилирование. Некоторые опе­
рационные системы предоставляют пользовательским программам возможность
построения гистограммы счетчика команд, позволяющей определить расход вре­
мени. Если поддержка профилирования присутствует, на каждом тике драйвер
проверяет, ведется ли профилирование текущего процесса, и если да, подсчиты­
вает двоичное число (диапазон адресов), соответствующий счетчику команд. За­
тем двоичное число увеличивается на единицу. Подобный механизм можно при­
менять и для профилирования самой системы.

2 . 8 . З . Обзор драй вера часов в M I N IX 3


Драйвер часов MINIX 3 находится в файле kerne l / c l o c k . c . В нем можно вы­
делить три функциональные части. Во-первых, как и драйверы устройств, кото­
рые будут рассмотрены нами в следующей главе, драйвер часов имеет циклически
2 . 8 . Таймерное задание в M I N IX 3 239

выполняемый механизм заданий, ожидающий сообщения и вызывающий под­


программы, выполняющие запрашиваемые в сообщениях действия. Однако в тай­
мерном задании участие этого механизма сведено к минимуму. Использование
сообщений приводит к значительным задержкам, вклад в которые вносит пере­
ключение контекста. По этой причине к сообщениям прибегают лишь в случаях,
когда необходимо выполнить большой объем работы. Таймерное задание прини­
мает лишь один вид сообщений, имеет одну подпрограмму их обработки, а по за­
вершении обслуживания ответное сообщение не генерируется.
Вторая существенная часть драйвера часов - обработчик прерывания, активи­
руемый 60 раз в секунду и обеспечивающий базовую поддержку времени. Он об­
новляет значение счетчика тактов часов с момента загрузки системы и сравнива­
ет его со временем следующего истечения таймера. Кроме того, обработчик
обновляет счетчики израсходованного процессом времени - общего и в текущем
кванте. Если процесс израсходовал свой квант или сработал таймер, обработчик
генерирует сообщение, поступающее в главный цикл задания. В противном слу­
чае никакого сообщения не передается. Цель стратегии обработчика - в каждом
тике выполнять минимум действий и завершаться как можно быстрее. Затратное
основное задание активизируется только при большом объеме работы.
Третья часть драйвера часов представляет собой набор подпрограмм, осуществ­
ляющих общую поддержку, однако не вызываемых в ответ на прерывания от
таймера ни обработчиком, ни главным циклом задания. Одна из этих подпро­
грамм объявлена закрытой (privat e ) и вызывается перед входом в главный
цикл. Она инициализирует часы - записывает в микросхему данные, обеспечи­
вающие генерацию прерываний через желаемые промежутки времени. Под­
программа инициализации также помещает адрес обработчика прерываний так,
чтобы воспользоваться им при срабатывании прерывания от таймера по линии 8,
и включает эту линию.
Остальные подпрограммы файла c l ock . с объявлены открытыми (puЬ l i c) и мо­
гут вызываться любым кодом ядра. Файл c l ock . с не содержит обращений к этим
подпрограммам; в основном их использует системное задание для обслуживания
системных вызовов, связанных с временем. Подпрограммы выполняют такие дей­
ствия, как чтение счетчика тактов часов с момента загрузки системы, синхрониза­
ция по каждому тику, чтение регистра микросхемы часов, синхронизация с мик­
росекундным разрешением. Имеются подпрограммы для сброса и переустановки
таймеров. Кроме того, одна из подпрограмм вызывается при завершении работы
MINIX 3. Она устанавливает параметры аппаратного таймера, требуемые BIOS.

Та й мерное задание
Главный цикл таймерного задания принимает единственный тип сообщений,
HARD_INT, поступающих от обработчика прерываний. Все остальные сообщения
считаются ошибочными. Более того, главный цикл принимает сообщения не для
всех прерываний от таймера, хотя подпрограмма приема сообщений называется
do_c l o ckt i ck. Последняя вызывается только при необходимости планирова­
ния процесса или при истечении таймера.
240 Глава 2. П роцессы

Обработчик прерываний от таймера


Обработчик прерываний от таймера запускается каждый раз, когда счетчик мик­
росхемы часов достигает нулевого значения и генерирует прерывание. Именно
обработчик выполняет основную работу по поддержке времени. В MINIX 3 вре­
мя хранится так, как показано на рис. 2.26, в. Тем не менее в файле c l o c k . с
используется лишь счетчик тикав, прошедших со времени последней загрузки
системы; время загрузки хранится в другом месте. Программное обеспечение ча­
сов пользуется только текущим счетчиком и поддерживает системный вызов,
запрашивающий реальное время. Дальнейшая обработка выполняется одним из
серверов. Такой подход соответствует стратегии MINIX 3, направленной на пе­
ренос функциональности в пользовательское пространство.
Обработчик прерываний обновляет значение локального счетчика при каждом
прерывании. Если прерывания запрещены, такты теряются. В некоторых случа­
ях этот эффект можно компенсировать. Подсчет потерянных тактов возможен
с использованием глобальной переменной, значение которой складывается с ло­
кальным счетчиком, а при наступлении прерывания и активации обработчика
счетчик сбрасывается в ноль. Мы увидим, как это делается, во время изучения
реализации.
Обработчик также изменяет значения переменных, расположенных в таблице
процессов, с целью учета времени процессов и управления ими. Он посылает со­
общение таймерному заданию лишь в случае, если в текущий момент следую­
щий запланированный таймер истек либо закончился квант процесса. Все, что
делает обработчик, - выполняет простые целочисленные операции: арифмети­
ческие, сравнения, логические ( И/ИЛИ), присваивания. Такие операции с лег­
костью переводятся компилятором в машинный код. В худшем случае соверша­
ется 5 сложений или вычитаний, 6 сравнений и несколько логических операций
и присваиваний в конце процедуры. Задержки, связанные с вызовами подпро­
грамм, равны нулю.

Сторожевые таймеры
Несколько страниц назад мы оставили открытым вопрос о том, как обеспечить
пользовательские процессы сторожевыми таймерами, которые обычно представ­
ляют как пользовательские процедуры, исполняемые по истечении таймера.
Очевидно, в MINIX 3 это попросту невозможно. Тем не менее мы можем постро­
ить мост между ядром и пользовательским пространством с помощью сигналов
синхронизации.
Сейчас самое время разобраться в том, что представляет собой сигнал синхрони­
зации. Сигнал может генерироваться вне зависимости от того, какой процесс ис­
полняется в текущий момент. То же касается и активации обычного сторожевого
таймера. Говорят, что такие события происходят асинхронно. Сигнал синхрони­
зации доставляется в виде сообщения и может быть получен при выполнении
адресатом вызова rec e i ve. Мы говорим о синхронности потому, что адресат ожи­
дает сигнал. Когда отправитель уведомляет адресата о сигнале с помощью проце­
дуры no t i fy, отправителю не нужно блокироваться, а адресату - беспокоиться
2 . 8 . Таймерное задание в M I NIX З 24 1

о том, что сигнал будет пропущен. Если адресат не находится в ожидании, сооб­
щения процедуры not i fy сохраняются. Для этой цели используется битовая
карта, где каждый бит соответствует возможному отправителю.
Сторожевые таймеры используют поле s_a l arm_t imer типа t imer_t , имею­
щееся в каждом элементе таблицы привилегий. Каждому системному процессу
в этой таблице соответствует свой элемент. Чтобы установить таймер, систем­
ный процесс, находящийся в пользовательском пространстве, выполняет вызов
sys_s e t a l arm, обрабатываемый системным заданием. Системное задание на­
ходится в пространстве ядра и, таким образом, может инициализировать таймер
от имени вызывающего процесса. Инициализация включает размещение в нуж­
ном поле адреса процедуры, запускаемой при истечении таймера, и ввод таймера
в список таймеров, как показано на рис. 2.27.
Разумеется, исполняемая процедура также должна находиться в пространстве
ядра. Это не является проблемой. Системное задание располагает сторожевой
функцией, c au s e_a l arm, генерирующей пользователю синхронное уведомле­
ние в момент срабатывания с помощью процедуры not i fy. Данный сигнал мо­
жет вызвать сторожевую функцию в пользовательском пространстве. «Настоя­
щая• сторожевая функция находится внутри ядра, а пользовательский процесс
всего лишь получает сигнал синхронизации. Подобный алгоритм отличается от
исполнения пользовательской функции таймером. Он сопровождается больши­
ми накладными расходами, однако проще, чем механизм прерываний.
Мы намеренно указали на то, что системное задание устанавливает сигналы от
имени некоторых процессов пользовательского пространства. Описанный меха­
низм применяется лишь к системным процессам. У каждого системного процес­
са есть собственная запись в таблице привилегий, а все несистемные процессы
используют единственную общую запись. Области таблицы привилегий, кото­
рые не могут использоваться совместно, например битовая карта активных уве­
домлений и таймер, недоступны пользовательским процессам вообще. Выход из
ситуации следующий: менеджер процессов управляет таймерами от имени поль­
зовательских процессов . Аналогичным образом системное задание управляет
таймерами от имени системных процессов.
Когда пользовательский процесс выполняет системный вызов a l arm, чтобы
установить сигнал, он обрабатывается менеджером процессов, конфигурирую­
щим таймер и помещающим его в список таймеров. Менеджер процессов просит
системное задание послать ему уведомление, когда для первого таймера в списке
будет назначено время истечения. Помощь менеджеру процессов требуется лишь
при изменении начального элемента списка таймеров - из-за того, что первый
таймер истек или был выключен, либо из-за нового вызова, таймер которого дол­
жен оказаться в списке первым. Все это делается для поддержки системного
вызова a l arm, определенного стандартом POSIX. Подлежащая исполнению
процедура находится в адресном пространстве менеджера процессов. Во время
выполнения инициировавшему вызов пользовательскому процессу передается
сигнал, а не уведомление.
242 Глава 2 . П роцессы

М иллисекундные задержки
В файле c l o ck . с имеется процедура, обеспечивающая микросекундное разре­
шение. Различным устройствам ввода-вывода требуются задержки длительно­
стью всего лишь несколько микросекунд, которые невозможно обеспечить на
практике при помощи сигналов и интерфейса передачи сообщений. Счетчик,
используемый для генерации прерываний от таймера, может быть считан впрямую.
Его уменьшение выполняется с интервалом, приблизительно равным 0,8 мкс,
а значение достигает нуля 60 раз в секунду (один раз за 1 6,67 мс). Для того
чтобы использовать значение счетчика для задержек устройств ввода-вывода,
его необходимо опрашивать процедурой, находящейся в пространстве ядра, од­
нако драйверы устройств размещены за его пределами. В настоящее время эта
функция используется только в качестве источника исходных данных для ге­
нератора случайных чисел. Возможно, она окажется более полезной в системе
с высоким быстродействием, но это - вопрос будущего.

Службы времени
В табл. 2.6 перечислены службы файла c l ock . с , прямо или косвенно предостав­
ляющие различные услуги. Несколько функций объявлены открытыми (puЬ l i c ),
что дает возможность вызывать их из ядра или системного задания. Все остальные
службы доступны только косвенно - с помощью системных вызовов, в конеч­
ном счете, обрабатываемых системным заданием. Остальные системные процес­
сы могут косвенно обращаться к системному заданию. Пользовательские про­
цессы должны взаимодействовать с менеджером задач, который также прибегает
к службам системного задания.

Таблица 2 . 6 . Службы времени, поддерживаемые драйвером часов


Служба Доступ Ответ Клиенты
get_uptime Вызов функции Такты Ядро или системное задание
set_timer Вызов функции Нет Ядро или системное задание
reset_timer Вызов функции Нет Ядро или системное задание
read_clock Вызов функции Счетчик Ядро или системное задание
clock_stop Вызов функции Нет Ядро или системное задание
Сигнал синхронизации Системный вызов Уведомление Сервер или драйвер, через
системное задание
Сигнал POSIX Системный вызов Сигнал Пользовательский процесс,
через менеджер процессов
Время Системный вызов Сообщение Любой процесс, через
менеджер процессов
Ядро и системное задание могут получить текущее время работы системы, уста­
новить или сбросить таймер, не прибегая к затратному обмену сообщениями.
Ядро и системное задание также вызывают функцию read_c l o ck, считываю­
щую счетчик из микросхемы таймера для получения времени, выраженного в еди­
ницах длительностью приблизительно 0,8 мкс. Функция c l o ck_s t op вызывает­
ся при завершении работы MINIX 3 и восстанавливает тактовую частоту BIOS.
2 . 8 . Тай мерное задание в M I N IX 3 243

Системный процесс (драйвер или сервер) может запросить сигнал синхрониза­


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

2 . 8 . 4 . Реал изация драйвера часов в M I N IX 3


Таймерное задание не использует основные структуры данных; для работы со
временем предназначены несколько переменных. Переменная r e a l t irne ( стро­
ка 1 0462) является главной - она считает такты часов. Глобальная переменная
l o s t_t i cks определена в файле g l o . h (строка 5333). Она предназначена для
использования всеми функциями, выполняемыми в пространстве ядра и способ­
ными запретить прерывания, которые достаточно длинны для того, чтобы один
или более тикав оказались потерянными. В настоящее время переменную l o s t_
t i cks использует функция int 8 6 , расположенная в файле k l iЬ3 8 6 . s . Функ­
ция int 8 6 задействует монитор загрузки для передачи управления в BIOS, и пе­
ред возвращением в ядро монитор возвращает в регистре есх число тикав, затра­
ченных на обслуживание вызова BIOS. Хотя микросхема таймера не приводит
к выполнению обработчика прерывания при обслуживании запроса BIOS, мони­
тор загрузки может поддерживать время с помощью BIOS.
Драйвер часов осуществляет доступ к ряду других глобальных переменных. Он
использует переменную proc__p t r - указатель на запись таблицы процессов,
соответствующую текущему процессу, p r ev__p t r - указатель на предыдущий
процесс и Ь i l l__p t r - указатель на процесс, которому начисляется процессор­
ное время. С помощью этих переменных драйвер часов оперирует различными
полями записей , в частности p_u s e r_t irne и p_sy s_t irne, для учета и для
уменьшения оставшегося времени кванта соответственно.
При запуске MINIX 3 выполняется вызов всех драйверов. Большинство из них
совершают действия по инициализации, пытаются получить сообщение и бло­
кируются. Драйвер часов, c l o ck_t a s k (строка 1 0648), не является исключени­
ем. Сначала он обращается к функции i n i t_c l o ck, чтобы установить частоту
часов равной 60 Гц. После получения сообщения драйвер вызывает функцию
do_c l o ckt i ck s при условии, что типом сообщения является HARD_INT (стро­
ка 1 0486). Все остальные сообщения считаются ошибками.
Функция do_c l o c k t i c k s (строка 1 0497), противореча собственному назва­
нию, не вызывается в каждом такте часов. Она вызывается в случае, если об­
работчик прерывания определил наличие важной работы. Одно из условий
запуска do_c l o c k t i cks - окончание кванта текущего процесса. Если процесс
может быть вытеснен (системное и таймерное задания не входят в их число), вы­
зывается функция l o c k_d equ e u e , а затем - функция l o c k_enqueue (стро­
ки 1 05 1 0 - 1 0 5 1 2). В результате процесс удаляется из очереди, снова переводится
244 Глава 2. П роцессы

в состояние готовности и перепланируется. Еще одна причина запуска do _


c l ockt i cks - истечение сторожевого таймера. Таймеры и их связанные спи­
ски используются в MINIX 3 столь интенсивно, что для их поддержки создана
целая библиотека функций. Библиотечная функция tmr s_exp t ime r s , вызы­
ваемая в строке 1 0 5 1 7 , запускает сторожевые функции для всех истекших тайме­
ров и деактивирует их.
Функция i n i t_c l o c k (строка 1 0529) вызывается только один раз - при вызо­
ве таймерного задания. В коде можно найти несколько мест и сказать: «выполне­
ние MINIX 3 начинается отсюда! • Это - одно из них; часы являются обязатель­
ным компонентом любой системы с вытесняющей многозадачностью. Функция
ini t_c l o c k сначала записывает 3 байта в микросхему часов, задавая ее режим
и нужное значение счетчика в главном регистре, а затем регистрирует свой но­
мер процесса, номер прерывания и адрес обработчика для надлежащего об­
служивания. Наконец, функция разрешает контроллеру прерываний принимать
прерывания от таймера.
Следующая функция, c l o ck_s t op, отменяет результаты инициализации мик­
росхемы часов. Она объявлена открытой ( рuЫ i с ) и не вызывается из файла
c l ock . с. Она помещена сюда из-за очевидного сходства с ini t_c l o ck. Един­
ственный ее вызов выполняется системным заданием при завершении работы
MINIX 3 для передачи управления монитору загрузки.
Одновременно с запуском функции i n i t_c l o c k (а точнее, через 1 6,67 мс)
происходит первое прерывание от таймера, и далее прерывания генерируются
с частотой 60 раз в секунду до тех пор, пока работает MINIX 3. Вероятно, код
функции c l o c k_hand l e r ( строка 1 05 5 6 ) выполняется чаще, чем любой дру­
гой в MINIX 3. По этой причине разработчики сделали так, чтобы он завер­
шался как можно быстрее. Единственная ситуация, в которой необходим вы­
зов подпрограмм, - использование устаревшего компьютера IBM PS/2 (строка
1 0586 ). Обновление текущего времени (в тактах часов) выполняется в стро­
ках 1 0589 - 1 059 1 . После этого обновлению подлежат учетное время и время
пользователя.
Внутри обработчика принимается несколько не вполне очевидных решений .
В строке 1 06 1 0 выполняются две проверки, и если хотя бы одна из них оказыва­
ется успешной, таймерному заданию посылается уведомление. Функция do_
c l ockt i ck, вызываемая таймерным заданием, повторяет проверки с целью оп­
ределения необходимых действий. Этот шаг нужен потому, что уведомление не
способно нести информацию о различных условиях. Рассмотреть и оценить аль­
тернативы мы предлагаем читателю самостоятельно.
Оставшаяся часть файла c l oc k . с содержит уже упомянутые функции. Функ­
ция ge t_upt ime (строка 1 0620) возвращает значение rea l t ime, видимое только
функциями файла c l o ck . с. Функции s e t_t imer и r es e t_t imer используют
другие функции библиотеки, ответственные за детали манипуляции с цепочкой
таймеров. Наконец, функция read_c l o ck считывает и возвращает текущее зна­
чение регистра счетчика микросхемы часов.
Рез ю ме 245

Рез ю ме
Чтобы скрыть эффект прерываний, операционная система предоставляет кон­
цептуальную модель, в которой параллельно выполняются логически упорядо­
ченные процессы. Процессы могут взаимодействовать друг с другом при помощи
примитивов, таких как семафоры, мониторы и сообщения. Назначение примити­
вов в том, чтобы гарантировать, что никакие два процесса не окажутся в крити­
ческой секции единовременно. Процессы могут находиться в состоянии вы­
полнения, готовности и приостановки, а также переходить из одного состояния
в другое, когда тот или иной процесс исполняет один из примитивов взаимодей­
ствия между процессами.
Примитивы необходимы для решения таких проблем, как проблема производи­
теля и потребителя, проблема обедающих философов, проблема писателей и чи­
тателей. Но даже при использовании примитивов необходимо быть осторожным,
во избежание ошибок и взаимных блокировок. Известно много различных алго­
ритмов планирования, таких как циклический алгоритм, приоритетный алго­
ритм, алгоритм с многоуровневыми очередями, алгоритм управления политика­
ми планирования.
Операционная система MINIX 3 поддерживает концепцию процесса и предо­
ставляет примитивы для взаимодействия между процессами. Сообщения не бу­
феризуются, поэтому вызов s end завершается успехом только после того, как
адресат получит сообщение. Аналогично, вызов r e c e i ve завершается лишь
тогда, когда сообщение уже отправлено. В противном случае сделавший вызов
процесс переходит в состояние ожидания. MINIX 3 также поддерживает небло­
кирующую разновидность сообщений - уведомления, передаваемые с помощью
примитива no t i fy. Попытка послать уведомление адресату, не находящемуся
в состоянии ожидания, приводит к установке бита, вызывающего восстановле­
ние уведомления при вызове r e c e i ve в будущем.
В качестве примера потока сообщений рассмотрим вызов r ead, выполняемый
пользовательским процессом. Процесс посылает запрос на чтение в виде сообще­
ния, адресованного файловой системе. Если данные не удалось обнаружить в кэше,
файловая система запрашивает их чтение с диска у драйвера, а сама переходит
в состояние блокировки. При дисковом прерывании системное задание получает
уведомление, позволяющее ему передать ответ драйверу диска, а тому, в свою
очередь, - файловой системе. После этого файловая система запрашивает систем­
ное задание скопировать данные из кэша, в который был помещен требуемый
блок, пользовательскому процессу. Перечисленные шаги иллюстрирует рис. 2.24.
За прерыванием может последовать переключение процессов. В случае прерывания
в элементе таблицы процессов, соответствующем прерванному процессу, созда­
ется стек, в который помещается вся информация, необходимая для его переза­
пуска. При перезапуске процесса указателю стека присваивается адрес элемента
таблицы процессов, далее выполняются команды, восстанавливающие регистры
процессора, а затем - команда i r e t d. Выбор элемента таблицы процессов, адрес
которого помещается в указатель стека, возлагается на планировщика.
246 Глава 2. П роцессы

Прерывания во время исполнения ядра невозможны. Если при выполнении ядра


возникло исключение, вместо стека таблицы процессов используется стек ядра.
По завершении обслуживания прерывания процесс перезапускается.
Алгоритм планирования в MINIX 3 использует множество очередей с различны­
ми приоритетами. Системные процессы, как правило, имеют наивысший при­
оритет, а приоритет пользовательских процессов, чаще всего, невысок. Тем не
менее приоритет каждого процесса может задаваться отдельно. Зациклившийся
процесс может быть временно понижен в приоритете и восстановлен после того,
как другие процессы получат возможность выполниться. Для изменения уровня
приоритета процесса внутри заданных границ используется команда ni c e. Вы­
бор процессов на исполнение происходит по циклическому алгоритму, а кванты
задаются для каждого процесса индивидуально. Тем не менее процесс, сначала
блокированный в середине кванта, а затем ставший готовым, помещается в на­
чало очереди и выполняется все время, оставшееся до окончания прерванного
кванта. Это сокращает время отклика для процессов, требующих ввода-вывода.
Драйверам устройств и серверам предоставлен больший квант, поскольку пред­
полагается, что их исполнение обычно прерывается блокированием. Тем не ме­
нее даже системные процессы вытесняются в случае, если их выполнение ока­
зывается слишком долгим.
Образ ядра включает системное задание, обеспечивающее взаимодействие про­
цессов, которые выполняются в пользовательском пространстве . Системное
задание поддерживает серверы и драйверы устройств, совершая от их имени
привилегированные операции. В состав ядра MINI X 3 также входит таймер­
ное задание. Оно не является драйвером устройства в обычном смысле: поль­
зовательские процессы не могут обращаться с часами так же, как с другими
устройствами.

В опросы и задания
1 . Почему многозадачность является основным требованием для современных
операционных систем?
2. Каковы три основных состояния процесса? Кратко опишите смысл каждого
из них.
3. Представьте, что вы разрабатываете усовершенствованную компьютерную
архитектуру, в которой процессы переключаются аппаратно, а не с помощью
прерываний. Какая информация потребуется процессору? Опишите возмож­
ную реализацию аппаратного переключения процессов.
4. На всех существующих компьютерах как минимум часть обработчиков пре­
рываний написана на ассемблере. Почему?
5. Измените рис. 2.2, добавив в него два состояния процесса - «новый� и «За­
вершенный� . Процесс находится в состоянии «новый� после создания, а в со­
стоянии «завершенный� - после окончания своей работы.
Вопросы и задания 247

6. В тексте утверждалось, что модель, представленная на рис. 2.4, а, не подходит


для файлового сервера с кэшем в памяти. Почему? Может ли каждый про­
цесс иметь собственный кэш?
7. В чем заключается фундаментальное различие между процессом и программ­
ным потоком?
8. Если в системе используются программные потоки, выделяется ли каждо­
му из них собственный стек или стеки имеются лишь у процессов? Поясните
ответ.
9. Что такое условия гонок?
10. Приведите пример условия гонок, которое возможно при покупке двумя пас­
сажирами билетов на один и тот же самолет.
1 1 . Напишите сценарий оболочки, которая создает файл, содержащий после­
довательные числа, путем считывания последнего числа, прибавления к нему
единицы и записывания результата в конец файла. Запустите одну копию
сценария в качестве фонового процесса и одну в качестве приоритетного
-

процесса. Сколько времени пройдет, прежде чем возникнут условия гонок?


Что в данной модели является критической секцией? Измените сценарий,
чтобы избежать условий гонок. Подсказка: воспользуйтесь следующим сцена­
рием, чтобы заблокировать файл данных
In f i l e f i l e . l o c k

12. Имеется инструкция


In f i l e f i l e . l oc k

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


для таких пользовательских программ, как сценарий в предыдущем задании?
Почему?
13. Будет ли работать решение для активного ожидания с применением перемен­
ной turn (см. листинг 2 . 1 ) в случае двух процессоров, совместно использую­
щих общую память?
14. Рассмотрим компьютер, в котором не поддерживается инструкция t s l , но
есть инструкция для обмена содержимого регистра и слова памяти за одну
неделимую операцию. Можно ли применить эту инструкцию для написания
программы ent er_r e g i on, аналогичной показанной в листинге 2.3?
15 . Опишите коротко, как реализовать семафоры в операционной системе, умею­
щей блокировать прерывания.
1 6. Покажите, как можно реализовать �считающие• семафоры (то есть способ­
ные хранить произвольные значения) при помощи только бинарных семафо­
ров и обычных машинных команд.
17. В п. 2.2.4 была описана ситуация с высокоприоритетным процессом Н и низко­
приоритетным процессом L, которая приводила к зацикливанию процесса Н.
Может ли возникнуть подобная проблема, если вместо приоритетного плани­
рования использовать циклическое? Поясните.
248 Глава 2. П роцессы

18. Синхронизация в мониторах происходит с использованием переменных со­


стояния и двух специальных операций, wa i t и s i gna l . Более общая форма
синхронизации предполагает один примитив wa i tunt i l с произвольным бу­
левым предикатом в качестве параметра. Например:
wa i tunt i l Х< О o r y + z < n

В данном случае примитив s i gnal больше не нужен. Эта схема существенно


более общая, чем схемы Хоара и Хансена, и тем не менее она не используется.
Почему? Подсказка: подумайте о реализации.
19. В ресторане быстрого питания есть четыре категории обслуживающего пер­
сонала:
1) работники, принимающие заказы;
2) повара, готовящие пищу;
3) специалисты по упаковке блюд;
4) кассиры, принимающие у клиентов деньги и выдающие еду.
Каждой из категорий можно сопоставить последовательный процесс взаимо­
действия. Какой формой взаимодействия между процессами они пользуют­
ся? Свяжите эту модель с процессами в MINIX 3.
20. Рассмотрим систему передачи сообщений через почтовые ящики. При попыт­
ке послать сообщение в полный ящик или получить сообщение из пустого
ящика процесс не блокируется, а получает код ошибки. Затем процесс повто­
ряет попытку, пока она не окажется успешной. Приведет ли подобная схема
к условиям гонок?
2 1 . Почему в процедуре t ake_ f o r k s решения задачи обедающих философов
(см. листинг 2 . 1 0) переменной состояния присваивается значение HUNGRY?
22. Рассмотрим процедуру put_forks в листинге 2. 10. Пусть переменной s tate [ i ]
присваивается значение TH INKING после двух вызовов процедуры t e s t , а не
до. Как это повлияет на решение для трех философов? Для 1 00 философов?
23. Проблему читателей и писателей можно формулировать по-разному, в зави­
симости от того, какие процессы и в какое время могут быть запущены. Тща­
тельно опишите три варианта проблемы, в каждом из которых предоставляет­
ся (или не предоставляется) преимущество одной из категорий. В каждом
варианте укажите, что происходит, когда читающий или пишущий процесс
готов обратиться к базе данных, и что происходит, когда процесс заканчивает
работу с базой.
24. Компьютеры CDC 6600 в состоянии обрабатывать до 10 процессов ввода-вы­
вода одновременно благодаря интересной форме циклического планирова­
ния, называемой разделением процессора. Переключение между процессами
происходит после каждой команды, поэтому команда 1 поступает от первого
процесса, команда 2 от второго и т. д. Переключение процессов произво­
-

дится аппаратно, и издержки равны нулю. Если в отсутствие других процес­


сов процессу для выполнения работы нужно Т секунд, сколько ему потребу­
ется времени в случае п процессов?
Воп росы и з адания 249

25. Обычно планировщики с циклическим алгоритмом поддерживают список про­


цессов, готовых к работе, причем каждый процесс находится в списке в единст­
венном экземпляре. Что произойдет, если процесс окажется в списке дважды?
Существует ли причина, по которой подобное изменение может оказаться по­
лезным?
26. Измерения показали, что время выполнения среднестатистического процесса
до блокировки ввода-вывода равно Т. На переключение между процессами
уходит время S, которое теряется впустую. Напишите формулу расчета эф­
фективности для циклического планирования с квантом Q, принимающим
следующие значения:
1) Q = оо;

2) Q > Т;
3) S < Q < Т;
4) Q = S;
5) Q около о.
27. Запуска ожидают пять заданий. Предполагаемое время выполнения заданий
составляет 9, 6, 3, 5 и Х. В каком порядке их следует запустить, чтобы мини­
мизировать среднее время отклика? ( Ответ должен зависеть от Х.)
2 8 . Пять пакетных заданий, А, В , С, D , Е, поступают в компьютерный центр прак­
тически одновременно. Ожидается, что время их выполнения составит 1 О, 6,
2, 4 и 8 мин. Их установленные приоритеты равны 3, 5, 2, 1 и 4, причем 5 -
высший приоритет. Определите среднее время оборота для каждого из сле­
дующих алгоритмов планирования, пренебрегая потерями на переключение
между процессами:
1) циклическое планирование;
2 ) приоритетное планирование;
3) первым пришел - первым обслужен (в порядке 10, 6, 2, 4, 8);
4) самое короткое задание - первое.
В первом случае предполагается, что система многозадачная и каждому за­
данию достается справедливая доля процессорного времени. В остальных
случаях считается, что в каждый момент времени выполняется одно задание,
работающее вплоть до завершения. Все задания ограничены исключительно
возможностями процессора.
29. Процессу, запущенному в СТSS-системе, для завершения необходимо 30 кван­
тов. Сколько раз он будет выгружен на диск, учитывая самый первый раз
(прежде, чем он бьш запущен)?
30. Для предсказания времени выполнения используется алгоритм старения
с а = 1/2. Предыдущие четыре значения времени составляли 40, 20, 40 и 15 мс
(первое значение - самое давнее). Оцените следующее время выполнения.
3 1 . На рис. 2 . 1 0 изображена схема трехуровневого планирования в системе па­
кетной обработки. Можно ли использовать его в интерактивной системе без
новых поступающих заданий? Как?
250 Глава 2 . П роцессы

32. Предположим, что программные потоки, изображенные на рис. 2. 13, а, вы­


полняются в следующем порядке: один поток процесса А, один поток процес­
са В и т. д. Сколько последовательностей программных потоков возможно
для первых четырех вариантов планирования?
33. В гибкой системе реального времени поддерживаются четыре события с пе­
риодами 50 , 1 00, 200 и 250 мс. Предположим, что эти события требуют 35, 20,
1 0 и х мс процессорного времени соответственно. Найдите ма�<симальное зна­
чение х, при котором система является планируемой.
34. В процессе работы MINIX 3 использует переменную proc_p t r , содержащую
указатель на ячейку текущего процесса в таблице процессов. Зачем?
35. В MINIX 3 сообщения не буферизуются. Поясните, почему это приводит к про­
блемам с прерываниями от таймера и клавиатуры.
36. Когда в MINIX приостановленному процессу отправляется сообщение, вызы­
вается подпрограмма r e ady, которая помещает процесс в одну из очередей
планировщика. Эта подпрограмма начинается с блокирования прерываний.
Почему?
37. В MINIX функция rnini_re c содержит цикл. Зачем?
38. Схема планирования в MINIX в целом соответствует рис. 2.22 с различными
приоритетами для разных классов. Нижний класс (пользовательские процес­
сы) планируется по циклической схеме, но задания и серверы выполняются
всегда, пока не попадут в состояние блокировки. Могут ли низкоуровневые
процессы зависать? Почему да (или почему нет)?
39. Подходит ли MINIX для решения задач реального времени (таких как сбор
данных)? Если нет, то что можно сделать для исправления ситуации?
40. Предположим, у вас имеется операционная система, предоставляющая сема­
форы. Реализуйте систему обмена сообщениями. Напишите подпрограммы
для отправки и приема сообщений.
4 1 . Студент, изучающий антропологию и посещающий занятия по информатике,
занялся исследовательским проектом, цель которого - выяснить, можно ли
научить африканс1<их бабуинов избегать взаимной блокировки. Он нашел
глубокий каньон и протянул через него веревку, чтобы бабуины могли пере­
браться на другую сторону на руках. За один раз могут перебраться несколь­
ко бабуинов, если все они движутся в одном направлении. Если на веревке
одновременно оказываются бабуины, двигающиеся на восток и на запад, воз­
никнет взаимная блокировка (бабуины зависают посреди веревки), так как
один бабуин не может перебраться через другого. Поэтому если бабуин хочет
перебраться, он сначала должен проверить, что на веревке нет тех, кто дви­
жется в обратном направлении. Напишите программу с использованием се­
мафоров, которая не допускала бы блокировки. Не беспокойтесь о том, что
группа бабуинов, движущихся на восток, может бесконечно удерживать тех,
кто хочет двигаться на запад.
42. Решите предыдущую задачу, но теперь постарайтесь избежать зависаний.
Для этого, когда бабуин хочет перебраться на восток и при этом на веревке
Вопросы и задания 25 1

уже висят несколько обезьян, движущихся на запад, бабуин ждет, когда ве­
ревка освободится. Другое условие: нужно запретить для остальных движе­
ние на запад, пока бабуин не перейдет на восток.
43. Решите задачу обедающих философов с помощью мониторов, а не семафоров.
44. Добавьте в ядро MINIX 3 код, который бы подсчитывал, сколько раз процесс
(или задание) i обратится к процессу (или заданию) j. Сделайте так, чтобы
полученная матрица печаталась по нажатию клавиши F4.
45. Измените планировщик MINIX 3 так, чтобы он отслеживал, сколько времени
каждый процесс занимал процессор последний раз. Когда все задания и сер­
веры освобождают процессор для пользовательских процессов, выбирайте
тот из них, который меньше всех тревожил процессор.
46. Измените MINIX 3 так, чтобы каждый процесс мог явно задавать приори­
тет своих дочерних процессов с использованием нового системного вызова
s e t pr i o r i ty с параметрами p i d и p r i or i ty.
47. Перепишите макросы hwint_rna s t er и hwint_s l ave из файла rnpxx3 8 6 . s
так, чтобы действия, выполняемые функцией s ave, были встроены в код. На­
сколько увеличился объем кода? Можете ли вы измерить прирост производи­
тельности?
48. Поясните все выводимые элементы команды sys env системы MINIX 3. Если
в вашем распоряжении нет работающей копии операционной системы, вос­
пользуйтесь листингом 2 . 1 4 .
4 9 . Обсуждая инициализацию таблицы процессов, м ы упомянули, что некоторые
компиляторы С могут генерировать несколько лучший код при сложении ад­
реса массива с константой, нежели при индексировании. Напишите пару ко­
ротких С-программ, чтобы проверить это.
50. Измените систему M INIX 3 так, чтобы в ней осуществлялся сбор сведений
о передаче сообщений с указанием отправителей и получателей. Напишите
программу, выполняющую подобный сбор и печатающую статистику в удоб­
ном для восприятия виде.
Глава 3
В вод - в ы вод

Одна из важнейших функций операционной системы состоит в управлении


всеми устройствами ввода-вывода компьютера. Операционная система должна
давать этим устройствам команды, перехватывать прерывания и обрабатывать
ошибки. Она должна также предоставить простой и удобный интерфейс между
устройствами и остальной частью системы. Интерфейс, насколько это возможно,
должен быть одинаковым для всех устройств (он не должен зависеть от при­
меняемого оборудования). Программное обеспечение ввода-вывода составляет
существенную часть операционной системы. Тому, как операционная система
управляет устройствами ввода-вывода, и посвящена эта глава.
Глава организована следующим образом. Сначала мы рассмотрим некоторые
базовые понятия, касающиеся устройств ввода-вывода, а затем в общих чертах
познакомимся с соответствующим программным обеспечением. Программное
обеспечение ввода-вывода может быть структурировано в виде уровней, каждо­
му из которых отведен строго очерченный круг задач. Мы рассмотрим все уров­
ни, чтобы понять, что они делают и как согласуются друг с другом.
Далее следует раздел, посвященный взаимной блокировке. Мы дадим строгое
определение этому понятию, покажем, как возникают взаимные блокировки,
представим две модели для их анализа и обсудим некоторые алгоритмы их пре­
дотвращения.
Затем мы сделаем общий обзор механизма ввода-вывода в MINIX 3, в том числе
прерываний, драйверов устройств, зависимого и независимого от оборудования
ввода-вывода. Далее мы детально изучим различные устройства ввода-вывода:
диски, клавиатуры и дисплеи. Каждое устройство будет рассмотрено как с аппа­
ратной, так и с программной точки зрения.

3 . 1 . Ап паратное обеспе ч ение


вв ода - вы в ода
Разные специалисты рассматривают аппаратное обеспечение ввода-вывода по­
разному. Инженеры-электронщики видят микросхемы, проводники, источники
питания, двигатели и прочие физические компоненты. Программисты, в первую
очередь, обращают внимание на интерфейс, предоставляемый программному
3 . 1 . Ап паратное обеспечение ввода-вывода 253

обеспечению, - команды, воспринимаемые аппаратурой, выполняемые ею функ­


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

3 . 1 . 1 . Устройства ввода- вы вода


Устройства ввода-вывода можно грубо разделить на две категории: блочные
и сим.вольные. Блочными называются устройства, хранящие информацию в виде
адресуемых блоков фиксированного размера. Обычно размеры блоков варьиру­
ются от 5 1 2 до 32 768 байт. Важное свойство блочного устройства состоит в том,
что каждый блок может быть прочитан независимо от остальных блоков. Наибо­
лее распространенными блочными устройствами являются диски.
Если приглядеться внимательнее, то окажется, что граница между устройствами,
адресуемыми поблочно, и устройствами, к отдельным составляющим которых
нельзя адресоваться напрямую, строго не определена. Все согласны с тем, что
диск является поблочно адресуемым устройством, так как вне зависимости от
текущего положения головки дисковода всегда можно переместить ее на опреде­
ленный цилиндр и затем считать или записать отдельный блок с нужной дорож­
ки. Рассмотрим теперь накопитель на магнитной ленте (магнитофон), применяе­
мый для хранения резервных копий диска. На ленте хранится последовательность
блоков. Если магнитофону дать команду прочитать некоторый блок, ему потре­
буется перемотать ленту и начать читать данные, пока процесс не дойдет до за­
прашиваемого блока. Эта операция подобна поиску блока на диске с той лишь
разницей, что она занимает значительно больше времени. Кроме того, в зависи­
мости от накопителя и формата хранящихся на нем данных запись отдельного
произвольного блока в середине ленты не гарантирована. Попытка использовать
магнитные ленты в качестве блочных устройств произвольного доступа явилась
бы в какой-то степени натяжкой: никто их не использует таким образом.
Другой тип устройств ввода-вывода - символьные устройства. Символьное уст­
ройство принимает или предоставляет поток символов без какой-либо блочной
структуры. Символьное устройство не является адресуемым и не выполняет
операцию поиска. Принтеры, сетевые интерфейсные адаптеры, мыши (для ука­
зания позиции на экране), крысы (для лабораторных экспериментов по психоло­
гии) и большинство других устройств, не похожих на диски, можно рассматри­
вать как символьные устройства.
Такая схема классификации несовершенна. Некоторые устройства просто не по­
падают ни в одну из категорий. Например, часы не являются поблочно адресуе­
мыми. Они также не формируют и не принимают символьных потоков. Вся их
254 Глава 3. Ввод-вывод

деятельность сводится к инициированию прерываний в строго определенные


моменты времени. И все же разделение на блочные и символьные устройства
является достаточно всеобъемлющим и неплохо подходит в качестве основы,
позволяющей добиться, чтобы программное обеспечение операционных систем
не зависело от устройств ввода-вывода. Так, файловая система общается с абст­
рактными блочными устройствами, а зависимую от устройств часть оставляет
низкоуровневому программному обеспечению - драйверам устройств.
Диапазон скорости работы устройств ввода-вывода очень широк (табл. 3 . 1 ) . Это
значительно усложняет программы, призванные обеспечивать качественное об­
служивание устройств со скоростями передачи данных, отличающимися на по­
рядки. Нельзя не отметить и год от года растущее быстродействие большинства
устройств ввода-вывода.

Табл ица З. 1 . Скорости передачи данных некоторых типичных устройств, шин и сетей
Устройство Скорость
Клавиатура 1 0 байт/с
Мышь 1 00 байт/с
Модем 56 Кбайт 7 Кбайт/с
Сканер 400 Кбайт/с
Цифровая камера 4 Мбайт/с
CD-ROM 52х 8 Мбайт/с
Firewire (IEEE 1 394) 50 Мбайт/с
USB 2.0 60 Мбайт/с
Монитор ХGА 60 Мбайт/с
Сеть SONEТ ОС- 1 2 78 Мбайт/с
Gigablt Etherпet 1 25 Мбайт/с
Диск Serial АТА 200 Мбайт/с
Диск SCSI Ultrawide 320 Мбайт/с
Шина PCI 528 Мбайт/с

3 . 1 2 Контроллеры устройств
. .

Устройства ввода-вывода, как правило, состоят из механических и электронных


компонепгов. В большинстве случаев эти компоненты можно логически разделить,
чтобы получить максимально модульную и обобщенную модель. Электронный
компонент называется контроллером устройства, или адаптером. В персональ­
ных компьютерах он обычно имеет вид печатной платы, вставляемой в слот рас­
ширения. Механический компонент - это само устройство. Данная структура
представлена на рис. 3 . 1 .
Плата контроллера обычно снабжается разъемом, к которому может быть под­
ключен кабель, ведущий к самому устройству. Многие контроллеры способны
управлять двумя, четырьмя или даже восемью идентичными устройствами. Если
интерфейс между контроллером и устройством является стандартным, то есть
определен официальным стандартом ANSI , IEEE или I S O, либо фактическим
3 . 1 . Аппаратное обеспечение ввода-вывода 255

стандартом, это упрощает выпуск по отдельности контроллеров и устройств, со­


ответствующих данному интерфейсу. Так, многие компании производят жесткие
диски, соответствующие интерфейсу I D E или S CSI.

Монитор

D
Клавиатура USB CD-ROM Жесткий диск
11111111

Процессор Память Видеоконтроллер Контроллер


жесткого диска

Шина
Рис. 3 . 1 . Модель подключения процессора, памяти, контроллеров и устройств ввода-вывода
Мы упоминаем о различии между контроллером и устройством потому, что опе­
рационная система практически всегда имеет дело с контроллером, а не с самим
устройством. У большинства небольших компьютеров взаимодействие с устрой­
ствами организуется по модели единой шины (см. рис. 3. 1 ) . У больших машин,
мэйнфреймов, применяется другая модель с несколькими шинами, которые об­
служиваются специализированными компьютерами ввода-вывода, называемыми
каналами ввода-вывода. Такая организация позволяет снизить нагрузку на основ­
ной процессор.
Интерфейс между устройством и контроллером часто является интерфейсом
очень низкого уровня. Например, какой-нибудь жесткий диск может быть от­
форматирован по 1 024 сектора на дорожку, с размером секторов по 5 1 2 байт.
В действительности с диска в контроллер поступает последовательный поток би­
тов, начинающийся с заzоловка сектора (преамбулы), за которым следует 4096 бит
в секторе, и, наконец, контрольная сумма, также называемая кодом исправления
ошибок ( Error- Correcting Code, ЕСС). Заголовок сектора записывается на диск
во время форматирования. Он содержит номера цилиндров и секторов, размер
сектора, информацию синхронизации и т. п.
Работа контроллера заключается в преобразовании последовательного потока
битов в блок байтов и в коррекции ошибок, если это необходимо. Обычно блок
байтов собирается бит за битом в буфере контроллера. Затем проверяется кон­
трольная сумма блока, и если она совпадает с указанной в заголовке сектора,
блок полагается считанным без ошибок, после чего он копируется в оператив­
ную память.
Контроллер монитора (видеоконтроллер) также работает как последовательное
побитовое устройство на таком же низком уровне. Он считывает из памят и байты,
содержащие символы, которые следует отобразить, и формирует сигналы, исполь­
зуемые для модуляции луча электронной трубки, заставляющие ее выводить
256 Глава 3 . Ввод-вывод

изображение на экран. Кроме того, видеоконтроллер формирует сигналы, управ­


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

3 . 1 . 3 . Ввод- вы вод с отображением на память


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

1 6-разрядного числа. Процессор может считать регистр управления PORT и со­


хранить результат в своем регистре REG, используя специальную команду ввода­
вывода, например, такую:
IN REG , PORT

Аналогично, следующая команда записывает содержимое регистра REG процес­


сора в регистр управления PORT устройства:
OUT PORT , REG

Так работало большинство первых компьютеров, включая практически все мэйн­


фреймы (в частности, IBM 360 и всех его предков). В этом случае память и область
ввода-вывода имеют разные адресные пространства, как показано на рис. 3.2, а.
3. 1 . Аппаратное обеспечение ввода-вывода 257

Одно адресное Два адресных


Разные адресные пространство пространства
пространства
OxFFFF. ..
�Порты ввода-вывода
1
о .______. "
а б в

Варианты расположения пространства памяти и ввода-вывода: - раздельные


пространства памяти и ввода-вывода; б - ввод-вывод с отображением на память;
Рис. 3 . 2 . а

в - смешанный вариант

В других компьютерах регистры ввода-вывода являются частью обычного адрес­


ного пространства памяти (рис. 3.2, б). Такая организация называется вводом­
выводом с отображением на память. Она была впервые применена в мини-ком­
пьютере PDP- 1 1 . Каждому регистру управления назначается уникальный адрес
памяти, с которым обычная память не связана. Как правило, для регистров управ­
ления выделяются адреса из верхней части адресного пространства. На рис. 3.2, в
представлена смешанная схема, использующая отображенные на память буферы
данных и отдельные порты ввода-вывода для регистров управления. Подобная
архитектура применяется в системах на основе процессора Pentiurn, где диапазон
адресов от 640 Кбайт до 1 Мбайт зарезервирован под буферы данных устройств,
а область портов ввода-вывода занимает первые 64 Кбайт.
Как же функционируют описанные схемы? Во всех случаях процессор, желаю­
щий считать слово, выставляет его адрес на адресные линии шины, а затем вы­
дает сигнал чтения по линии управления. Для того чтобы отличить обращение
к пространству ввода-вывода от обращения к памяти, требуется вторая сиг­
нальная линия. В случае обращения к памяти ответить на запрос должна память,
а в случае обращения к пространству ввода-вывода - соответствующее устройст­
во. При наличии единого адресного пространства (см. рис. 3.2, б) каждый модуль
памяти и каждое устройство ввода-вывода сопоставляют адрес, выставленный
на шину, с диапазоном обслуживаемых адресов. Если адрес попал в диапазон,
устройство отвечает на запрос. Поскольку адреса не присваиваются ни памяти,
ни устройствам ввода-вывода, двусмысленность и конфликты исключены.

3 . 1 . 4 . П рерывания
Как правило, регистры контроллеров содержат один или несколько битов состоя­
ния. Их можно проверить и определить, завершена ли операция вывода и имеются
ли новые данные в устройстве ввода. Цикл, выполняемый процессором и прове­
ряющий бит состояния до готовности устройства принять или передать данные,
называется опросом, или активным ожиданием. Мы познакомились с этой кон­
цепцией в пункте 2.2.3 в контексте работы с критическими секциями (впрочем,
в большинстве случаев активное ожидание нежелательно). Поскольку ожидание
258 Глав а 3. Ввод- вывод

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


очень долгим, активное ожидание допустимо лишь в небольших выделенных од­
нозадачных системах.
В дополнение к битам состояния, многие контроллеры часто используют преры­
вания, которые позволяют сообщить процессору, что регистры готовы для запи­
си или чтения. Мы рассмотрели обработку прерываний процессором в пунк­
те 2. 1 .6. В контексте ввода-вывода вам нужно знать лишь одно: большинство
интерфейсных устройств генерирует вывод, означающий завершение операции
или готовность данных (то есть по смыслу совпадающий с соответствующими
битами состояния), однако возбуждающий определенную линию (линию запро­
са прерывания) системной шины. В результате завершение операции, вызы­
вающей прерывание, останавливает процессор и запускает процедуру обработки
прерывания. Процедура обработки прерывания информирует операционную
систему о том, что ввод-вывод завершен. После этого операционная система мо­
жет проверить биты состояния и убедиться в отсутствии ошибок, считать полу­
ченные данные или инициировать повторную передачу.
Количество входов контроллера прерываний ограничено. Например, у персональ­
ных компьютеров Pentiurn только 15 линий прерывания доступны для устройств
ввода-вывода. Некоторые из контроллеров устаревших компьютеров встроены
в материнскую плату, как, например, контроллер клавиатуры на I B M РС. У тех
контроллеров, что вставляются в разъем на объединительной плате, установить
соответствие между IRQ-сигналом и устройством иногда можно при помощи пе­
ремычек или переключателей. Если пользователь приобретал новую карту, он
был вынужден вручную устанавливать линию прерывания, чтобы избежать ее
конфликта с существующими устройствами. Большинство пользователей со­
вершало в этом ошибки, что, в конечном счете, привело к появлению механизма
автоконфиzурирования ( Plug and Play ) , благодаря которому В I O S самостоя­
тельно назначает устройствам корректные линии прерывания на этапе загрузки
системы.

3 . 1 . 5 . П ря мой доступ к памяти


Независимо от того, поддерживается ли отображение ввода-вывода системы на
память, центральному процессору необходимо адресовать контроллеры уст­
ройств, чтобы обмениваться с ними данными. Процессор может запрашивать
данные у контроллера побайтно, но если требуется получать от устройства боль­
шие блоки (например, при считывании с диска), значительная часть времени
будет потрачена впустую. По этой причине для взаимодействия с памятью при­
меняют другой метод, называемый прямым доступом к памяти (Direct Mernory
Access, DMA). Операционная система может использовать D M A только при
наличии контроллера прямого доступа к памяти ( D МА-контроллера). Боль­
шинство компьютеров оснащено таким контроллером. Иногда D МА-контрол­
лер встраивают в другие контроллеры (например, дисковые), но в этом случае
он требуется каждому устройству. Чаще системы оснащают единственным D MA-
3 . 1 . Аппаратное обеспечение ввода-вывода 259

контроллером, который обычно размещается на материнской плате. Он управля­


ет обменом данными с множеством устройств ввода-вывода, причем зачастую
параллельно.
Где бы физически ни располагался DМА-контроллер, он имеет независимый от
процессора доступ к системной шине (рис. 3.3). DМА-контроллер имеет несколь­
ко регистров, доступных процессору для чтения и записи: регистр адреса, счет­
чик байтов и ряд регистров управления. Последние определяют используемый
порт ввода-вывода, направление обмена данными (чтение или запись), единицу
обмена (байт или слово) и число байтов, передаваемых в одном цикле.

К Накопитель

П роцессор ОМА-контроллер Контроллер диска Основная память

Буфер
1 . П роцессор
п рограммирует
Адрес Ji
ОМА-контроллер Счетчик
J Уп равление J 4. Подтверждение

П рерывание 2. О МА-зап росы З. П ередача


по окончании данных
Шина
Рис . 3 . 3 . Работа О М А - контроллера

Чтобы объяснить принципы функционирования D MA, сначала разберемся, как


осуществляется чтение с диска в отсутствие прямого доступа к памяти. Сначала
контроллер последовательно, бит за битом, считывает блок (один или несколько
секторов ), пока он не окажется во внутреннем буфере контроллера. Далее под­
считывается контрольная сумма и проверяется наличие ошибок. Затем генери­
руется прерывание. Когда операционная система запущена, она может считать
переданный блок из буфера контроллера. Считывание осуществляется цикличе­
ски, побайтно или пословно. После считывания байта ( слова) он сохраняется
в основной памяти, адрес памяти инкрементируется, а счетчик оставшихся эле­
ментов декрементируется. Цикл прекращается, когда значение счетчика стано­
вится равным нулю.
Прямой доступ к памяти изменяет описанную процедуру. Сначала процессор
программирует DМА-контроллер, записывая в его регистры значения, указываю­
щие контроллеру, что и куда передавать (шаг 1 на рис. 3.3). Затем контроллеру
посылается команда считать данные с диска в свой внутренний буфер и сверить
контрольную сумму. После появления в буфере контроллера корректных дан­
ных DMA может приступать к работе.
DМА-контроллер начинает перенос данных, посылая дисковому контроллеру по
шине запрос на чтение (шаг 2). Этот запрос выглядит как обычный запрос на
260 Глава 3. Ввод- вывод

чтение, потому контроллер диска даже не знает, поступил он от центрального


процессора или от DМА-контроллера. Обычно адрес памяти уже находится на
адресной шине, соответственно, контроллер диска всегда в курсе, куда нужно
переслать следующее слово из своего внутреннего буфера. Запись в память яв­
ляется еще одним стандартным циклом шины (шаг 3). Когда запись закончена,
контроллер диска также по шине посылает сигнал подтверждения DМА-кон­
троллеру (шаг 4). Затем D МА-контроллер инкрементирует используемый адрес
памяти и декрементирует значение счетчика байтов. После этого шаги 2-4 по­
вторяются, пока значение счетчика не станет равным нулю. По завершении
цикла копирования D МА-контроллер инициирует прерывание процессора. Опе­
рационной системе не нужно копировать блок с диска в память. Он уже нахо­
дится там.
Возможно, вы задаетесь вопросом, почему контроллер не помещает данные пря­
мо в оперативную память по мере получения их с диска. Другими словами, зачем
ему нужен внутренний буфер? Тому есть две причины. Во-первых, за счет внут­
ренней буферизации контроллер диска может проверить контрольную сумму до
начала переноса данных в память. Если значения не совпадают, формируется
сигнал об ошибке и передача данных не производится.
Во-вторых, дело в том, что как только стартует операция чтения с диска, биты
начинают поступать с постоянной скоростью, независимо от того, готов контрол­
лер диска их принимать или нет. Если контроллер диска попытается писать эти
данные напрямую в память, ему придется делать это по системной шине. Если
при передаче очередного слова шина окажется занятой каким-либо другим уст­
ройством, контроллеру диска придется ждать. Если следующее слово с диска
прибудет раньше, чем контроллер успеет сохранить отложенное, контроллер ли­
бо потеряет предыдущее слово, либо ему придется запоминать его где-либо еще.
Если шина используется интенсивно, контроллер будет вынужден сохранять
сразу несколько слов и выполнять немало служебных действий. При наличии
внутреннего буфера шина не нужна до тех пор, пока не начнется операция пря­
мого доступа к памяти. В результате устройство контроллера диска оказывается
проще, так как при операции прямого доступа к памяти временные параметры не
являются критичными.
Не все компьютеры поддерживают D MA. Главный аргумент против прямого
доступа к памяти состоит в том, что по скорости центральный процессор обычно
значительно превосходит D МА-контроллер и в состоянии выполнить ту же ра­
боту значительно быстрее (если только сдерживающим фактором не является
быстродействие устройства ввода-вывода). При отсутствии другой нагрузки на
быстрый центральный процессор заставлять его ждать, пока медленный DМА­
контроллер выполнит свою работу, бессмысленно. Кроме 'fОГО, компьютер без
DМА-контроллера, но с центральным процессором, вы:rюлняющим все программ­
но, оказывается дешевле, что крайне важно в производстве .компьютеров нижней
ценовой категории, а также встроенных систем.
3. 2. П рограмм ное обеспечение ввода-вывода 261

3 . 2 . П ро г рам м ное обеспе ч ение


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

3 . 2 . 1 . Н азначение програм много


обеспечения ввода- в ывода
Ключевая концепция разработки программного обеспечения ввода-вывода из­
вестна как независи.м,остъ от устройств. Эта концепция означает возможность
написания программ, способных получать доступ к любому устройству ввода­
вывода без предварительного указания конкретного устройства. Соответственно,
программа, читающая данные из входного файла, должна с одинаковым успехом
работать с файлом на дискете, жестком диске или компакт-диске. Причем без
каких-либо изменений в программе. Например, должна иметься возможность
выполнить команду вроде
s o r t < i nput >output

Эта команда должна работать, невзирая на то, что именно указано в качестве
входного устройства - гибкий диск, I D Е-диск, SСSI -диск или клавиатура. В ка­
честве выходного устройства также с равным успехом может быть указан экран,
файл на любом диске или принтер. Все проблемы, связанные с отличиями этих
устройств, должна разрешать операционная система.
Тесно связан с идеей независимости от устройств принцип единообразного и.м,е­
нования. Имя файла или устройства должно быть просто текстовой строкой
или целым числом и никоим образом не зависеть от физического устройства.
В UNIX и MINIX 3 все диски могут быть произвольным образом интегрированы
в иерархию файловой системы, поэтому пользователю не обязательно знать,
какое имя какому устройству соответствует. Например, гибкий диск не запреща­
ется монтировать поверх каталога / u s r / a s t / ba c kup, вследствие чего копи­
рование файла в каталог / u s r / a s t / ba c kup / monday автоматически приведет
к копированию файлов на гибкий диск. Таким образом, все файлы и устройства
адресуются одним и тем же способом - по пути к ним.
Другим важным аспектом программного обеспечения ввода-вывода является
обработка ошибок. Ошибки должны обрабатываться как можно ближе к аппа­
ратуре. Если контроллер обнаружил ошибку чтения, он должен попытаться по
возможности исправить эту ошибку сам. Если он не в силах это сделать, тогда
ошибку обязан обработать драйвер устройства, допустим, попытавш:ись прочи­
тать этот блок еще раз. Многие ошибки бывают временными, например ошибки
чтения, вызванные пылинками на читающих головках. Такие ошибки часто не
воспроизводятся при повторной попытке чтения блока. Только если нижний
262 Глава 3 . Ввод-вывод

уровень пасует перед проблемой, о ней следует информировать верхний уро­


вень. Во многих случаях восстановление после ошибок предпочтительно делать
на нижнем уровне, прозрачно для верхних уровней, то есть так, чтобы вышестоя­
щие уровни даже не подозревали о наличии сбоев.
Еще один ключевой вопрос - способ передачи данных: синхронный (блокирую­
щий) против асинхронного (управляемого прерываниями). Большинство опера­
ций ввода-вывода на физическом уровне являются асинхронными - централь­
ный процессор начинает передачу данных и забывает о ней, пока не появится
прерывание. Пользовательские программы значительно легче писать, применяя
блокирующие операции ввода-вывода, - после обращения к системному вызову
rec e i ve программа автоматически приостанавливается до тех пор, пока данные
не появятся в буфере. Тем, чтобы операции ввода-вывода, в действительности
являющиеся асинхронными, в пользовательских программах выглядели как бло­
кирующие, занимается операционная система.
Говоря о программном обеспечении ввода-вывода, нельзя обойти вниманием буфе ­
ризацию. Часто данные, поступающие с устройства, не могут быть сохранены сра­
зу там, куда они в конечном итоге направляются. Например, когда пакет прихо­
дит по сети, операционная система не знает, куда его поместить, пока не изучит
его содержимое, для чего этот пакет нужно где-то временно пристроить. Кроме
того, для многих устройств реального времени крайне важными оказываются па­
раметры сроков поступления данных (например, для устройств воспроизведения
оцифрованного звука), поэтому полученные данные должны быть помещены
в выходной буфер заранее, чтобы скорость, с которой они извлекаются из буфера
проигрывателем, не зависела от скорости заполнения буфера. Буферизация под­
разумевает копирование данных в значительных количествах, что часто являет­
ся основным фактором снижения производительности операций ввода-вывода.
И последнее - это понятие выделенных устройств и разделяемых устройств. С не­
которыми устройствами ввода-вывода, такими как диски, может одновременно
работать большое количество пользователей. При этом не должно возникать
проблем, когда несколько пользователей одновременно откроют файлы на одном
и том же диске. Другие устройства, такие как накопители на магнитной ленте,
должны предоставляться в монопольное владение одному пользователю, пока он
не завершит свою работу с этим устройством. Если два или более пользователей
одновременно станут писать вперемешку блоки на одну ленту, ничего хорошего
не получится. Введение понятия выделенных (монопольно используемых) уст­
ройств также привносит целый спектр проблем, таких как взаимные блокиров­
ки. Тем не менее операционная система обязана управлять как разделяемыми,
так и выделенными устройствами и преодолевать различные потенциальные про­
блемы самостоятельно.
Эти задачи решаются путем разбиения программного обеспечения ввода-вывода
на четыре уровня.
1. Обработчики прерываний (нижний уровень).
2. Драйверы устройств.
3 . 2 . П рограмм ное обеспечение ввода-вывода 263

3. Независимый от аппаратуры код операционной системы.


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

3 . 2 . 2 . Обработч ики прерываний


Хотя программный ввод-вывод иногда бывает полезен, для большинства опера­
ций ввода-вывода приходится прибегать к прерываниям. Прерывания должны
быть упрятаны как можно глубже во внутренностях операционной системы, что­
бы о них знала как можно меньшая ее часть. Лучший способ завуалировать их
заключается в блокировке драйвера, начавшего операцию ввода-вывода, вплоть
до окончания этой операции и получения прерывания. Драйвер может заблоки­
ровать себя сам, выполнив на семафоре процедуру down, процедуру wa i t на пе­
ременной состояния, процедуру r e c e i ve на сообщении или что-либо подобное.
Когда происходит прерывание, начинает работу обработчик прерываний. По ее
окончании он может разблокировать драйвер-инициатор. В некоторых случаях
это реализуется через процедуру up на семафоре. В других ситуациях обработ­
чик прерываний вызывает процедуру монитора s i gnal с переменной состояния.
Или же он посылает заблокированному драйверу сообщение. В любом случае ре­
зультат один - драйвер разблокируется и продолжает работу. Эта схема лучше
всего работает в драйверах, являющихся процессами с собственными состояни­
ем, стеком и счетчиком команд.

3 . 2 . 3 . Драйверы устройств
Ранее в этой главе мы узнали, что у каждого контроллера устройства есть реги­
стры, в которые можно записывать команды, считывать состояние устройства
или делать и то и другое. Число регистров и смысл команд значительно изменя­
ются от устройства к устройству. Например, драйвер мыши принимает инфор­
мацию о ее перемещении и нажатых кнопках. В то же время драйверу диска
нужно знать о секторах, дорожках, цилиндрах, головках, их перемещении и вре­
мени установки, двигателях и тому подобных вещах.
Поэтому для управления каждым устройством ввода-вывода, подключенным
к компьютеру, требуется специальная программа. Эта программа, называемая
драйвером устройства, часто пишется производителем устройства и распростра­
няется на компакт-дисках вместе с самим устройством. Поскольку для каждой
операционной системы требуются специализированные драйверы, производите­
ли обычно поставляют драйверы для нескольких наиболее популярных операци­
онных систем.
Каждый драйвер обслуживает один тип устройств или более крупный класс сход­
ных устройств. Например, было неплохо иметь один драйвер мыши, несмотря на
264 Глава 3. Ввод-вывод

то, что система поддерживает несколько типов мышей. Дисковый драйвер мог бы
поддерживать несколько типов дисков, различающихся объемами и скоростями,
а также, возможно, компакт-диски. С другой стороны, диск настолько непохож
на мышь, что им, безусловно, нужны разные драйверы.
Чтобы драйвер имел доступ к аппаратной части устройства, то есть к регистрам
контроллера, его традиционно интегрируют в ядро операционной системы. Та­
кой подход обеспечивает максимальную производительность, но минимальную
надежность, поскольку ошибка в любом драйвере устройства способна вывести
из строя всю систему. В MINIX 3 используется другая, более надежная модель.
Как мы увидим, в этой операционной системе каждый драйвер устройства явля­
ется отдельным процессом, выполняющимся в пользовательском пространстве.
Как мы говорили ранее, с точки зрения операционной системы бывают драйверы
для блочных устройств (например, диски) и символьных устройств (например,
клавиатуры и принтеры). Большинство операционных систем определяет два
стандартных интерфейса, которые должны поддерживаться всеми блочными
и всеми символьными устройствами компьютера соответственно. Интерфейсы
включают совокупность процедур, вызываемых операционной системой, чтобы
обеспечить драйверам возможность выполнять свою работу.
Вообще говоря, назначение драйвера в том, чтобы воспринимать абстрактные за­
просы от аппаратно-независимых программ верхнего уровня и сообщать им, что
запрос выполнен. Типичный запрос, поступающий драйверу диска, - считать за­
данный блок данных. При этом если в момент передачи запроса драйвер бездей­
ствует, он сразу начинает работу. Если же драйвер занят, запрос обычно помеща­
ется в очередь и обслуживается по мере возможности.
Первым шагом в обслуживании запроса ввода-вывода является проверка кор­
ректности переданных параметров и при необходимости возврат ошибки. Если
запрос верен, следующий шаг - его преобразование из абстрактного представле­
ния в конкретную форму. Скажем, драйвер диска должен выяснить, где находит­
ся запрошенный блок данных, проверить, работает ли привод диска, находится
ли головка над нужной дорожкой и т. д. Говоря коротко, драйвер должен сам оп­
ределить свою последовательность действий.
После того как необходимые команды определены, драйвер начинает передавать
их устройству через регистры контроллера. Простые контроллеры способны вос­
принимать только по одной команде за раз, а более сложные поддерживают свя­
занный список команд, выполняемых далее без вмешательства операционной
системы.
Когда все команды переданы, ситуация развивается по одному из двух сценари­
ев. Во многих случаях драйвер устройства должен ждать, пока контроллер вы­
полняет для него определенную работу, поэтому он блокируется до поступления
прерывания от устройства. В других вариантах операция завершается без за­
держек, и драйверу не нужно блокироваться. Например, для прокрутки экрана
в символьном режиме требуется записать лишь несколько байтов в регистры
контроллера. Каких-либо физических перемещений нет, и вся операция занима­
ет несколько микросекунд.
3 . 2 . П рограмм ное обеспечение ввода-вывода 265

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


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

3 . 2 . 4 . Н езависимое от устройств
программ ное обеспечение ввода-вывода
Хотя некоторая часть программного обеспечения предназначена для работы с кон­
кретными устройствами, значительная его часть не зависит от устройств. Точ­
ную границу между драйверами и независимым от устройств программным
обеспечением проводит система, так как некоторые функции, которые можно реа­
лизовать независимо от устройств, часто выполняются прямо в драйверах из
различных соображений, в том числе с позиций эффективности. Следующие функ­
ции обычно реализуются независимым от устройств программным обеспечением:
+ единообразный интерфейс для драйверов устройств;
+ буферизация;
+ сообщения об ошибках;
+ захват и освобождение выделенных устройств;
+ обеспечение аппаратно-независимого размера блока.
В MINIX 3 большинство независимых от устройств программ является частью
файловой системы. Файловую систему мы будем изучать в главе 5, а здесь дадим
только краткий обзор, чтобы продемонстрировать некоторые перспективы и луч­
ше объяснить, как работают драйверы.
Главная цель независимого от устройств программного обеспечения - выполне­
ние функций ввода-вывода, общих для всех устройств, и предоставление едино­
образного интерфейса для программ пользовательского уровня. Далее мы рас­
смотрим эти вопросы более подробно.

Ед иноо б разн ы й интерфе й с для дра й веров устро й ств


Основное назначение операционной системы - более или менее унифициро­
ванное представление устройств ввода-вывода и драйверов. Если бы диски,
принтеры, мониторы, клавиатуры и другие устройства имели разные интерфейсы,
266 Глава 3 . Ввод- вывод

подключение к компьютеру каждого нового устройства требовало бы модифи­


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

Драйвер Драйвер Драйвер Драйвер Драйвер Драйвер


диска принтера кл авиатуры диска принтера кл авиатуры
а б
Рис. 3.4.Взаимодействие драйверов с операционной системой : в отсутствие стандартного
а -

интерфейса драйверов; б при наличии стандартного интерфейса драйверов


-

Стандартный интерфейс значительно упрощает ввод в систему нового драйвера


устройства, если последний его поддерживает. Следование стандарту предпола­
гает, что авторы драйвера знают, какие функции должны быть реализованы и ка­
кие вызовы ядра находятся в их распоряжении. На практике устройства разли­
чаются, однако число их типов невелико, и между разными типами зачастую
имеется много общего. Даже блочные и символьные устройства выполняют не­
мало одинаковых функций.
Один из аспектов унификации интерфейса - способ именования устройств вво­
да-вывода. Отображением символических имен устройств на соответствующие
драйверы занимаются аппаратно-независимые программы. Например, в UNIX
и MINIX 3 имя устройства / dev / di s k O однозначно указывает индексный узел
специального файла, а подходящий драйвер определяется по главному номеру
устройства. Этот индексный узел также содержит вспомогательный номер уст ­
ройства, передаваемый в виде параметра драйверу для указания конкретного
диска или раздела диска, к которому относится операция чтения или записи. Все
устройства в системе UNIX имеют главный и вспомогательный номера, по кото­
рым они однозначно идентифицируются. Выбор всех драйверов осуществляется
по главному номеру устройства.
С именованием устройств тесно связан вопрос защиты. Как операционная систе­
ма предотвращает доступ пользователей к устройствам, на который у них нет
прав? В UNIX, MINIX 3 и поздних версиях Windows (например, 2000 и ХР) уст­
ройства представляются в файловой системе в виде именованных объектов, что да­
ет возможность применять обычные правила защиты файлов к устройствам ввода­
вывода. Таким образом, системному администратору легко установить нужные
разрешения для каждого устройства (например, при помощи битов rwx в UNIX).
3 . 2 . П рограмм ное обеспечение ввода- вывода 267

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

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

Захват и освобождение выделенных устро й ств


Некоторые устройства, например привод CD-RW, рассчитаны на монопольное
владение процессом в каждый момент времени. Операционная система должна
рассмотреть запросы на использование такого устройства и либо принять их, либо
отказать в выполнении запроса, в зависимости от доступности запрашиваемого
устройства. Простой способ обработки этих запросов заключается в соответст­
вующей реализации системного вызова open по отношению к специальным фай­
лам. Если устройство недоступно, вызов op en завершится неуспешно. Обраще­
ние к системному вызову c l o s e освобождает устройство.

Обеспечение аппаратно - независимо го размера блока


У различных дисков могут быть разные размеры сектора. Независимое от уст­
ройств программное обеспечение должно скрывать этот факт от верхних уровней
и предоставлять им единообразный размер блока, например, объединяя несколь­
ко физических сегментов в одну логическую сущность. При этом более высокие
уровни имеют дело только с абстрактными устройствами, с одним и тем же
размером логического блока, не зависящим от размера физического сектора.
268 Глава 3 . Ввод- выв од

Некоторые символьные устройства предоставляют свои данные побайтно (на­


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

3 . 2 . 5 . П рограмм ное обеспечение ввода - в ывода


пол ьзовател ьского пространства
Хотя большая часть программного обеспечения ввода-вывода относится к опера­
ционной системе, небольшие его порции состоят из библиотек, скомпонованных
с пользовательскими программами, или даже целых программ, работающих вне
ядра. Системные вызовы, включая системные вызовы ввода-вывода, обычно соб­
раны из библиотечных процедур, например:
count = wri t e ( fd , b u f f e r , nby t e s ) ;

Если С-программа содержит такой вызов, библиотечная процедура wr i t e будет


скомпонована с программой и, таким образом, окажется в двоичном коде, загру­
жаемом в память во время выполнения программы. Набор всех этих библиотеч­
ных процедур, несомненно, является частью системы ввода-вывода.
Хотя многие такие процедуры мало что делают, помимо выполнения системного
вызова с соответствующими аргументами, есть ряд процедур ввода-вывода, произ­
водящих определенную работу. В частности, библиотечными процедурами выпол­
няются форматные операции ввода и вывода. Например, С-процедура p r int f ,
принимающая н а входе текстовую строку и, возможно, несколько переменных,
создает из нее АSСП-строку, после чего делает системный вызов wr i t e для не­
посредственного вывода. Рассмотрим следующую команду:
print f ( " Kв aдpaт % 3 d равен % 6 d \ n " , i, i*i) ;

Эта команда формирует строку, состоящую из слова • Квадрат�, значения i в ви­


де трехсимвольной строки, •слова равен�, значения квадрата i в виде строки из
6 символов и символа конца строки.
Примером сходной процедуры ввода служит процедура s can f , читающая тексто­
вую строку и преобразующая ее в значения переменных в соответствии с форма­
том, напоминающим используемый процедурой p r int f . Стандартная библиотека
ввода-вывода содержит большое количество процедур, включающих операции
ввода-вывода и работающих как часть пользовательской программы.
Не все программное обеспечение ввода-вывода пользовательского пространства
состоит из библиотечных процедур. Другая важная категория - это система
спулинга. Спулинг (spooling) представляет собой способ работы с выделенными
устройствами в многозадачной системе. Типичным устройством, на котором ис­
пользуется спулинг, является принтер. В принципе, можно разрешить каждому
пользователю открывать специальный символьный файл принтера, однако пред­
ставьте себе, что процесс, открыв его, не обращается к принтеру в течение несколь­
ких часов. Ни один другой процесс в это время не сможет ничего напечатать.
Вместо этого создается специальный процесс, называемый демоном, и специаль­
ный каталог, называемый каталогом спулинга. Чтобы распечатать файл, процесс
3 . 2 . П рограмм ное обеспечение ввода-вывода 269

сначала создает специальный файл, предназначенный для печати, который по­


мещает в каталог спулинга. Этот файл печатает демон, единственный процесс,
которому разрешается пользоваться специальным файлом принтера. Таким об­
разом, потенциальная проблема, связанная с тем, что какой-либо процесс на
слишком долгий срок захватит принтер, решается защитой специального файла
принтера от прямого доступа пользователей.
Спулинг используется не только для принтеров. Например, программное обес­
печение электронной почты, как правило, включает демона. Письмо, которое не­
обходимо послать, помещается в каталог спулинга. Затем демон электронной
почты извлекает его оттуда и пытается отправить. В любой момент времени
существует вероятность, что получатель недоступен; в этом случае демон остав­
ляет письмо в каталоге спулинга и фиксирует информацию о том, что попытку
передачи следует повторить позднее. Демон также может уведомить отправите­
ля о задержке, а если письмо не удается отправить в течение нескольких часов
или дней, - послать сообщение о том, что доставка письма невозможна.
На рис. 3.5 показана структура системы ввода-вывода со всеми уровнями и основ­
ными функциями каждого уровня.

Ответ
ввода-вывода Функции
Уровень ввода-вывода
Обращение к вызовам ввода-вывода;
Процесс пользователя
Запрос форматный ввод-вывод; спулинг
ввода-вывода
Устройство-независимое Именование, защита, блокирование,
программное обеспечение буферизация , назначение

Установка регистров устройств;


Драйверы устройства
завершение операции ввода-вывода

Активизировать драйвер по завершении


Обработчики прерываний
операции ввода-вывода

Аппаратура Выполнение операции ввода-вывода

Рис. 3 . 5 . Уровни и основные функции системы ввода-вывода

Стрелки на рис. 3.5 изображают потоки управления. Например, когда пользо­


вательская программа пытается прочитать блок из файла, для обработки вызова
запускается операционная система. Независимое от устройств программное обес­
печение ищет этот блок в кэше. Если требуемого блока там нет, оно вызывает
драйвер устройства, чтобы обратиться к аппаратуре и получить этот блок с диска.
Процесс же блокируется до завершения дисковой операции.
Когда диск завершает операцию, аппаратура инициирует прерывание. Обработчик
прерываний запускается с целью определить, что случилось, то есть выяснить,
какое устройство требует внимания. Затем он получает информацию о состоя­
нии устройства и активизирует �спящий» процесс, чтобы завершить обработку
запроса ввода-вывода и предоставить пользовательскому процессу возможность
продолжения работы.
270 Глава 3. Ввод- вывод

3 . 3 . В заи м на я блоки ровка


В компьютерных системах есть такие ресурсы, каждый из которых в конкретный
момент времени может использоваться только одним процессом. В качестве при­
меров можно привести принтеры, накопители на магнитной ленте и элементы
внутренних таблиц системы. Наличие двух процессов, одновременно передаю­
щих данные на принтер, приведет к печати бессмысленного набора символов.
Наличие двух процессов, использующих один и тот же элемент таблицы файло­
вой системы, обязательно станет причиной краха файловой структуры. Поэтому
все операционные системы обладают способностью предоставлять процессу экс­
клюзивный доступ (по крайней мере, временный) к определенным ресурсам, как
программным, так и аппаратным.
Часто прикладной процесс нуждается в исключительном доступе не к одному,
а к нескольким ресурсам. Предположим, например, что каждый из двух процессов
хочет записать отсканированный документ на компакт-диск. Процесс А запра­
шивает разрешение на использование сканера и получает его. Процесс В запро­
граммирован по-другому, поэтому сначала запрашивает устройство для записи
компакт-дисков и также получает его. Затем процесс А обращается к устройству
для записи компакт-дисков, но запрос отклоняется до тех пор, пока это устрой­
ство занято процессом В. К сожалению, вместо того чтобы освободить устройст­
во для записи компакт-дисков, В запрашивает сканер. Процессы оказываются за­
блокированными и будут вечно оставаться в <1:подвешенном� состоянии. Такая
ситуация называется взаимной блокировкой, или тупиком.
Взаимные блокировки характерны не только для запросов выделенных устройств
ввода-вывода, но и для множества других ситуаций. В системах баз данных про­
грамма может оказаться вынужденной заблокировать несколько записей, чтобы
избежать гонок. Если процесс А заблокирует запись R 1, процесс В заблокирует
запись R2, а затем каждый процесс попытается заблокировать чужую запись, мы
также окажемся в тупике. Таким образом, взаимные блокировки появляются при
работе как с аппаратными, так и с программными ресурсами.
В этой главе мы рассмотрим тупиковые ситуации более подробно, увидим, как
они возникают, и изучим некоторые способы, позволяющие предупредить вза­
имные блокировки или избежать их. Хотя информация о взаимных блокировках
представлена в контексте операционной системы, они также могут встретиться
в системах баз данных и во множестве других систем, поэтому рассматриваемый
материал на самом деле применим к широкому кругу систем, работающих с не­
сколькими процессами.

3 . 3 . 1 . Ресурсы
Система может оказаться в ситуации взаимной блокировки, когда процессам
предоставляются исключительные права доступа к устройствам, файлам и т. д.
Чтобы максимально обобщить рассказ о взаимных блокировках, мы будем назы­
вать объекты доступа ресурсами. Ресурсом может быть устройство (например,
3. 3. Взаи мная блоки ровка 27 1

накопитель на магнитной ленте) или порция информации (закрытая запись в базе


данных). В компьютере существует масса различных ресурсов, к которым могут
происходить обращения. Кроме того, в системе может оказаться несколько иден­
тичных экземпляров какого-либо ресурса, например три накопителя на магнит­
ных лентах. Если в системе есть несколько взаимозаменяемых копий ресурса,
называемых однородны.ми1, то в ответ на обращение к ресурсу может предостав­
ляться любая из доступных копий. Короче говоря, ресурс - это все то, что впра­
ве использоваться только одним процессом в любой момент времени.
Ресурсы бывают двух типов: выгружаемые и невыгружаемые. Выгружаемый ре­
сурс позволяется безболезненно забирать у владеющего им процесса. Образцом
такого ресурса является память. Рассмотрим, например, систему с пользователь­
ской памятью размером 64 Мбайт, одним принтером и двумя процессами по
64 Кбайт, каждый из которых хочет что-то напечатать. Процесс А запрашивает
и получает принтер, затем начинает вычислять данные для печати. Еще не за­
кончив расчеты, он превышает свой квант времени и выгружается на диск в об­
ласть подкачки.
После этого работает процесс В и безуспешно пытается обратиться к принтеру.
В данный момент мы получили потенциальную тупиковую ситуацию, поскольку
процесс А оккупирует принтер, процесс В занимает память, и ни один из них
не может продолжать работу без ресурса, удерживаемого другим. К счастью,
не запрещено выгрузить (забрать) память у процесса В, переместив его на диск
в область подкачки и загрузив с диска в память процесс А. После этого процесс А
может закончить вычисления, выполнить печать и затем освободить принтер.
Взаимной блокировки не происходит.
Невыгружаемый ресурс, в противоположность выгружаемому, - это такой ре­
сурс, который нельзя забрать от текущего владельца, не уничтожив результаты
вычислений. Если в момент записи компакт-диска внезапно отнять у процесса
устройство для записи и передать его другому процессу, то в результате мы по­
лучим испорченный компакт-диск. Устройство для записи компакт-дисков яв­
ляется невыгружаемым в произвольный момент времени ресурсом.
Вообще говоря, взаимные блокировки касаются невыгружаемых ресурсов. По­
тенциальные тупиковые ситуации, в которые вовлечен противоположный вид
ресурсов, обычно разрешаются путем перераспределения ресурсов от одного
процесса другому. Поэтому мы сконцентрируем свое внимание на невыгружае­
мых ресурсах.
Последовательность событий, необходимых для использования ресурса, в абст­
рактной форме выглядит следующим образом.
1 . Запрос ресурса.
2. Использование ресурса.
3. Возврат ресурса.

1 Это юридический и экономический термин. Например, взаимозаменяемым является золото: один


его грамм эквивалентен любому другому.
272 Глава 3. Ввод- вывод

Если ресурс недоступен, запрашивающий его процесс вынужден ждать. В неко­


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

3 . 3 . 2 . Механизм взаим ной блоки ровки


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

Условия взаимно й блокировки


В [2 1 ) перечислены 4 условия взаимной блокировки.
1. Взаимное исключение. Каждый ресурс в данный момент либо отдан ровно
одному процессу, либо доступен.
2. Удержание и ожидание. Процессы, в данный момент удерживающие полу­
ченные ранее ресурсы, вправе запрашивать новые ресурсы.
3. Отсутствие принудительной выгрузки ресурса. У процесса нельзя принуди­
тельным образом забрать ранее полученные ресурсы. Процесс, владеющий
ими, должен сам освободить ресурсы.
4. Циклическое ожидание. Должна существовать циклическая последователь­
ность из двух и более процессов, каждый из которых ждет доступа к ресурсу,
удерживаемому следующим членом последовательности.
3 . 3 . Взаим ная блокировка 273

Для того чтобы произошла взаимная блокировка, должны выполниться все эти
четыре условия. Если хоть одно из них не выполняется, тупиковая ситуация не­
возможна.
В серии публикаций Левина [76-78] указывается на то, что в литературе тер­
мином «взаимная блокировка• называют целый ряд ситуаций и перечисленные
в [2 1 ] условия относятся лишь к взаимной блокировке ресурсов. Известны приме­
ры взаимных блокировок, не удовлетворяющих всем четырем сформулирован­
ным условиям. Например, если четыре автомобиля одновременно встретятся на
перекрестке, то, согласно правилам, ни один из них не вправе продолжить дви­
жение, однако здесь ни один из «процессов• (автомобилей) не является владель­
цем уникального ресурса. Данная проблема называется взаимной блокировкой
планирования и разрешается внешним участником - полицейским, принимаю­
щим решение о приоритетах машин.
Следует заметить, что каждое из условий относится к политике, которая может
быть принята или не принята в системе. Может ли определенный ресурс едино­
временно использоваться более чем одним устройством? Выгружаемы ли ресур­
сы? Возможно ли циклическое ожидание? Позже мы увидим, как справиться
с взаимными блокировками, нарушая некоторые из этих условий.

Модели рование взаимных блокировок


В [6 1 ] показано, как можно смоделировать четыре условия взаимной блокиров­
ки, используя направленные графы. Графы имеют два вида узлов: процессы, по­
казанные кружочками, и ресурсы, нарисованные квадратиками. Ребро, направ­
ленное от узла ресурса (квадрат) к узлу процесса (круг), означает, что ресурс
ранее был запрошен процессом, получен и в данный момент используется этим
процессом. На рис. 3.6, а ресурс R в настоящее время отдан процессу А.

а б в

Рис . 3 . 6 . Графы распределения ресурсов:а -ресурс занят; б запрос ресурса;


-

в - взаимная блокировка
Ребро, направленное от процесса к ресурсу, означает, что процесс в данный
момент блокирован и находится в состоянии ожидания доступа к этому ресур­
су. На рис. 3.6, б процесс В ждет ресурс S. На рис. 3.6, в мы видим взаимную
блокировку: процесс С ожидает ресурс Т, удерживаемый процессом D. Про­
цесс D вовсе не намеревается освобождать ресурс Т, потому что он ждет ре­
сурс И, используемый процессом С. Оба процесса будут ждать до бесконечности.
274 Глава 3 . Ввод- вывод

Цикл в графе означает наличие взаимной блокировки, циклично включающей


процессы и ресурсы (предполагается, что в системе есть по одному ресурсу ка­
ждого вида). В этом примере циклом является последовательность C-T-D- U- C.
Теперь рассмотрим пример того, как извлечь пользу из графов ресурсов. Пред­
ставим, что у нас есть три процесса: А, В и С, и три ресурса: R, 5 и Т. Последо­
вательность запросов и возвратов ресурсов для трех процессов показана на
рис. 3.7, а-в. Операционная система может запустить любой незаблокированный
процесс в любой момент времени, значит, таковым может оказаться процесс А.
Процесс А будет выполняться до тех пор, пока не закончит всю свою работу, за­
тем запустится процесс В, а по его завершении - процесс С.
Такой порядок не приводит к взаимной блокировке (не возникает соперничества
за использование ресурсов) , но здесь также по сути нет параллельной работы.
Помимо запроса и возврата ресурсов, процессы выполняют вычисления и ввод­
вывод данных. Когда процессы работают последовательно, нереальна ситуация,
при которой один процесс использует процессор, в то время как другой ждет за­
вершения операции ввода-вывода. Таким образом, строго последовательная ра­
бота процессов не бывает оптимальной. С другой стороны, если вообще ни один
процесс не выполняет операций ввода-вывода, алгоритм 4 самое короткое за­
дание - первое» работает эффективнее, чем циклический, поэтому в некото­
рых ситуациях последовательный запуск всех процессов может быть наилучшим.
Теперь предположим, что процессы выполняют как вычисления, так и ввод-вы­
вод, соответственно циклический алгоритм планирования является рациональ­
ным. Запросы ресурсов могут происходить в порядке, указанном на рис. 3.7, г.
Если эти шесть запросов будут осуществлены в такой последовательности, в ре­
зультате мы получим шесть графов, показанных на рис. 3.7, д-к. После запроса 4
процесс А блокируется в ожидании ресурса 5 (см. рис. 3.7, з). На двух следую­
щих шагах также блокируются процессы В и С, в конечном счете приводя к цик­
лу и взаимной блокировке на рис. 3.7, к.
Однако как мы упоминали ранее, операционная система не обязана запускать
процессы в каком-то особом порядке. В частности, если выполнение отдельного
запроса приводит к тупику, операционная система вправе просто приостановить
процесс без удовлетворения запроса (то есть не выполняя план процесса) до тех
пор, пока это безопасно. На рис. 3.7 операционная система могла бы приостановить
процесс В вместо того, чтобы отдавать ему ресурс 5, если бы она знала о пред­
стоящей взаимной блокировке. Работая только с процессами А и С, мы могли бы
получить такой порядок запросов ресурсов и их возвратов, который представлен
на рис. 3.7, л, вместо показанного на рис. 3.7, г. Такая последовательность дейст­
вий отражена графами на рис. 3.7, м-с, и она не приводит к тупику.
После шага с процесс В может получить ресурс 5, потому что процесс А уже за­
кончил свою работу, а процесс С имеет в своем распоряжении все необходимые
ему ресурсы. Даже если затем процесс В, когда он запросит ресурс Т, будет за­
блокирован, система не зависнет. Процесс В всего лишь будет ждать завершения
процесса С.
3 . 3. В заимная бл окировка 275

А в с
Запрос R Запрос S Запрос Т
Запрос S Запрос Т Запрос R
Освобождение R Освоб ождение S Освобождение Т
Освобождение S Освобождение Т Освобождение R
а б в

1 . А запрашивает R

� ® @)
�% �%Ж
@)
2. В запрашивает S
3. С запрашивает Т
4. А запрашивает S
5. В запрашивает Т
6. С запрашивает R
Взаимоблокировка 0 (2] [2]
г д в ж

� cr � � ��
0�ш ш 0�шсь
3 и к

1 . А запрашивает R

�®� �®
2. С запрашивает Т
3. А запрашивает S
4. С запрашивает R
5. R
00Ш 00
А освобождает
6. А освобождает S
Взаимоблокировки нет

л м н о

�® �® 0®
00 00
п р с

Рис . 3 . 7 . Пример возникновения взаимной блокировки и способ избежать ее


276 Глава 3. Ввод- вывод

Позже в этой главе мы изучим подробный алгоритм для принятия решений


о распределении ресурсов, которые не приведут к взаимной блокировке. В дан­
ный момент важно понять, что графы ресурсов являются инструментом, позво­
ляющим увидеть, станет ли заданная последовательность запросов и возвратов
ресурсов причиной тупиковой ситуации. Мы всего лишь шаг за шагом осуществ­
ляем запросы и возвраты ресурсов и после каждого шага проверяем граф на со­
держание циклов. Если они есть, мы оказались в тупике; если нет, значит, взаим­
ной блокировки тоже нет. Хотя мы рассматривали графы ресурсов для случая,
когда в системе присутствует по одному ресурсу каждого типа, графы также
можно построить для обработки ситуации с несколькими одинаковыми ресурса­
ми [61 ) . Тем не менее в [76, 77) указано на то, что в случае однородных ресурсов
графы оказываются очень сложными. Если хотя бы одна ветвь графа не входит
в цикл, то есть один неблокированный процесс захватывает одну из копий одно­
го из ресурсов, взаимной блокировки не происходит.
Вообще говоря, для борьбы с взаимными блокировками практикуются четыре
стратегии.
1 . Полное игнорирование проблемы. Если вы проигнорируете проблему, воз­
можно, затем она проигнорирует вас.
2. Обнаружение и устранение. Взаимной блокировке позволяется произойти,
затем она обнаруживается и предпринимаются те или иные действия для ре­
шения проблемы.
3. Предотвращение путем структурного избежания одного из четырех условий
взаимной блокировки.
4. Динамическое избежание тупиковых ситуаций путем аккуратного распреде­
ления ресурсов.
Мы по очереди изучим каждый из этих методов в следующих четырех разделах.

3 . 3 . З . Ал горитм страуса
Самым простым подходом является «алгоритм страуса• : воткните голову в пе­
сок и притворитесь, что проблемы вообще не существует. Различные люди от­
зываются об этой стратегии по-разному. Математики считают ее полностью не­
приемлемой и говорят, что взаимные блокировки нужно предотвращать любой
ценой. Инженеры спрашивают, как часто встает подобная проблема, как часто
система попадает в аварийные ситуации по другим причинам и насколько серь­
езны последствия взаимных блокировок. Если взаимные блокировки случа­
ются в среднем один раз в пять лет, а сбои операционной системы, ошибки ком­
пилятора и поломки компьютера из-за неисправности аппаратуры происходят
раз в неделю, то большинство инженеров не захотят добровольно терять в произ­
водительности и удобстве для того, чтобы ликвидировать возможность взаим­
ных блокировок.
Чтобы подчеркнуть контраст между этими подходами, добавим, что UNI X и
MINIX 3 потенциально страдают от взаимных блокировок, которые даже не
3 . 3 . Взаимная блокировка 277

обнаруживаются, не говоря уже об автоматическом выходе из тупика. Суммар­


ное количество процессов в системе определяется количеством записей в таблице
процессов. Таким образом, ячейки таблицы процессов являются ограниченным
ресурсом. Если системный вызов fork получает отказ, в силу того, что таблица
целиком заполнена, разумно будет, чтобы программа, вызывающая f o rk, подож­
дала какое-то время и повторила попытку.
Теперь предположим, что система MINIX 3 имеет 1 00 ячеек процессов. Работа­
ют десять программ, каждой необходимо создать 1 2 (под)процессов. После обра­
зования каждым процессом девяти процессов 1 О исходных и 90 новых процессов
заполнят таблицу до конца. После этого каждый из десяти исходных процессов
попадает в бесконечный цикл, состоящий из попыток разветвления и отказов, то
есть возникает взаимная блокировка. Вероятность того, что произойдет по­
добное, минимальна, но она ненулевая. Нужно ли нам: отказываться от процессов
и вызова f o rk, чтобы решить проблему?
Максимальное количество открытых файлов также ограничено размером табли­
цы индексных узлов, следовательно, когда таблица заполняется целиком, возни­
кает та же самая проблема. Пространство дл:я подкачки файлов на диск является
еще одним ограниченным ресурсом. Фактически, почти каждая таблица в опера­
ционной системе представляет собой ограниченный ресурс. Должны ли мы уп­
разднить их все из-за того, что теоретически возможна ситуация, когда в группе
из п процессов каждый может потребовать 1/п от целого, а затем попытаться по­
лучить еще часть?
Большая часть операционных систем, включая UNIX, M INIX 3 и Windows,
игнорируют эту проблему. Разработчики исходят из предположения, что боль­
шинство пользователей скорее предпочтут иметь дело со случающимися время
от времени взаимными блокировками, чем с правилом, по которому всем поль­
зователям разрешается иметь только один процесс, только один открытый файл
и т. д. Если бы можно было легко устранить взаимные блокировки, не возникло
бы столько разговоров на эту тему. Сложность заключается в том, что цена
достаточно высока, и в основном она, как мы вскоре увидим, исчисляется в на­
ложении неудобных ограничений на процессы. Таким образом, мы столкнулись
с неприятным выбором между удобством и корректностью и множеством дис­
куссий о том, что более важно и для кого. В таких условиях трудно найти вер­
ное решение.

3 . 3 . 4 . Обнаружение и уст ранение


взаи м н ых блоки ровок
Второй подход предполагает обнаружение и устранение. Здесь система лишь
следит за запросом и освобождением ресурсов. Каждый раз, когда запрашивает­
ся или освобождается новый ресурс, то есть когда обновляется граф ресурсов,
система проверяет, имеются ли в нем циклы. Если цикл есть, один из входящих
в него процессов принудительно завершается. Это повторяется до тех пор, пока
циклов не останется.
278 Глава 3 . Ввод- вывод

Более грубый метод не анализирует граф ресурсов, а просто проверяет наличие


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

3 . 3 . 5 . П редотвраще н ие взаи м н ых блоки ровок


Третья стратегия предполагает наложение на процессы допустимых ограничений,
делающих взаимные блокировки конструктивно невозможными. Четыре усло­
вия, сформулированные в [2 1 ] , являются предпосылками для некоторых подоб­
ных решений.
Сначала попробуем атаковать условие взаимного исключения. Если в системе
нет ресурсов, отданных в единоличное пользование одному процессу, мы нико­
гда не попадем в состояние взаимной блокировки. Но в равной степени понятно,
что если позволить двум процессам одновременно печатать данные на принтере,
воцарится хаос. Используя механизм спулинга, несколько процессов могут гене­
рировать свои выходные данные одновременно. В такой модели только один
процесс, который фактически запрашивает физический принтер, является демо­
ном принтера. Так как демон не запрашивает никакие другие ресурсы, для прин­
тера тупики исключаются.
К сожалению, не все устройства поддерживают спулинг (таблица процессов -
дело резидентное). Кроме того, конкуренция за дисковое пространство для спулин­
га сама по себе чревата тупиком. Что получится, если два процесса заполнят свои­
ми выходными данными каждый по половине диска, отведенного под спулинг,
и ни один из них не заканчивает вычисления? Демон может быть запрограммиро­
ван так, что начнет печать, не дожидаясь спулинга всех выходных данных, и прин­
тер тогда простоит впустую в том случае, если вычисляющий процесс решил по­
дождать несколько часов после первого пакета выходных данных. По этой при­
чине обычно демоны программируют так, что они начинают печать только после
того, как файл выходных данных целиком станет доступен. В этом же случае мы
получаем два процесса, каждый из которых обработал часть выходных данных, но
не все и не в состоянии продолжать вычисления дальше. Ни один из двух процес­
сов никогда не завершится, то есть имеет место взаимная блокировка на диске.
Второе из условий, сформулированных в [2 1 ] , кажется, все же подает надежду.
Если у нас получится уберечь процессы, занимающие некоторые ресурсы, от ожи­
дания остальных ресурсов, мы устраним тупиковую ситуацию. Один из способов
достижения этой цели состоит в требовании, следуя которому любой процесс дол­
жен запрашивать все необходимые ресурсы до начала работы. Если все ресурсы
доступны, процесс получит все, что ему нужно, и сможет работать до успешного
завершения. Если один или несколько ресурсов заняты, процессу ничего не пре­
доставляется, и он просто переводится в состояние ожидания.
3 . 3 . Взаимная блокировка 279

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

1 . Фотонаборное устройство
2. Сканер
3. Плоттер
4. Ленточный накопитель
5. Накопител ь CD-ROM
а б
Рис. 3 . 8 . Общая нумерация ресурсов: а - пронумерованные ресурсы; б граф ресурсов
-

При выполнении такого соглашения граф распределения ресурсов никогда не


будет иметь циклов. Покажем, что это так, в случае двух процессов (рис. 3.8, б).
Мы попадем в ситуацию взаимной блокировки, только если процесс А запросит
pecypc j, а процесс В обратится к ресурсу i. Предположим, что ресурсы i и j раз­
личны, значит, они имеют разные номера. Если i > j, тогда процессу А не позво­
ляется з апрашивать pecypc j, потому что его номер меньше, чем номер уже имею­
щегося у него ресурса. Если же i < j, процесс В не может запрашивать ресурс i,
280 Глава 3. Ввод- вывод

так как этот номер меньше номера уже занятого им ресурса. Так или иначе, вза­
имная блокировка исключена.
При работе с несколькими процессами сохраняется та же самая логика. В каж­
дый момент времени один из предоставленных ресурсов будет иметь наивысший
номер. Процесс, использующий этот ресурс, уже никогда не запросит другие
занятые ресурсы. Он либо закончит свою работу, либо, в худшем случае, запро­
сит ресурс с еще большим номером, а любой такой ресурс является доступным.
В итоге процесс завершит работу и освободит свои ресурсы. На этот момент сло­
ж:йтся ситуация, когда ресурс с высшим номером уже занят каким-то другим
процессом, который также сможет нормально завершиться. То есть существует
алгоритм, по которому все процессы отрапортуют о выполнении без взаимной
блокировки.
Вариантом этого алгоритма является схема, в которой исключается требование
приобретения ресурсов в строго возрастающем порядке, но сохраняется условие,
что процесс не может запросить ресурсы с меньшим номером, чем уже у него
имеющиеся. Если процесс на начальной стадии запрашивает ресурсы 9 и 10, за­
тем освобождает их, то это равнозначно тому, как если бы он начал работу зано­
во, поэтому нет причины запрещать ему запрос ресурса 1 .
Хотя систематизация ресурсов путем их нумерации устраняет проблему взаимных
блокировок, бывают ситуации, когда невозможно найти порядок, удовлетворяю­
щий всех. Когда ресурсы включают в себя области таблицы процессов, дисковое
пространство для спулинга, закрытые записи базы данных и другие абстрактные
ресурсы, число потенциальных объектов интереса и вариантов их применения
может быть настолько огромным, что никакая систематизация не спасет. Кроме
того, согласно [78), упорядочивание ресурсов сводит «на нет� взаимозаменяе­
мость - копия ресурса при таких правилах может оказаться недоступной.
В табл. 3.2 обобщены различные методы предотвращения взаимных блокировок.

Таблица 3 . 2 . Методы предотвращения взаимных блокировок


Условие Метод
Взаимное исключение Организовывать спулинг
Удержание и ожидание Запрашивать все ресурсы на начальной стадии
Отсутствие принудительной выгрузки ресурса Отобрать ресурсы
Циклическое ожидание Упорядочить ресурсы по номерам

3 . 3 . 6 . Избежание взаи м н ых блоки ровок


На рис. 3. 7 мы видели, что избежать вэаимных блокировок можно не с помо­
щью строгих правил, применяемых к процессам, а путем тщательного анализа
каждого запроса на ресурсы и его удовлетворения только в случае, если запрос
признан безопасным. Неизбежен новый вопрос: существует ли алгоритм, кото­
рый никогда не приведет к взаимной блокировке, то есть все время делающий
правильный выбор ? Ответом является условное «да� - мы можем избежать
3 . 3 . Взаимная блокировка 281

тупиков, но только если заранее будет доступна определенная информация. В сле­


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

Ал горитм банкира для одно г о вида ресурсов


Алгоритм планирования, позволяющий избегать взаимных блокировок, был пред­
ложен в [38) и носит название алгоритма банкира. Алгоритм моделирует банки­
ра в маленьком городке, имеющего дело с группой клиентов, которым он выдал
ряд кредитов. У банкира не обязательно имеется на руках достаточно средств,
чтобы выдать каждому клиенту полную сумму кредита. На рис. 3.9, а мы видим
четырех клиентов, А, В, С и D, каждый из которых получил определенное коли­
чество единиц кредита (пусть единица равна 1 000 долларов). Банкир знает, что
не всем клиентам понадобится вся сумма немедленно, поэтому он зарезервиро­
вал только 10 единиц, а не все 22, которые требуются клиентам. Он рассчитыва­
ет на то, что каждый из клиентов будет способен погасить кредит вскоре после
его получения. В этом случае банкир сможет обслужить все запросы (чтобы про­
вести аналогию с компьютерной системой, считаем, что клиенты - это процес­
сы, единицами, скажем, являются накопители на магнитной ленте, а банкир -
это операционная система) .


D:
!§ �
!ii
"
.D

:>.
:о �

:>.


.D

:>.

s s s
а
i:::: !;;J а
i:::: !;;J а
i:::: !;;J
!'О !'О !'О
u
:s: :::!: u
:s: :::!: u
:s: :::!:
А о 6 А 1 6 А 1 6
в о 5 в 1 5 в 2 5
с о 4 с 2 4 с 3 4
D о 7 D 4 7 D 4 7
Свободно: 1 0 Свободно: 2 Свободно: 1
а б в

Рис . 3 . 9 . Три состояния распределения ресурсов: а - безопасное;


б безопасное; в небезопасное
- -

Каждая часть рисунка демонстрирует состояние системы, соответствующее выде­


ленным ресурсам, то есть список клиентов с указанием ссуженных сумм ( выде­
ленных ленточных накопителей) и максимальных доступных им кредитов (наи­
большее число накопителей, одновременно необходимых в будущем). Состояние
системы считается безопасным, если существует последователъностъ других со­
стояний, ведущих к тому, что все клиенты до исчерпания кредита могут взятъ
максимально доступный для них заем (все процессы получают требуемые ресур­
съt и завершаются).
Клиенты вращаются в соответствующем бизнесе, время от времени прося у бан­
ка ссуды (то есть запрашивая ресурсы). В некоторый момент возникает ситуа­
ция, показанная на рис. 3.9, б. Это состояние безопасно, поскольку остались две
282 Глава 3 . Ввод- вывод

свободные единицы и банкир может задержать все обращения, кроме запро­


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

Т раектории ресурсов
Предыдущий алгоритм описан в терминах одного класса ресурсов (то есть в на­
шем распоряжении имеются только принтеры или только ленточные накопители,
но не то и другое вместе). На рис. 3. 10 представлена модель системы с двумя про­
цессами и двумя классами ресурсов, например принтером и плот:rером. По гори­
зонтальной оси выводятся номера команд, выполняемых процессом А. По верти­
кальной оси отложены номера команд, выполняемых процессом В. В команде 11
процесс А запрашивает принтер, в команде 12 ему требуется плоттер. Принтер
и плоттер освобождаются командами 13 и 14 соответственно. Процессу В необхо­
дим плоттер с команды 15 по команду 17 и принтер с команды 16 по команду 18•
Каждая точка на диаграмме представляет совместное состояние двух процес­
сов. Изначально система находится в точке р, когда ни один из процессов еще
не выполнил ни одной инструкции. Если планировщик запустит процесс А пер­
вым, мы попадем в точку q, в которой процесс А выполнил какое-то количество
команд, а процесс В еще ничего не сделал. В точке q траектория становится
вертикальной, показывая, что планировщик решил запустить процесс В. При
наличии одного процессора все отрезки траектории могут быть только верти­
кальными или только горизонтальными, но не наклонными. Кроме того, движе­
ние всегда происходит на «север� или «восток� (вверх или вправо) и никогда
на «юг� или «запад� (вниз или влево), так как процессы не могут течь в обрат­
ном направлении.
3 . 3 . Взаимная блокировка 283

в • u (Завершены оба процесса)


Принтер
7 7"7t77777..,.,___-+----
18 t---------t7777

! ! ::
Плоттер 1-----+� : ���-
•- - - - - - •
s

---�-�--�---- А
р q lз
Принтер oCt------1•
... �
...
,..,______,.
"Плоттер
Рис . 3 . 1 О. Две траектории ресурсов для процессов
Когда процесс А пересекает линию 11 на отрезке от точки r до точки s, он запра­
шивает и получает принтер. Когда процесс В достигает точки t, он запрашивает
плоттер.
Особенно интересны заштрихованные области. Область со штриховкой из верх­
него левого угла в правый нижний представляет промежуток времени, когда оба
процесса занимают принтер. Правило взаимного исключения делает попадание
в эту область невозможным. Вторая заштрихованная область соответствует то­
му, что оба процесса используют плоттер, и это также невозможно.
Если система войдет в прямоугольник, ограниченный линиями 11 и 12 по сто­
ронам и линиями 15 и 16 сверху и снизу, она в конце концов доберется до пере­
сечения линий 12 и 16• В этот момент процесс А запросит плоттер, а процесс В по­
требует принтер, но оба ресурса будут к тому времени заняты. Получается, что
тупиковым является целый прямоугольник и в него нельзя входить. В точке t
единственно безопасный вариант состоит в том, чтобы оставить процесс А рабо­
тать до тех пор, пока он не достигнет команды 14• После нее любая траектория
дойдет до точки и.
Важный для понимания момент заключается в том, что в точке t процесс В за­
прашивает ресурс. Система должна принять решение, предоставлять его или нет.
Если выдается разрешение, система попадает в небезопасную область и в итоге
блокируется. Чтобы избежать тупика, нужно приостановить процесс В до тех
пор, пока процесс А не запросит и не освободит плоттер.

Ал горитм банкира дпя нескольких видов ресурсов


Такую графическую модель трудно применить для общего случая, когда имеют­
ся несколько процессов и несколько различных классов ресурсов и в каждом
классе может быть несколько экземпляров (например, два плоттера и три нако­
пителя) . Но алгоритм банкира поддается обобщению для управления системой
с несколькими видами ресурсов. На рис. 3. 1 1 показано, как он работает.
284 Гл ава 3 . Ввод-вывод

А з о 1 1 А 1 1 о о Е = (6342)
в о 1 о о в о 1 1 2 Ар = (1(5322)
=

020)
с 1 1 1 о с з 1 о о
D 1 1 о 1 D о о 1 о
Е о о о о Е 2 1 1 о
Распределенные ресурсы Ресурсы, которые еще нужны
Рис . 3. 1 1 . Алгоритм банкира в системе с несколькими видами ресурсов

На рисунке изображены две матрицы. Матрица слева показывает, сколько ресурсов


каждого вида занимает в настоящее время каждый из пяти процессов. Матрица
справа объединяет ресурсы, которые нужно добавить каждому процессу для успеш­
ного завершения. Как и в случае одного вида ресурсов, процессы должны точно
определять необходимое суммарное количество ресурсов до начала работы для
того, чтобы система могла рассчитать правую матрицу в каждый момент времени.
Три вектора, изображенные справа от матриц, показывают соответственно суще­
ствующие (вектор Е), занятые (вектор Р) и доступные ресурсы (вектор А). Из
вектора Е мы видим, что система имеет шесть накопителей на магнитной ленте,
три плоттера, четыре принтера и два устройства чтения компакт-дисков. Из них
заняты в данный момент пять ленточных накопителей, три плоттера, два принте­
ра и два привода компакт-дисков. Чтобы увидеть это, нужно просуммировать че­
тыре столбца, соответствующие ресурсам, в левой матрице. Вектор доступных
ресурсов является разницей между тем, что доступно в системе, и тем, что ис­
пользуется.
Теперь можно изложить алгоритм, проверяющий, безопасно ли состояние системы.
1. Ищем в матрице R строку, соответствующую процессу, чьи неудовлетво­
ренные потребности ресурсов меньше или равны вектору А. Если такой стро­
ки не существует, система в конце концов попадет в состояние взаимной
блокировки, так как ни один процесс не сможет проработать до успешного за­
вершения.
2. Допускаем, что процесс, строку которого мы выбрали на шаге 1, запрашивает
все необходимые ресурсы (гарантируется, что это возможно) и заканчивает
работу. Отмечаем этот процесс как завершенный и прибавляем все его ресур­
сы к вектору А.
3. Повторяем шаги 1 и 2 до тех пор, пока либо все процессы будут помечены как
завершенные (состояние в этом случае является безопасным), либо произой­
дет взаимная блокировка (тогда состояние небезопасно).
3.3 . Взаимная блокировка 285

Если на шаге 1 можно выбрать несколько процессов, не имеет значения, какой


из них будет взят: общий резерв доступных ресурсов увеличится или, в худшем
случае, останется неизменным.
Вернемся к примеру на рис. 3. 1 1 . Текущее состояние является безопасным. Пред­
положим, процесс В в данный момент запрашивает принтер. На этот запрос мож­
но ответить положительно, потому что получающееся в результате состояние все
еще будет безопасным (процесс D может доработать до конца, затем процесс А
или Е, затем остальные).
Теперь представим, что после того, как процесс В получает один из двух остав­
шихся принтеров, процесс Е требует последний принтер. Удовлетворение этого
запроса сократит вектор доступных ресурсов до ( 1 О О О), что приведет к взаимной
блокировке процессов. Ясно, что следует отложить на время запрос процесса Е.
Алгоритм банкира впервые был предложен в 1965 году [38). С тех пор практиче­
ски каждая книга по операционным системам описывает его в деталях. Различ­
ным аспектам этого алгоритма было посвящено бессчетное количество статей.
К сожалению, мало у кого из авторов хватило смелости показать, что хотя алго­
ритм замечателен в теории, на практике он, по существу, бесполезен, поскольку
нечасто можно определить заранее, сколько ресурсов потребуется процессам
в будущем. Кроме того, количество процессов не фиксировано, оно динамически
изменяется по мере входа пользователей в систему и выхода из нее. И, более
того, считавшиеся доступными ресурсы могут внезапно исчезнуть (например,
накопитель на магнитной ленте может сломаться) . Таким образом, на практике
немногие системы (если такие вообще есть) используют алгоритм банкира, что­
бы избежать взаимных блокировок.
Обобщая, скажем, что описанные алгоритмы, объединенные термином •предот­
вращение взаимных блокировок•, накладывают чересчур сильные ограничения.
В то же время алгоритмы, входящие в группу •избежание взаимных блокиро­
вок•, требуют для своей работы информацию, которая не всегда имеется. Если
вам удастся придумать обобщенный алгоритм, который в реалии работает так же
хорошо, как и в теории, непременно сообщите об этом в ближайший научный
компьютерный журнал.
Несмотря на практическую несостоятельность обобщенных алгоритмов избе­
жания и предотвращения взаимных блокировок, для некоторых частных при­
менений разработаны превосходные специализированные алгоритмы. Например,
в системах управления базами данных часто встречается ситуация, когда сначала
запрашивается блокировка нескольких записей, а затем эти записи обновляются.
Если же одновременно работают несколько процессов, возникает реальная опас­
ность тупика. Для решения этой проблемы существуют специальные методы.
Часто используемый подход называется двухфазной блокировкой. В первой фазе,
то есть на первом этапе, процесс пытается заблокировать все требуемые записи,
по одной за раз. Если операция успешна, процесс переходит ко второму этапу,
выполняя обновление заблокированных записей и освобождение ресурсов. Ни­
какой полезной работы на первом этапе не совершается.
286 Глава 3 . Ввод -вывод

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

3 . 4 . В в од - вы в од в M I N IX 3
Структура системы ввода-вывода в MINIX 3 показана на рис. 3.5. Четыре верхних
уровня этой структуры соответствуют четырем уровням на рис. 2 . 1 4 . В следую­
щих разделах мы вкратце рассмотрим каждый из этих уровней, делая акцент на
драйверах устройств. Механизм обработки прерываний MINIX 3 был рассмот­
рен в предыдущей главе, а аппаратно-зависимый ввод-вывод будет обсуждаться
в главе 5 при изучении файловой системы.

3 . 4 . 1 . Обработчики прерываний и доступ


к вводу- выводу в M I N IX 3
Большинство драйверов инициируют ввод-вывод и переходят в состояние блоки­
ровки, ожидая, когда прибудет сообщение. Обычно это сообщение генерируется
обработчиком прерываний устройства. Существуют другие драйверы, которые
не запускают физических процессов ввода-вывода (пример - чтение с вирту­
ального диска или вывод текста в видеопамять, представляющую собой, по сути,
экран, отображаемый на память), не опираются на прерывания и не ждут сооб­
щений от устройств. Механизм генерации сообщений и переключения заданий,
управляемый прерываниями, в предыдущей главе обсуждался очень подробно.
Здесь же мы рассмотрим его применение в общем, а детально вернемся к этой
теме, когда будем демонстрировать код для различных устройств.
Для дисков ввод и вывод в основном сводятся к передаче устройству соответст­
вующей команды и ожиданию ее завершения. Большую часть работы выполняет
контроллер диска, поэтому обработчик прерывания весьма прост. Если бы все
прерывания обрабатывались так просто, наша жизнь была бы намного легче.
3 .4. Ввод-вывод в M I N IX 3 287

Однако иногда обработчику приходится выполнять более сложные действия.


Механизм передачи сообщений имеет свою цену. Когда прерывания происходят
часто, а объем передаваемых данных невелик, имеет смысл усложнить обра­
ботчик и отложить передачу сообщения до следующего прерывания, чтобы дать
обслуживающему устройство заданию больше времени. В MINIX 3 это невоз­
можно для большинства устройств ввода-вывода, поскольку низкоуровневый
обработчик, находящийся в ядре, представляет собой подпрограмму общего на­
значения, пригодную почти для всех устройств.
Из предыдущей главы вы знаете, что часы в этом отношении являются исключе­
нием. Часы входят в ядро и могут иметь собственный обработчик прерываний,
выполняющий дополнительную работу. Многие такты часов не требуют большо­
го числа действий - обычно необходимо лишь обновлять время. Это делается
без передачи сообщений таймерному заданию. Обработчик прерываний от тай­
мера инкрементирует переменную реального времени r e a l t ime, возможно, до­
бавляя такты, пропущенные при вызове BIOS. Обработчик выполняет простые
арифметические действия - увеличивает счетчики времени пользователя и про­
цесса, уменьшает счетчик t i c k s_l e f t текущего процесса и проверяет, истек ли
таймер. Сообщение посылается таймерному заданию лишь в случае, если теку­
щий процесс исчерпал свой квант или таймер истек.
Обработчик прерываний от таймера является уникальным компонентом MINIX 3,
поскольку часы - единственное устройство, генерирующее прерывания и вьmол­
няемое в пространстве ядра. Аппаратная часть часов интегрирована в компьютер:
фактически, линия прерываний от таймера не подключается к контакту какого­
либо разъема, предназначенного для дополнительных контроллеров ввода-выво­
да. Таким образом, часы невозможно модернизировать, подключив их новую мо­
дель и установив драйвер от производителя, а, значит, резонно сделать драйвер
частью ядра и предоставить ему полный доступ к внутренним данным. Тем не
менее основной целью разработки MINIX 3 является устранение необходимости
подобного доступа для всех остальных устройств.
Драйверы устройств, выполняемые в пользовательском пространстве, не имеют
прямого доступа к памяти ядра и портам ввода-вывода. Хотя процедура обслужи­
вания прерываний могла бы совершить дальний вызов для запуска подпрограм­
мы, обрабатывающей прерывание и находящейся в сегменте кода пользователь­
ского процесса, это нарушило бы принципы организации архитектуры MINIX 3.
Такой подход был бы более опасным, чем возможность вызова функции ядра
пользовательским процессом. В последнем случае мы, как минимум, можем быть
уверены в том, что функция написана компетентным системным разработчиком,
сведущим в вопросах безопасности и даже, возможно, читавшим эту книгу. Но
в любом случае ядро не должно доверять коду пользовательской программы.
Драйверу, находящемуся в пользовательском пространстве, могут понадобиться
различные уровни доступа к вводу-выводу.
1 . Доступ к памяти за пределами области данных драйвера. Пример - драйвер
оперативной памяти, управляющий виртуальным диском. Подобный доступ
является для него единственным необходимым.
288 Глава 3 . Ввод-вывод

2. Чтение и запись портов ввода-вывода. Инструкции машинного кода, выпол­


няющего эти операции, доступны только в режиме ядра. Как мы скоро уви­
дим, такой доступ необходим драйверу жесткого диска.
3. Реакция на ожидаемые прерывания. Например, драйвер жесткого диска запи­
сывает команды в дисковый контроллер, а тот генерирует прерывания по за­
вершении операций.
4. Реакция на непрогнозируемые прерывания. Такая возможность необходима
драйверу клавиатуры. Данный уровень доступа можно рассматривать как
подмножество предыдущего, однако непредсказуемость усложняет проблемы
доступа.
Все перечисленные уровни доступа поддерживаются вызовами ядра, обрабаты­
ваемыми системным заданием.
На первом уровне, уровне доступа к внешним сегментам памяти, используется
аппаратная сегментация процессоров Intel. Несмотря на то что обычный про­
цесс может обращаться только к собственным сегментам кода, данных и стека,
системное задание позволяет пользовательским процессам определять другие
сегменты и получать к ним доступ. Так драйвер памяти обращается к областям
памяти, зарезервированным под виртуальный диск, и к другим областям специ­
ального применения. Аналогичным образом драйвер консоли имеет доступ к па­
мяти дисплейного адаптера.
На втором уровне MINIX 3 предоставляет вызовы ядра для использования ко­
манд ввода-вывода. Системное задание выполняет ввод-вывод от имени менее
привилегированных процессов. Позже в этой главе мы познакомимся с тем, как
эту возможность задействует жесткий диск. Пока скажем лишь несколько слов.
Драйвер диска записывает значение в выходной порт, чтобы выбрать диск, а за­
тем считывает другой порт, чтобы убедиться в готовности устройства. Если оt­
вет, как обычно, приходит быстро, можно воспользоваться механизмом опроса.
Ядро предоставляет вызовы, позволяющие выполнять запись и чтение. При за­
писи указывается номер порта и данные, а при чтении - номер порта и место
под считанные данные. Требуется, чтобы вызов чтения из порта был неблокирую­
щим; в действительности, вызовы ядра вообще не вызывают блокировку.
Полезно каким-либо образом страховаться от сбоев устройств. Например, в оп­
рашивающий цикл можно включить счетчик, завершающий его при отсутствии
ответа от устройства в течение определенного числа итераций. Это - не очень
хороший метод, поскольку время ожидания зависит от быстродействия процес­
сора. Можно соотнести число итераций с используемым процессором, к прйме­
ру, устанавливая его на основе значения глобальной переменной, инициализи­
руемой при запуске системы. Более удачный способ предоставляет системная
библиотека MINIX 3, содержащая функцию ge tupt irne. Последняя обращается
к ядру для получения числа тактов часов с момента запуска системы, содержа­
щегося в счетчике, который поддерживает таймерное задание. Этот способ опре­
деления времени нахождения в цикле имеет свою цену - дополнительный вызов
ядра в каждой итерации. Еще один выход из положения - запросить у системного
3. 4. Ввод-вывод в M I N IX 3 289

задания установку сторожевого таймера, однако для получения уведомления от


таймера требуется блокирующая операция r e c e i ve. Это решение не подходит,
если ответ необходимо получить быстро.
Жесткий диск также использует варианты вызовов ядра, позволяющие посылать
системному заданию списки портов и данных для записи или переменных для
присваивания. Это очень полезная возможность: драйверу жесткого диска, кото­
рый мы рассмотрим позже, для инициализации действия требуется записывать
последовательность байтов в семь выходных портов. Последний байт содержит
команду, и дисковый контроллер генерирует прерывание по ее выполнении. Все
это можно сделать одним вызовом ядра, значительно уменьшив число требуе­
мых сообщений.
Мы подошли к третьему уровню - реакции на ожидаемые прерывания. Как бы­
ло отмечено при рассмотрении системного задания, обработчиком прерывания,
инициализируемого от имени пользовательской программы вызовом ядра sys_
i rqc t l , всегда является внутренняя функция системного задания gener i c_
hand l e r . Она преобразует прерывание в уведомление процессу, от имени кото­
рого было установлено прерывание. Таким образом, после вызова ядра, подающего
команду контроллеру, драйвер устройства должен вызвать операцию rec e i ve.
После получения уведомления драйвер может продолжить выполнение дейст­
вий по обслуживанию прерывания.
Описанный механизм предполагает наличие прерывания. Несмотря на это, сле­
дует учесть возможность сбоя. Чтобы подготовиться к возможному отсутствию
прерывания, процесс может запросить системное задание установить стороже­
вой таймер. Сторожевые таймеры генерируют уведомления, а, значит, посредст­
вом операции r e c e ive можно получить два вида уведомлений - о появлении
прерывания и об истечении таймера. Это не является проблемой, поскольку по
уведомлению можно установить его источник. Хотя оба уведомления генериру­
ются системным заданием, отправителем уведомления о прерывании является
HARDWARE, а отправителем уведомления об истечении таймера - СLОСК.
Есть еще одна проблема. Если прерывание получено вовремя и сторожевой тай­
мер не успел истечь, то его истечение будет обнаружено позднее очередной опе­
рацией r e c e ive, возможно, в главном цикле драйвера. Одно из решений - от­
ключить таймер системным вызовом при получении уведомления от НARDWARE.
Альтернативная ситуация: если при следующем вызове rec e i ve сообщение от
CLOCK маловероятно, уведомление можно проигнорировать. Следует иметь в ви­
ду и еще один редкий сценарий: завершение дисковой операции после истечения
сторожевого таймера. Решения те же - запретить прерывание вызовом ядра при
истечении таймера либо игнорировать при операции r e c e i ve неожидаемые
сообщения от HARDWARE.
Самое время сказать о том, что при первом разрешении прерывания вызовом яд­
ра для него можно задать �политику�. � Политика� представляет собой обычный
флаг, определяющий, следует разрешать прерывание автоматически или остав­
лять его запрещенным до тех пор, пока драйвер устройства сам не разрешит его
290 Глава 3 . Ввод- вы вод

вызовом ядра. Драйверу диска после прерывания может потребоваться выпол­


нить большое количество работы, поэтому ему лучше разрешить прерывание
самостоятельно по окончании копирования данных.
Самым проблематичным в списке является четвертый пункт. Поддержка клавиату­
ры осуществляется драйвером t ty, обслуживающим как ввод, так и вывод. Кроме
того, возможна поддержка нескольких устройств. Это означает, что вывод может
приходить как с локальной клавиатуры, так и от удаленного пользователя, подклю­
ченного через сеть или последовательный порт. Допустимо также выполнение
нескольких процессов, каждый из которых генерирует вывод для своего локального
или удаленного терминала. Если вы не знаете, когда может произойти (и может
ли вообще) прерывание, недопустимо использовать блокирующий вызов rece ive
для получения ввода от одного источника в то время, как этому же процессу
требуется реагировать на ввод других источников и выводить данные самому.
В MINIX 3 предусмотрено несколько методов разрешения этой проблемы. Основ­
ной метод, используемый драйвером терминала для обработки клавиатурного
ввода, состоит в максимально быстром отклике на прерывание, исключающем
потерю символов. Перенос данных из аппаратной части клавиатуры в буфер
производится как можно меньшим количеством действий. Кроме того, после
буферизации перед завершением обработки прерывания считывание клавиатуры
осуществляется еще раз . Прерывания генерируют уведомления, не блокирую­
щие отправителя, что помогает предотвратить потери ввода. Доступна и опера­
ция rec e i ve, также не вызывающая блокировку, хотя она и используется лишь
для обработки сообщений в случае аварии системы. Сторожевые таймеры вызы­
вают подпрограмму, проверяющую клавиатуру.

3 . 4 . 2 . Драйверы устройств в M I N IX 3
Для каждого из классов устройств ввода-вывода в MINIX существует отдельный
драйвер. Эти драйверы являются полноценными процессами, каждый со своим
состоянием, регистрами, стеком и т. д. Драйверы взаимодействуют с файловой
системой при помощи механизма передачи сообщений, как и любые другие про­
цессы MINIX 3. Код простого драйвера можно уместить в одном файле. Драйверы
виртуального диска, жесткого диска и дисковода для дискет расположены каждый
в своем файле; кроме того, они используют общие функции работы с блочными
устройствами, вынесенными в файль1 dr i ver . с и drv l ib . с. Подобное разделе­
ние программного обеспечения на части, зависимые и независимые от аппарат­
ного обеспечения, позволяет с легкостью приспосабливать его к самым разным
конфигурациям оборудования. Хотя дисковые драйверы пользуются некоторы­
ми фрагментами общего кода, каждый из них работает как отдельный процесс.
Это изолирует драйверы друг от друга и ускоряет передачу данных.
Подобным же образом организован и исходный код драйвера терминала, где ап­
паратно-независимый код помещен в файле t ty . с, а в отдельных файлах нахо­
дится код для поддержки различных типов устройств, таких как отображаемые
на память консоли, клавиатура, последовательные интерфейсы и псевдотерми­
налы. Однако в этом случае все типы устройств обслуживает один процесс.
3 .4. Ввод- вывод в M I N IX 3 29 1

Кроме того, для групп сходных устройств, например для дисков и терминалов,
есть еще и заголовочные файлы. Файл dr i ver . h поддерживает все драйверы
блочных устройств, а файл t ty . h предоставляет общие определения для всех
типов терминалов.
Поскольку система MINIX 3 построена на исполнении компонентов операционной
системы в пользовательском пространстве как полностью независимых процессов,
для нее характерны высокая степень модульности и приемлемая эффективность.
Это - одно из кардинальных отличий MINIX 3 от UNIX. В MINIX процесс, для того
чтобы прочитать файл, посылает сообщение файловой системе. В свою очередь,
файловая система может послать сообщение драйверу диска, запрашивая чтение не­
обходимого блока. Драйвер диска использует вызовы ядра для фактического ввода­
вывода и копирования данных между процессами. Эта последовательность (не­
сколько упрощенная по сравнению с реальностью) изображена на рис. 3. 1 2, а.
За счет того, что взаимодействие происходит через механизм сообщений, обеспе­
чивается стандартный коммуникационный интерфейс между частями системы.

Система, структурированная
с помощью процессов Монолитная система

Процесс

П ространство
пользователя

,,,,,. - -- - .......
(Аппаратное' 1 Пространство
.......
..рбеспечени�
_ _
_ ,,,,,. ядра

а б
Рис. 3. 1 2 . Два варианта взаимодействия пользователя и системы: а - сообщения с запРQСами
и ответами между четырьмя независимыми п роцессами; б - передача управления
в адресное п ростра нство ядра из пользовател ьского п ространства по п рерыванию

В UNIX у каждого процесса есть две части: одна в пользовательском пространст­


ве и другая в пространстве ядра (рис. 3 . 1 2 , б). Когда процесс делает системный
вызов, операционная система некоторым волшебным образом переключается из
пользовательской области в область ядра. Такой механизм является пережитком
MULTICS, где переключение было простым вызовом процедуры, а не ловушкой
с сохранением состояния пользовательской части процесса, как в UNIX.
В UNIX драйверы устройств являются простыми подпрограммами ядра, которые
может вызывать часть процесса, загруженная в пространстве ядра. Когда драйверу
292 Глава З . Ввод-вывод

надо дождаться прерывания, он вызывает процедуру, и процесс засыпает до тех


пор, пока прерывание его не разбудит. Заметьте, что пользовательский процесс
при этом также приостанавливается, так как это - две части одного процесса.
Можно бесконечно приводить доводы в пользу преимущества монолитных сис­
тем (таких как UNIX) над структурированными с помощью процессов (наподо­
бие MINIX 3), и наоборот. Используемый в MINIX 3 подход обеспечивает более
высокую степень модульности, более прозрачный интерфейс между компонента­
ми и легко расширяется на распределенные системы, где разные процессы могут
выполняться на разных машинах. Подход UNIX более эффективен, так как вызов
подпрограммы выполняется гораздо быстрее, чем передача сообщения. Система
MINIX 3 структурирована в виде множества процессов, так как мы убеждены,
что по мере роста производительности компьютерных систем ясная структура
программного обеспечения окажется важнее, чем небольшая потеря в произ ­
водительности. Для большинства операционных систем плата за переход к ис­
полнению компонентов в пользовательском пространстве составила бы 5 - 1 О %.
Тем не менее многие системные разработчики не разделяют это убеждение.
В этой главе обсуждаются драйверы виртуального диска, жесткого диска, таймера
и терминала. Стандартная комплектация MINIX 3 включает в себя также драй­
веры для дисковода гибких дисков и принтера, которые мы подробно не рассмат­
риваем. Кроме того, имеются драйверы для последовательного интерфейса RS-
232, приводов CD-ROM, адаптера Ethernet и звуковой карты. Они могут быть
скомпилированы отдельно и задействованы � на лету» в любой момент времени.
Все драйверы взаимодействуют с другими частями MINIX 3 единообразно: путем
отправки сообщений. Сообщения с запросами содержат различные поля, в кото­
рые помещаются код операции (чтение или запись) и ее параметры. Получив со­
общение, драйвер пытается выполнить запрос и отправляет ответное сообщение.
Поля сообщений запроса и ответа для блочного устройства приведены в табл. 3.3.
Сообщение с запросом содержит адрес буфера для обмена данными. Ответ вклю­
чает в себя информацию о состоянии, которую вызвавший процесс может ис­
пользовать для проверки правильности выполнения запроса. Для символьных
устройств поля сообщений могут несколько отличаться от драйвера к драйверу,
но в целом имеют сходную структуру. Например, сообщения для драйвера терми­
нала - это адрес структуры данных, описывающей все возможные параметры,
такие как символы, используемые функциями построчного редактирования.
Назначение драйверов - воспринимать и выполнять запросы от других процес­
сов (обычно от файловой системы). Все драйверы блочных устройств написаны
так, чтобы получить запрос, выполнить его и послать ответ. Такая структура
означает простоту - драйверы выполняются строго последовательно, в них нет
внутренней многозадачности. Когда обслуживание аппаратного запроса заверше­
но, драйвер вызывает операцию rece ive, чтобы указать, что далее он ожидает
только сообщений прерываний, но не новых запросов на обслуживание. Новые
запросы при этом откладываются до тех пор, пока не будет обслужен текущий
запрос (принцип рандеву). Драйвер терминала работает несколько по-другому,
так как обслуживает несколько устройств. Поэтому он может воспринимать
3. 4. Ввод- вывод в M I N IX 3 293

задание на ввод с клавиатуры и тогда, когда еще не завершено чтение с последо­


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

Поля запросов от файловой системы к драйверам блочных устройств


Табл и ца 3 . 3 .
и ответов, отправляемых файловой системе
Поле Тип Значение
За прос
m m type
. _ iпt Запрошенное действие
m.DEVICE iпt Вспомогательный номер устройства
m.PROC_NR iпt Процесс, запрашивающий ввод-вывод
m.COUNT iпt Количество байтов или код ioctl
m.POSITION loпg Позиция устройства
m.ADDRESS char* Адрес в пределах процесса, сделавшего запрос
О твет
m.m_type iпt Всегда имеет значение DRIVER_REPLУ
m.REP_PROC_NR iпt То же, что и PROC_NR в запросе
m.REP_STATUS iпt Количество переданных байтов или код ошибки
Структура основной части кода у каждого драйвера одинакова и в общих чертах
приведена в листинге 3. 1 . При запуске системы управление передается каждому из
драйверов с целью дать им возможность инициализировать внутренние таблицы
и тому подобные данные. Завершив инициализацию, каждый из драйверов делает
вызов re c e i ve, чтобы получить сообщение, и переходит в состояние блокировки.
Когда сообщение приходит, запоминается, какой процесс его отправил, после чего
вызывается подпрограмма для выполнения необходимых действий. После того как
обработка запроса завершена, вызвавшему процессу отправляется сообщение
с результатами, а драйвер переходит к началу цикла и ожидает новых сообщений.
Л и стинг 3 . 1 . Общая структура основной процедуры драйвера ввода-вывода
me s s age me s s ; / * Буфер с о о бщений * /

vo i d i o_dr i ver ( )
ini t i a l i z e ( ) ; / * Вы зы вает с я только один раз при инициализ ации * /
whi l e ( TRUE ) {
r e c e ive ( ANY , &me s s ) ; / * Ожидаем з апро с * /
c a l l e r = me s s . s ourc e ; / * Проце с с , с дела вший з апро с * /
s w i t c h ( me s s . typ e ) {
c a s e READ : r c o de = dev_read ( &me s s ) ; break ;
c a s e WR I T E : r c o d e = dev_wr i t e ( &me s s ) ; break ;
/ * Тут находитс я код для выполнения дру гих операций ,
таких как OPEN , CLOSE и I OCTL * /
de f au l t : r c o de = ERROR ;

me s s . type = DRIVER_REPLY ;
me s s . s t a t u s = r c o de ; / * Код в о з врата * /
s end ( c a l l e r , &me s s ) ; / * Отправляем о т в е т * /
294 Глава З. Ввод-вывод

За выполнение различных действий, поддерживаемых драйвером, ответственны


подпрограммы с именами dev_xxx. Код завершения помещается в поле RE P_
STATUS ответа, он равен количеству переданных байтов (ноль или положитель­
ное число), если операция закончилась успехом, или номер ошибки (отрицатель­
ное число) в противном случае. Количество переданных байтов может отличаться
от запрошенного, например, когда достигнут конец файла. В случае терминалов
за один запрос возвращается не больше одной строки, даже если запрошено боль ­
ше информации.

3 . 4 . 3 . Ап паратно- независимый код


ввода-вывода в M I N IX
Весь аппаратно-независимый код ввода-вывода в MINIX 3 является частью фай­
ловой системы. Система ввода-вывода так тесно связана с файловой системой,
что они обе объединены в один процесс. Функции файловой системы перечисле­
ны в пункте 3.2.4, за исключением функций захвата и освобождения отдельных
устройств, которых в существующей конфигурации MINIX 3 нет. Тем не менее,
если такая потребность возникнет, эти функции могут быть легко добавлены
в код соответствующих драйверов.
В дополнение к взаимодействию с драйверами, к буферизации и выделению бло­
ков, файловая система также решает задачи защиты и управления индексными
узлами, каталогами и монтированными файловыми системами. Подробно эта те­
ма рассмотрена в главе 5.

3 . 4 . 4 . П рограммы ввода - вывода


пол ьзовател ьского уровня в M I N IX
В программах ввода-вывода пользовательского уровня также применяется обоб­
щенная модель, описанная ранее в этой главе. Для выполнения системных
вызовов и стандартных С - функций, требуемых стандартом P O S I X ( напри­
мер, функций форматного ввода-вывода p r int f и s c an f ) , предоставляются
библиотечные процедуры. В стандартной конфигурации M INIX имеется так­
же демон lpd, который занимается поддержкой очереди печати документов ,
отправленных на печать командой l p . В стандартной комплектации MINIX
имеется еще ряд демонов, ответственных за различные сетевые функции. Опи­
сываемая в этой книге комплектация MINIX 3 поддерживает большинство се­
тевых операций. Все, что требуется, - это включить во время запуска сетевой
сервер и драйверы адаптеров Ethernet. Перекомпиляция драйвера терминала
с поддержкой псевдотерминалов и последовательного порта позволит входить
в систему с удаленных терминалов и иметь доступ к сети через последова­
тельные интерфейсы (включая модемы) . Сетевой сервер имеет тот же приори­
тет, что менеджер памяти и файловая система. Все они выполняются как пользо­
вательские процессы.
3.4. Ввод-вывод в M I N IX 3 295

3 . 4 . 5 . Взаимная блоки ровка в M I N IX


Как UNIX, так и MINIX 3 относятся к взаимной блокировке одинаково - они
полностью игнорируют проблему. В MINIX 3 нет выделенных устройств ввода­
вывода, хотя, если кто-то хочет �подвесить� стандартный промышленный лен­
точный DAТ-привод на персональный компьютер, написать к нему программное
обеспечение не составит особенного труда. Говоря коротко, единственное место,
в котором могут возникнуть взаимные блокировки, это общие ресурсы, такие
как ячейки таблицы процессов, ячейки таблицы индексных узлов и т. д., исполь­
зуемые неявно. Ни один из известных алгоритмов борьбы с взаимными блоки­
ровками не подходит для подобных ресурсов, которые явно не запрашиваются.
В действительности, сказанное - не совсем правда. Помимо того что пользова­
тельский процесс может оказаться в тупиковой ситуации, в самой операционной
системе есть несколько мест, где необходимы особые меры предосторожности,
позволяющие избежать проблем. Главное - это взаимодействие между процес­
сами посредством передачи сообщений. Например, для обмена сообщениями поль­
зовательским процессам разрешено задействовать единственный метод s endrec.
По этой причине пользовательский процесс никогда не будет заблокирован вызо­
вом re c e i ve при отсутствии других процессов, желающих вступать с ним в кон­
такт при помощи вызова s end. Серверы пользуются вызовами s end и s endrec
только для взаимодействия с драйверами устройств, а те, в свою очередь, - толь­
ко для взаимодействия с системным заданием, находящимся на уровне ядра. Для
тех редких случаев, когда серверам необходимо общаться между собой (напри­
мер, менеджеру процессов вести обмен с файловой системой во время инициа­
лизации своих фрагментов таблицы процессов), порядок взаимодействия во из­
бежание блокировок тщательно продуман. Кроме того, на самом низком уровне
системы обмена сообщениями выполняется проверка, позволяющая гарантиро­
вать, что при попытке одного процесса отправить сообщение другому получа­
тель не попытается сделать то же самое.
Помимо упомянутых ограничений, в MINIX 3 введен новый примитив not i fy
для обработки ситуаций, когда необходимо передавать сообщения �снизу вверх�.
Примитив not i fy не приводит к блокировке, и уведомления сохраняются в слу­
чае, если их получатель недоступен. По мере изучения реализации драйверов
устройств MINIX 3 мы увидим, насколько интенсивно используется механизм
уведомлений.
Еще один способ предотвращения взаимных блокировок - разграничение досту­
па. Разграничивать доступ к устройствам можно и без поддержки операционной
системы. В качестве по-настоящему глобальной переменной, доступной всем про­
цессам, изумительно подходит имя файла. Н аличие или отсутствие определенного
файла может быть легко замечено любым процессом. В MINIX 3, как и в UNIХ­
подобных системах, существует специальный каталог, / u s r / spoo l / locks / , где
процессы, чтобы обозначить занятость определенного ресурса, могут создавать
подобные файлы блокировки (lock file), которые иногда называют файловыми се­
мафорами. Файловая система MINIX также поддерживает блокировку файлов,
предписанную стандартом POSIX. Но ни один из этих механизмов не является
296 Глава 3. В вод- вывод

принудительным. Все зависит от того, обращает ли программа внимание на эти


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

3 . 5 . Бло ч н ы е устройства в M I N IX 3
В MINIX 3 поддерживается несколько типов блочных устройств, поэтому мы
начнем с обсуждения общих аспектов, затем изучим виртуальный диск, жесткий
диск и дисковод для дискет. Каждое из этих устройств по-своему интересно.
Виртуальный диск необычен тем, что это устройство обладает всеми атрибутами
обычного блочного устройства, за исключением того, что при его работе ника­
кого реального ввода-вывода не происходит. Этот �диск� целиком находится
в оперативной памяти. Поэтому драйвер устроен просто и является хорошей от­
правной точкой для изучения. Жесткий диск позволяет понять, на что похож
драйвер реального диска. Некоторым может показаться, что привод для дискет
(накопитель на гибких дисках) проще, чем жесткий диск, но это не так. Мы не
будем обсуждать драйвер гибкого диска в подробностях, но укажем на некото­
рые связанные с ним сложности.
Закончив с драйверами блочных устройств, мы приступим к драйверу термина­
ла (клавиатуры и дисплея), который важен для любой системы и, к тому же, яв­
ляется удачным примером драйвера символьного устройства.
Каждый из разделов содержит описание соответствующего оборудования и прин­
ципов построения программы драйвера, а также обзор реализации и сам код.
Такая организация делает информацию полезной еще и тем, кто не заинтересо­
ван в деталях.

3 . 5 . 1 . Обзор драйверов
блоч ных устройств M I N IX 3
Ранее мы упомянули, что основной код большинства драйверов ввода-вывода
имеет единую структуру. У MINIX 3 в ядре всегда в наличии, как минимум, два
встроенных блочных драйвера ввода-вывода: драйвер виртуального диска и с ним
драйвер жесткого диска или дисковода для дискет. Как правило, присутствуют
три блочных драйвера, поскольку в системе есть и дисковод для дискет, и жест­
кий диск стандарта IDE (Integrated Drive Electronics - встроенный интерфейс
дисковых накопителей). Драйверы всех блочных устройств компилируются не­
зависимо, однако используют общую библиотеку исходного кода.
В состав предыдущих версий MINIX иногда включался отдельный драйвер
CD-RO M, который при необходимости можно было добавить. В настоящий
3 . 5 . Блочные устройства в M I N IX 3 297

момент такие драйверы считаются устаревшими. Они были нужны для поддерж­
ки собственных интерфейсов различных производителей дисководов. Современ­
ные накопители, как правило, подключаются к I DЕ-контроллеру, хотя на пор­
тативных компьютерах дисководы часто используют интерфейс с шиной USB
( Universal Serial Bus - универсальная последовательная шина). Полная версия
драйвера жесткого диска MINIX 3 включает поддержку CD-ROM, однако в дан­
ной книге она не рассматривается.
Конечно, каждому драйверу нужно выполнить некоторые действия для инициа­
лизации. Драйвер виртуального диска должен зарезервировать необходимый объ­
ем памяти, драйвер жесткого диска - определить параметры устройства и т. д.
Все драйверы дисков вызываются, инициализируются и, завершив инициализа­
цию, входят в бесконечный цикл. Этот цикл никогда не завершается, в нем нет
команды выхода. В цикле драйвер получает сообщение, вызывается одна из функ­
ций обработки и генерируется ответное сообщение.
Основной цикл, вызываемый каждым из драйверов, компилируется из файлов
каталога драйвера, в том числе dr ivers / l ibdr i ve r / dr i ver . с, а затем копия
объектного файла dri ver . о компонуется в исполняемый файл драйвера. Бла­
годаря такому подходу каждый драйвер имеет возможность передать в основной
цикл параметр - указатель на таблицу адресов функций, которые драйвер дол­
жен косвенно вызывать для выполнения требуемых действий.
Если бы драйверы компилировались в единый исполняемый файл, то потребова­
лась бы всего одна копия главного цикла. На самом деле, главный цикл был на­
писан для более ранней версии MINIX, в которой драйверы компилировались
вместе. В MINIX 3 акцент сделан на максимальную индивидуализацию компо­
нентов операционной системы, однако использование общего исходного кода
разными программами остается хорошим способом повышения надежности.
Создав свободный от ошибок фрагмент кода, вы обеспечиваете им все драйверы,
а если в драйвере обнаружилась ошибка, то с большой вероятностью она присут­
ствует и в других драйверах. В результате тестирование общего кода можно про­
вести более тщательно.
В файле drivers / l iЬdr i ve r / drvl i b . c определен набор функций, потенци­
ально полезных для различных дисковых драйверов. Эти функции скомпилирова­
ны в объектный файл drvl ib . о. Их можно было бы включить в файл dr i ver . о,
однако полный функциональный набор нужен далеко не всем драйверам. Напри­
мер, самый простой драйвер memo ry компилируется исключительно с файлом
dri ver . о. Драйвер at_wi n i , напротив, при компиляции использует и файл
dri ver . о, и файл drvl ib . о .
Главный цикл приведен в листинге 3.2. Команды, подобные следующей, являют­
ся косвенными вызовами функций:
c ode = ( * ent ry_po i nt s - >dev_read ) ( &me s s ) ;

Каждый драйвер вызывает свою функцию dev_read, хотя главный цикл един
для всех драйверов. Но некоторые операции, например c l o s e , достаточно про­
сты, чтобы для всех устройств можно было использовать одну и ту же функцию.
298 Глава 3 . Ввод-вывод

Листинг 3 . 2 . Основная процедура драйверов устройств ввода-вывода


me s s age me s s ; ! * Буфер сообщений * /

vo i d shared_i o_dr ive r ( s t ru c t dr iver_t aЫ e * en t ry_po int s ) {


! * Перед входом в эту функцию каждый из драй в еров инициализируется * /
whi l e ( TRUE ) {
rece ive ( ANY , &me s s ) ;
c a l l e r = me s s . s ourc e ;
swi t c h ( me s s . type ) {
c a s e READ : rc ode = ( * ent ry_po i nt s - >dev_read ) ( &me s s ) ; brea k ;
c a s e WRITE r c ode = ( * en t ry_po i nt s - >dev_wr i t e ) ( &me s s ) ; break ;
! * Тут находится код для выполнения других операций ,
таких как OPEN , CLOSE и IOCTL * /
de fau l t : rc ode = ERROR ;

me s s . type = DRIVER_REPLY ;
me s s . s t at u s = rc ode ; / * Код воз врата * /
s end ( c a l l e r , &me s s ) ;

Есть шесть различных операций, которые могут быть запрошены у каждого


драйвера устройства. Им соответствуют значения в поле m . m_type сообщения
(см. табл. 3.3). Вот они: OPEN, C LOSE, READ, WRITE, IOCTL, SCATTERED_IO.
Большая часть этих операций, вероятно, знакома читателям, имеющим опыт
программирования. На уровне драйвера устройства они сводятся к системным
вызовам с соответствующими именами. Смысл операций READ и WRI TE , на­
пример, вполне понятен. Эти операции передают блок данных из памяти вы­
звавшего процесса в устройство или наоборот. Обычно операция READ не пере­
дает управление в вызвавший процесс до тех пор, пока не завершена передача
данных. Но операционная система может буферизовать передаваемые данные,
тогда вызов может завершиться сразу, а данные переданы позже. Это никак не
сказывается на сделавшей вызов программе, сразу после него она вправе исполь­
зовать буфер, так как операционная система уже скопировала нужные ей дан­
ные. Операции OPEN и C LOSE для устройства означают то же, что и системные
вызовы open и c l o s e для файлов. Операция OPEN проверяет, что устройство
доступно, и возвращает код ошибки, если это не так. Операция C LOSE должна
гарантировать, что все буферизованные данные переданы до полного заверше­
ния обмена информацией с устройством.
Возможно, назначение операции I OCTL не столь очевидно. У многих устройств
ввода-вывода есть параметры, значение которых иногда нужно проверять и, пред­
положительно, менять. Выполнение этих действий обеспечивает операция IOCTL.
Хороший пример использования этой операции - управление быстродействием
последовательного интерфейса или настройка механизма контроля четности.
Для блочных устройств I OCTL применяется реже. В частности, в MINIX 3 при
помощи IOCTL можно определять и управлять разбиением диска на разделы
(хотя это можно бьmо бы сделать просто при помощи операции чтения и записи
блока данных).
3. 5 . Блоч ные устройства в M I NIX 3 299

Операция SCATTERED_I O, без сомнений, известна менее всех других. Дело в том,
что трудно достигнуть приемлемой производительности блочного устройства,
если запрашивать данные последовательно, отдельными порциями. Исключение
составляют лишь очень быстрые устройства, такие как виртуальный диск. Опе­
рация SCATTERED_I O позволяет системе сделать запрос на чтение или запись
нескольких блоков. В случае операции READ система пытается предугадать,
какие блоки может запросить процесс, и считывает их заранее. В таком запросе
не все затребованные данные обязательны. Каждый из запросов блока данных,
передаваемых драйверу устройства, можно модифицировать, установив бит, со­
общающий, что запрос не обязателен. В результате файловая система может соз­
дать запрос, который в переводе на человеческий язык звучал бы так: � Было бы
неплохо прочитать все эти данные, но все они вовсе не нужны мне прямо сей­
час». Далее устройство само решает, что лучше делать. Например, гибкий диск
может прочитать все данные в пределах одной дорожки, говоря тем самым: � эти
данные я для тебя прочитаю, а остальные попроси потом, мне слишком долго
переходить на другую дорожку».
Когда необходимо записать данные, не возникает вопроса, обязательно ли запи­
сывать каждый отдельный блок - все, что запрошено, обязательно для выполне­
ния. Однако система вправе буферизовать несколько запросов на запись в наде­
жде на то, что сразу их удастся обработать быстрее, чем по отдельности. При
запросе SCATTERE D_I O, будь то для чтения или для записи, список запрошен­
ных блоков сортируется, что позволяет считывать данные более эффективно,
чем если бы они считывались в порядке поступления. Кроме того, передача не­
скольких блоков за один раз уменьшает количество сообщений, пересылаемых
внутри MINIX 3.

3 . 5 . 2 . Общие п рограм м ы для драй веров


блоч н ых устройств
Определения для всех драйверов блочных устройств находятся в файле dri vers /
l ibdr i ver / dr i ver . h. Самая главная часть этого файла - структура driver
(строки 1 0829- 1 0845 ), посредством которой в главный цикл передается список
адресов функций, выполняющих запросы. Дополнительно здесь определяется
структура devi c e (строки 1 0856- 1 0859), в которой хранится наиболее важная
информация о разделах, базовый адрес и размер в байтах. Формат этой структу­
ры был выбран так, чтобы устройства, основой которых является оперативная
память, могли задействовать данные без преобразования, обеспечивая высокую
скорость отклика. Для реальных дисков характерно такое количество разнооб­
разных факторов, влияющих на время отклика, что преобразование байтовых ад­
ресов в секторы не играет принципиальной роли.
Код главного цикла и общих для всех драйверов блочных устройств функций
находится в файле dr i ver . с . Выполнив все действия по инициализации, зави­
сящие от конкретных устройств, каждый драйвер вызывает процедуру driver_
t a s k, передавая в качестве аргумента вызова заполненную структуру driver.
300 Глава 3. Ввод- вывод

Далее определяется адрес буфера DМА-устройства, и управление переходит в глав­


ный цикл (строки 1 1 07 1 - 1 1 1 20).
В инструкции swi t ch главного цикла выполняются косвенные вызовы функ­
ций, адреса которых содержатся в структуре dr i ver. Вызовы функций являют­
ся реакцией на получение сообщений первых пяти типов DEV_OPEN, DEV_
-

CLOSE, DEV_IOCTL, DEV_CANCE L и DEV_SELECT. Сообщения DEV_READ и DEV_


WRI TE приводят к непосредственному вызову функции do_rwdt , а сообщения
DEV_GATHER и DEV_SCATTER -функции do_vrdwt . Заметьте, что структура
dri v e r передается как аргумент во все вызовы, как косвенные, так и непо­
средственные, поэтому все вызываемые подпрограммы могут к ней обращаться.
Функции do_rwdt и do_ vrdwt выполняют некоторую предварительную об­
работку, однако затем также косвенно обращаются к зависящим от устройств
подпрограммам.
Другие выходы инструкции swi t ch, включая HARD_INT, SYS_S IG и SYN_
ALARМ, относятся к уведомлениям. В них также используются косвенные вызо­
вы, однако по завершении каждого из них исполняется инструкция cont i nue,
возвращающая управление в начало цикла, минуя очистку и генерацию ответно­
го сообщения.
После выполнения запрошенных в сообщении действий может потребоваться
определенная подготовка перед переходом к следующей операции. Например,
в случае гибкого диска можно запустить таймер, который по истечении некото­
рого времени отключит привод, если за это время не будет других запросов. Для
выполнения подобных действий также используется косвенный вызов. После
очистки генерируется сообщение, которое отправляется в ответ процессу, сде­
лавшему вызов (строки 1 1 1 1 3 - 1 1 1 1 9). Подпрограммы, обслуживающие сообще­
ния, могут отменять создание ответа, возвращая значение E DONTRE PLY, однако
ни один из существующих драйверов не прибегает к такой возможности.
Первое действие, которое выполняет каждый драйвер при входе в главный цикл, -
вызов функции i n i t_bu f f e r ( строка 1 1 1 26), задающей буфер для операций
прямого доступа к памяти. Причина, по которой эта инициализация вообще
нужна, кроется в странностях работы первых компьютеров IBM РС, для кото­
рых было необходимо, чтобы буфер не пересекал границу в 64 Кбайт. Другими
словами, если размер буфера 1 Кбайт, он может начинаться по адресу 645 10, но
не по адресу 645 14, так как в последнем случае будет превышена граница, нахо­
дящаяся по адресу 65536.
Это неприятное требование проистекает из того, что в IBM РС применялся старый
чип контроллера прерываний Intel 8237 А с 16-разрядным счетчиком. Так как для
DMA используются абсолютные, а не относительные сегментные адреса, требовал­
ся счетчик большей емкости. На старых машинах, способных адресовать только
1 Мбайт памяти, младшие 16 бит адреса DMA загружались в 8237 А, а оставшиеся
старшие 4 бита помещались в 4-разрядный регистр-переключатель. У более совре­
менных машин применяется 8-разрядный переключатель, что позволяет адресо­
вать 16 Мбайт. Но когда адрес в 8237 А переходит от OxFFFF к ОхОООО, не фор­
мируется признак переноса, и адрес DMA внезапно прыгает вниз на 64 Кбайт.
3 . 5 . Блочные устройства в M I N IX 3 30 1

Переносимая С-программа не может оперировать абсолютными адресами, по­


этому не в силах предотвратить недопустимого положения буфера. Эта пробле­
ма решается так: выделяется буфер вдвое большего размера, чем нужно (строка
1 1 044), а специальный указатель tmp_bu f (строка 1 1 045) содержит адрес реаль­
но используемого буфера. Функция i n i t_bu f f er сначала пробует установить
этот указатель так, чтобы он ссылался на начало выделенного буфера, а затем
проводит проверку, допустимо ли такое положение. Если нет, то значение ука­
зателя tmp_bu f увеличивается на количество реально затребованных байтов.
Таким образом, всегда остается неиспользованный блок памяти, но граница
64 Кбайт никогда не попадает внутрь буфера.
У новых компьютеров семейства IBM РС более совершенные контроллеры DMA,
поэтому код мог бы быть упрощен, а количество запрашиваемой памяти - умень­
шено. Но нет гарантии, что проблема никогда не проявится. Если же вы рассчи­
тываете на это, то должны знать, как действовать в случае ошибки. Если требует­
ся буфер объемом 1 Кбайт, то с вероятностью 1/64 выделенный буфер окажется
непригоден для компьютера со старым контроллером DMA. Каждый раз, если
изменение кода ядра приводит к изменению размера его исполняемых файлов,
ошибка может проявить себя с той же вероятностью. Ошибка может не прояв­
лять себя, поэтому, когда в следующем месяце или году она возникнет, ее свя­
жут с последними изменениями в коде, а не с реальной причиной. Подобные
�особенности� аппаратного обеспечения чреваты длительным поиском ошибок,
особенно если в технической документации о них ни слова не говорится ( как
в этом случае).
Следующая функция в файле dri v e r . с - это do_rdwt ( строка 1 1 1 48). О на,
в свою очередь, косвенным вызовом запускает одну из двух функций dr_prepare
или dr_t rans f e r, опираясь на переданные адреса из структуры dri ver. В даль­
нейшем, чтобы обозначить функцию, вызываемую через указатель, мы будем ис­
пользовать запись вида * функция_ука з атель, напоминающую язык С.
Далее проверяется, что количество переданных байтов больше нуля, после чего
функция do_rdwt вызывает * dr_prepare. Эта операция вводит в структуру
devi c e базовый адрес и размер диска, раздела или подраздела, к которому осу­
ществляется доступ. Для драйвера памяти, не поддерживающего разделы, лишь
проверяется корректность вспомогательного номера устройства. Для жесткого
диска функция использует вспомогательный номер устройства, чтобы получить
размер соответствующего раздела или подраздела. Данный вызов не должен за­
кончиться неудачно, так как единственная причина, по которой это возможно -
неправильно указанное в операции op en устройство. Далее заполняется стан­
дартная структура iove c l типа i ove c_t , описание которой находится в файле
inc l ude /minix/ typ e . h (строки 2856-2859). Эта структура определяет вирту­
альный адрес и размер локального буфера, которым системное задание вос­
пользуется для копирования данных. Она же применяется как элемент массива
запросов в случае, если вызов требует считать несколько блоков. Адрес перемен­
ной и первого элемента массива такого же типа может быть обработан аналогич­
ным образом. Затем следует еще один косвенный вызов, на этот раз функции
302 Глава 3 . Ввод- вывод

* dr_t rans f er, которая копирует данные и выполняет другие действия, необхо­
димые для ввода-вывода. Все подпрограммы, обрабатывающие передачу, ожида­
ют в качестве аргумента массив запросов . В функции do_rdwt последним аргу­
ментом является 1, что указывает массив из одного элемента.
Как мы увидим при обсуждении программного обеспечения диска в следующем
разделе, обслуживание дисковых запросов в порядке поступления может быть
неэффективным, и данная подпрограмма позволяет конкретному устройству
обслуживать запросы в оптимальном для себя порядке. Косвенный вызов по
максимуму маскирует различия конкретных устройств. Для виртуального дис­
ка dr_t rans f er указывает на подпрограмму, совершающую вызов ядра, чтобы
скопировать данные из одной области физической памяти в другую посредством
системного задания, если вспомогательным устройством является / dev / rarn,
/ dev / rnern, / dev / krnern, / dev / bo o t или / dev / z e ro. Разумеется, при доступе
к / dev / nu l l никакого копирования не требуется. В случае реального диска
функция dr_t rans f er тоже должна запросить у системного задания передачу
данных. Тем не менее перед копированием (при чтении) или после него (при за­
писи) нужно дополнительно вызвать ядро, чтобы системное задание выполнило
фактический ввод-вывод, записав в регистры дискового контроллера байты, оп­
ределяющие место на диске, объем и направление передачи.
В подпрограмме передачи значение счетчика i ov_s i z e структуры i ove c l изме­
няется и содержит либо отрицательное значение (код ошибки), либо положитель­
ное значение, равное числу переданных байтов. Если число переданных байтов
равно нулю, это не обязательно означает ошибку; возможно, в процессе ввода­
вывода был достигнут конец устройства. При возвращении в главный цикл код
ошибки или число байтов записывается в поле RE P_STATUS ответного сообще­
ния процедуры dri ver_t a s k.
Следующая функция, do_vrdwt ( строка 1 1 182), обрабатывает разрозненные
запросы ввода-вывода. В сообщении драйверу, содержащем подобный запрос,
в поле ADDRE SS помещается адрес массива структур типа i ovec_t . Каждая из
этих структур хранит информацию для выполнения одного запроса: адрес буфе­
ра и число передаваемых байтов. В MINIX 3 подобный запрос может быть сде­
лан только для смежных блоков диска; начальное смещение на устройстве и вид
операции (чтение или запись) указаны в сообщении. Таким образом, все опера­
ции одного запроса являются либо чтением, либо записью и отсортированы со­
гласно следованию блоков в устройстве. В строке 1 1 198 проверяется, сделан ли
запрос от имени задания ввода-вывода внутри ядра. Эта проверка унаследована
из ранних редакций MINIX 3, в которых драйверы еще не были полностью пере­
писаны для выполнения в пользовательском пространстве.
Код данной операции очень похож на чтение и запись в процедуре do_rdwt .
Делаются те же косвенные вызовы аппаратно-зависимых подпрограмм * dr_
p repare и * dr_t rans f er. Цикл для обработки множественных запросов вы­
полняется внутри функции, на которую указывает * dr_t rans f e r. Последний
аргумент в этом случае не 1, а размер массива элементов i ovec_t . После заверше­
ния цикла массив запросов копируется в обратном направлении. Поле i o_s i z e
3 . 5 . Блочные устройства в M I N IX 3 303

каждого элемента массива показывает число байтов, переданных по соответст­


вующему запросу, и хотя общая сумма не посылается в сообщении, генерируе­
мом dri ver_t a s k, вызывающая сторона может извлечь ее из массива.
Следующие несколько функций из файла dri ver . с поддерживают выполнение
описанных ранее операций. Вызов * dr_narne возвращает имя устройства. Если
у устройства нет фиксированного имени, функция no_narne возвращает строку
nonarne. Некоторым устройствам не нужен ряд функций. Например, виртуаль­
ный диск не должен ничего делать по запросу DEV_CLOSE. В качестве пустой
функции используется функция do_nop, возвращаемое ею значение зависит от
типа запроса. Дополнительные функции nop_s i gna l, nop_al arrn, nop_prepare,
nop_c l e anup и nop_c anc e l также являются пустыми и предназначены для
устройств, которым не требуется предоставлять подобное обслуживание.
Наконец, функция do_diocnt l (строка 1 1 2 16) выполняет для блочных устройств
запросы DEV_IOCTL. Она сообщает об ошибке, если затребована любая другая
операция помимо считывания ( D I OGETP) или записи ( D I OSETP) информации
о разделе. Чтобы удостовериться, что устройство задано корректно, и получить
указатель на структуру devi c e с информацией о базовом адресе и размере разде­
ла, do_di o c t l вызывает * dr_prepare. При обработке запроса на чтение вызы­
вается специфичная для устройства функция * dr_georne t ry, с целью получить
информацию о цилиндрах, головках и секторах, относящихся к разделу диска.
В каждом случае выполняется вызов ядра sys_dat ac opy, чтобы системное за­
дание скопировало данные из пространства памяти драйвера в пространство по­
славшего запрос процесса.

3 . 5 . 3 . Библиотека поддержки драйверов


Файлы drvl ib . h и drv l i b . с содержат системно-зависимый код, поддержи­
вающий работу с разделами дисков на IBM РС-совместимых компьютерах.
Разбиение диска на разделы позволяет получить на одном накопителе несколько
логических дисков. Обычно на разделы дробятся жесткие диски, хотя MINIX 3
поддерживает и разбиение дискет. Вот несколько причин в пользу разбиения на
разделы.
1 . Стоимость мегабайта меньше для больших дисков. Если используются две
или более операционные системы с различными файловыми подсистемами,
дешевле иметь один большой диск, поделенный на несколько частей, чем не­
сколько маленьких.
2. У операционной системы могут иметься ограничения на максимальный размер
устройства. Обсуждаемая здесь версия M INIX 3 поддерживает максималь­
ный размер файловой системы 4 Гбайт, а у более старых версий было ограни­
чение до 256 Мбайт. В результате оставшееся дисковое пространство расхо­
довалось впустую.
3. Операционная система может использовать две или более файловые подсис­
темы. Например, для обыкновенных файлов - стандартная файловая подсис­
тема, а для области подкачки - подсистема с совершенно другой структурой.
304 Глава 3. Ввод- вывод

4. Бывает удобно поместить часть операционной системы на отдельное устрой­


ство. Например, для упрощения создания резервных копий можно записать
корневую файловую систему MINIX в отдельный маленький раздел. Это так­
же позволяет скопировать ее на виртуальный диск во время загрузки.
Механизм поддержки разделов зависит от платформы, причем эта зависимость
не относится к аппаратному обеспечению. К накопителям поддержка разделов
не привязана. Но если на одном и том же наборе устройств работают несколько
операционных систем, они должны иметь одинаковое представление о формате
таблицы разделов. Для IBM РС стандарт задает DОS-команда fdi s k, и другие
операционные системы, такие как MINIX 3, Windows и Linux, поддерживают этот
формат и могут сосуществовать с MS- DOS. Когда MINIX 3 переносится на дру­
гую платформу, имеет смысл использовать формат таблицы разделов, совмести­
мый с другими операционными системами, работающими на данной платформе.
Код для поддержки таблицы разделов на IBM РС-совместимых системах поме­
щен в отдельный файл drvl i b . c , а не включен в файл driver . c . Этому есть
две причины. Во-первых, не все типы дисков поддерживают разделы. Как было
отмечено ранее, драйвер памяти компонуется с объектным файлом dr i ver . о ,
однако н е использует функции, скомпилированные в drv l ib . о . Во-вторых, пе­
ренос MINIX 3 на другие платформы упрощается. Заменить один небольшой
файл легче, чем редактировать большой, включающий код условной компиля­
ции для различных вариантов окружения.
Основная структура данных, унаследованная от разработчиков программно-ап­
паратных средств, определена в файле inc l ude / i brn /p a r t i t i on . h, включае­
мом при помощи директивы # inc lude (строка 1 0900) в файл drvl i b . h. Сюда
входит информация о геометрии всех разделов (цилиндры, головки, секторы),
а также код, идентифицирующий тип файловой системы раздела, и флаг, указы­
вающий, является ли раздел загрузочным. После проверки файловой системы
большая часть этой информации MINIX 3 не нужна.
Функция part i t ion в файле drvl ib . h (строка 1 1426) вызывается, когда в пер­
вый раз открывается блочное устройство. В аргументы этой функции входит струк­
тура dri ver, поэтому первая в состоянии вызывать специфичные для данного
устройства функции. Также в функцию передаются вспомогательный номер уст­
ройства и параметр, задающий вариант разбиения на разделы (существуют различ­
ные варианты разбиения для гибкого диска, основного раздела и подраздела).
Чтобы проверить работоспособность устройства и поместить в структуру dev i c e
базовый адрес и размер раздела, вызывается функция * dr_prepare. Затем, чтобы
определить факт наличия таблицы разделов и считать ее, вызывается get_part_
t аЫ е. Если таблицы разделов не оказалось, работа завершается. В противном
случае вычисляется вспомогательный номер устройства для первого раздела (пра­
вила нумерации устройств определяются упомянутым ранее вариантов разбие­
ния на разделы) . Для случая основных разделов таблица сортируется так, чтобы
порядок следования разделов соответствовал другим операционным системам.
В этом месте делается другой вызов * dr_p r ep a r e , на этот раз ему в качест­
ве параметров передается только что вычисленный для первого раздела номер
3 .6. В и ртуал ь ные диски 305

устройства. Если устройство корректное, в цикле проверяются все записи в таблице


на этом устройстве с целью удостовериться, что хранящиеся в таблице ссылки не
выходят за пределы, отведенные устройству. Если все данные корректны, таблица
в памяти соответствующим образом изменяется. Может показаться, что такие
проверки сродни маниакальной идее, но таблицы разделов могут создаваться раз­
личными операционными системами. Программист, работавший с другой системой,
мог бы попытаться каким-то необычным образом использовать таблицу разделов,
или же таблица может быть просто испорчена. Мы с большим доверием относим­
ся к значениям, вычисляемым в MINIX. Лучше подстраховаться, чем сожалеть.
В том же самом цикле для всех разделов, идентифицированных как разделы
MINIX 3, рекурсивно вызывается функция part i t i on, чтобы получить инфор­
мацию о подразделах. Если раздел идентифицирован как расширенный, вместо
нее вызывается функция extpart i t i o n (строка 1 1 5 0 1 ) .
Последняя в действительности н е имеет никакого отношения к собственно опера­
ционной системе MINIX 3, поэтому мы не будем вдаваться в детали. Некоторые
другие операционные системы (например, Windows) используют расширенные
разделы ( extended partition). Такие разделы хранят подразделы в виде связанного
списка, а не массива фиксированного размера. Для простоты в MINIX 3 использу­
ется один и тот же механизм и для основных разделов, и для подразделов. Тем не
менее минимальная поддержка расширенных разделов присутствует, чтобы дать
возможность MINIX 3 считывать и записывать файлы и каталоги других операци­
онных систем. Эти операции просты; организовать полноценную поддержку мон­
тирования и расширенных разделов в качестве основных значительно труднее.
Функция get_pa rt_t aЫ e (строка 1 1 549) вызывает do_rdwt , чтобы получить
тот сектор устройства ( или вложенного устройства), в котором расположена табли­
ца разделов. Передаваемое функции смещение должно быть равно О, если функ­
ция вызывается для основного раздела, а для подразделов смещение принимает
отличное от нуля значение. Функция проверяет наличие сигнатуры (ОхАА55)
и возвращает результат проверки (истина или ложь). Кроме того, если таблица най­
дена, функция копирует таблицу по адресу, переданному ей через другой аргумент.
Наконец, функция s o r t (строка 1 1 582) сортирует по нижнему сектору все запи­
си в таблице разделов. Записи, помеченные как не имеющие раздела, исключа­
ются из сортировки и идут последними, даже если в поле, по которому велась
сортировка, у них записано нулевое значение. Для сортировки применяется про­
стой пузырьковый алгоритм. Более сложные способы сортировки для таблицы
из четырех записей ни к чему.

3 . 6 . В и ртуал ьны е диски


Теперь мы вернемся к изучению отдельных блочных устройств и подробно рас­
смотрим некоторые из них. Сначала мы обратимся к драйверу памяти, обеспечи­
вающему доступ к произвольным областям памяти. Его типовая область приме­
нения - резервир0вание части памяти для использования в качестве обычного
306 Глава 3. В вод- вывод

диска. Драйвер памяти мы также будем называть драйвером виртуального диска.


Виртуальный диск не обеспечивает долговременного хранения данных, но рабо­
тает со скопированными в него файлами исключительно быстро.
Виртуальный диск полезен при начальной установке операционной системы на
компьютер с единственным устройством, обслуживающим сменные носители
данных - дисководом для дискет, компакт-дисков и др. Если поместить корневую
файловую систему на виртуальный диск, сохранение файлов на дискету станет
невозможным, так как корневое устройство (единственный дисковод для дискет)
не подлежит демонтированию. Виртуальные диски используются также с демон­
страционными компакт-дисками, позволяющими оценить и опробовать работу
операционной системы, не копируя ее файлы на жесткий диск. Кроме того, раз­
мещение корневого устройства на виртуальном диске придает системе большую
гибкость: к ней можно монтировать любую комбинацию дисководов для дискет
и жестких дисков. MINIX 3 и множество других операционных систем распростра­
няются на компакт-дисках с описанными �демонстрационными• возможностями.
Мы увидим, что возможности драйвера памяти выходят за рамки поддержки
виртуального диска. Драйвер позволяет осуществлять прямой случайный доступ
к любой области памяти, побайтно или блоками произвольного размера. Функ­
ционируя подобным образом, драйвер ведет себя скорее как символьное, нежели
блочное, устройство. Драйвер памяти также поддерживает два символьных уст­
ройства - / dev / z e ro и / dev / nu l l .

3 . 6 . 1 . Аппаратное и п рограммное обеспечен ие


виртуал ьного диска
Идея устройства виртуального диска проста. Любое блочное устройство - это
накопитель, поддерживающий две команды: прочитать блок и записать блок.
Обычно блоки находятся на вращающихся дисках, таких как дискеты или пла­
стины жестких дисков. Виртуальный диск проще. Он хранит данные в предва­
рительно выделенной области оперативной памяти. Преимущество такого диска
в том, что он обеспечивает высокую скорость доступа (так как не требуется пере­
мещать головки и вращать носитель) и может быть использован для хранения
данных, к которым часто происходят обращения.
Отступая в сторону, нужно вкратце рассказать о различии между операционны­
ми системами, поддерживающими монтирование файловых подсистем ( UNIX,
MINIX), и системами, не имеющими такой поддержки (MS-DOS, Windows). Когда
файловые системы монтируются, корневая файловая система всегда находится
в фиксированном месте, а прочие (например, диски) встраиваются в указанное
место в дереве файлов, образуя единую файловую структуру. Смонтировав неко­
торую файловую систему, пользователь может больше не задумываться, на ка­
ком устройстве расположен файл.
В противовес такому подходу в других операционных системах пользователь дол­
жен указывать положение каждого файла либо явно (например: В : \ DI R \ F I LE),
либо пользуясь различными умолчаниями (текущее устройство, текущий каталог
3.6. Виртуал ьн ые диски 307

и т. д.). В небольших системах, с одним или двумя дисками, это можно вытер­
петь, но на мощных компьютерах с несколькими десятками устройств постоянно
следить за всеми дисками невыносимо. Заметьте, что UNIX работает на компью­
терах от IBM РС до рабочих станций и суперкомпьютеров, подобных IBM Blue
Gene/L, а MS- DOS используется только в малых системах.
На рис. 3. 13 показано устройство виртуального диска. Диск разбивается на п бло­
ков, в зависимости от того, сколько памяти для него выделено. Размер каждого
блока равен размеру блока реального диска. Когда драйвер получает запрос на
чтение или запись блока, он просто вычисляет адрес и производит чтение или
запись по этому адресу, вместо того чтобы обслуживать дискету или жесткий
диск. Обмен данными выполняется системным заданием. Ассемблерная проце­
дура ядра phy s_c opy копирует информацию в пользовательскую программу или
из нее с максимальной скоростью, поддерживаемой аппаратным обеспечением.

Основная память (ОЗУ)

{
Пользовательские
программы

RАМ-диск

....... 1 -й бпок RАМ-диска


....... При обращениях
к блоку О RАМ­

{
диска используется
Операционная эта память
система

Рис . 3. 1 3 . В и ртуальный диск

Драйвер виртуального диска может поддерживать несколько виртуальных дис­


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

3 . 6 . 2 . Драй вер виртуал ьного диска в M I N IX 3


Драйвер виртуального диска в MINIX 3 в действительности представляет собой
шесть тесно связанных между собой драйверов. Каждое сообщение для этого
драйвера указывает одно из следующих устройств:
О: / dev/ ram 2: / dev/ kmem 4: / dev/ boot
1: / dev / mem 3: / dev / nu l l 5: / dev / z ero

Первый из специальных файлов, / dev / r am, является �настоящим» виртуаль­


ным диском. Ни размер, ни положение занимаемой им области памяти в драйвере
308 Глава 3. Ввод- вы вод

не прописаны. Они определяются файловой системой при загрузке MINIX 3. По


умолчанию размер виртуального диска равен размеру образа корневой файловой
системы, чтобы можно было скопировать на него корневую систему. Варьирова­
ние параметрами загрузки позволяет установить больший размер виртуального
диска, а если не копировать на него образ корневой системы, то можно назна­
чить диску произвольный размер, который и в памяти умещается, и оставляет
достаточно места для работы MINIX 3. Определив размер диска, система выде­
ляет в памяти область достаточно большого размера, до того как начнет работу
менеджер процессов. Такая стратегия позволяет увеличивать или уменьшать
размер виртуального диска без перекомпиляции системы.
Следующие два устройства применяются для доступа к оперативной памяти или
к памяти ядра соответственно. Открыв устройство / dev / rnern и считывая из него
данные, можно обратиться к любой области памяти, начиная с абсолютного ну­
левого адреса (где расположены векторы прерываний реального режима). Обыч­
ные пользовательские программы не имеют такой потребности, но системным
программам, служащим для отладки операционной системы, это может оказать­
ся полезным. Записывая данные в / dev / rnern, можно изменять векторы преры­
ваний. Не стоит и говорить, что делать это должен только опытный пользова­
тель, твердо знающий, что он делает.
Файл / dev / krnern полностью подобен файлу / dev / rnern за тем исключением, что
нулевой адрес в файле соответствует нулевому адресу в пространстве ядра, поло­
жение последнего зависит от размера сегмента кода MINIX 3. Этот файл также
по большей части привлекается для целей отладки рядом специальных программ.
Обратите внимание на то, что области виртуального диска, соответствующие ука­
занным двум устройствам, перекрываются. Если вы точно знаете, где в памяти
находится ядро, то если прочитать из / dev / rnern данные по означенному адресу,
можно увидеть точно те же данные, которые находятся в начале файла / dev / krnem.
При перекомпиляции системы размер и положение ядра в памяти могут изме­
ниться, но файл / dev / krnern все равно будет содержать область памяти ядра.
Следующий файл в группе, / dev / nu l l , служит специально для ненужных дан­
ных. Он часто используется в программах оболочки, чтобы скрыть вывод про­
граммы, когда он не нужен. Например:
a . out > / dev / nu l l

Эта команда запустит программу а . out , н о все, что она выводит, будет игнориро­
ваться. Драйвер виртуального диска считает, что размер этого устройства равен О,
поэтому никаких данных на него не записывается и с него не считывается. При
попьпке считывания вы сразу же получите символ конца файла (End of File, EOF).
Освоив назначения файлов каталога / dev / , вы, возможно, обратили внимание
на то, что единственным блочным устройством среди них является / dev / rarn,
все остальные устройства - символьные. Драйвер памяти поддерживает еще
одно блочное устройство - / dev / bo o t . С точки зрения драйвера это блочное
устройство находится в памяти, как и / dev / rarn. Тем не менее для его инициа­
лизации требуется скопировать в память файл, присоединенный к загрузочному
3 . 6 . Ви ртуал ь ные диски 309

образу, а не начать с пустого блока, как в случае / dev / r am. Это устройство заре­
зервировано на будущее и не используется в текущей версии MINIX 3, которой
посвящена книга.
Наконец, последним устройством, поддерживаемым драйвером памяти, является
символьный файл / dev / z ero. Иногда бывает удобно иметь источник нулевых
значений. Запись в / dev / z er o эквивалентна записи в / dev / nu l l , однако при
чтении из / dev / z ero вы получаете нули в любом нужном количестве - от од­
ного символа до целого диска.
На уровне драйвера код, обслуживающий устройства / dev / r am, / d ev / mem,
/ dev / kmem и / dev / boot , идентичен. Единственное различие между этими че­
тырьмя устройствами состоит в том, что они работают с разными областями па­
мяти, задаваемыми массивами ram_or i g i n и ram_l imi t , каждый из которых
индексируется вспомогательным номером устройства. Файловая система управ­
ляет устройствами на более высоком уровне. Она различает символьные и блочные
устройства, а следовательно, способна монтировать / dev / ram и / dev / bo o t ,
читать и записывать потоки данных (хотя чтение потока и з / dev / nu l l всегда
возвращает символ конца файла).

3 . 6 . З . Реал изация драй вера


виртуал ьного диска в M I N IX 3
Как и у других дисковых драйверов, главный цикл драйвера виртуального диска
находится в файле dr i ver . с. Поддержка специальных функций виртуального
диска обеспечивается файлом memory . с (строка 1 0800). При компиляции драй­
вера памяти копия объектного файла dr i v e r s / l ibdr i v e r / dr i ver . o, по­
лучаемого из файла dr i v e r s / l ibdr iv e r / driver . c , компонуется с объект­
ным файлом dr iver s / memory / memory . o - результатом компиляции файла
dr i ver s / memory / memory . с .
Компиляции основного цикла следует уделить особое внимание. Объявление
структуры dr iver в файле driver . h (строки 1 0829 - 1 0845) лишь описывает,
но не создает ее. Объявление m_dt ab (строки 1 1 645- 1 1 660) создает экземпляр
структуры dr i ver и заполняет его указателями на функции. Некоторые из этих
функций являются общими и компилируются из файла dr i ver . с (например,
все функции nop), другие компилируются из файла memory . с (например, m_
do_op en). Обратите внимание на то, что для драйвера памяти семь подпрограмм
делают мало или вообще ничего, а последние два указателя являются нулевыми
(NULL ). Это означает, что соответствующие функции никогда не вызываются
(смысла нет даже в do_nop). Таким образом, нетрудно видеть, что функциони­
рование виртуального диска не отличается замысловатостью.
Работа с памятью не требует большого количества структур данных. В массиве
m_g eom [ NR_D EVS ] (строка 1 1 62 7 ) хранятся базовые адреса и размеры всех
шести специальных устройств. Для них используется 64-разрядный беззнаковый
тип данных, что на сегодняшний день исключает возможность нехватки объема
виртуального диска. В следующей строке определена интересная структура
31 0 Глава 3 . Ввод- вы вод

rn_s e g [ NR_DEVS ] , недоступная другим драйверам. Внешне она представляет со­


бой массив целых чисел, однако эти числа используются как индексы, позволяю­
щие отыскивать дескрипторы сегментов. Отличие драйвера памяти от других
процессов пользовательского пространства заключается в возможности доступа
к памяти, находящейся за пределами сегментов данных, кода и стека. Этот мас­
сив содержит информацию, необходимую для доступа к выделенным дополни­
тельным областям памяти. Переменная же rn_dev i c e хранит индекс текущего
активного вспомогательного устройства.
Чтобы воспользоваться устройством / dev / rarn как корневым, драйверу па­
мяти необходимо инициализироваться на очень ранней стадии запуска MINIX 3.
Структуры k i n f o и rna chine, определения которых следуют далее, хранят дан­
ные, полученные из ядра при запуске. Эти данные необходимы для инициализа­
ции драйвера памяти.
Одна структура данных, dev _z ero, определяется раньше исполняемого кода.
Она представляет собой массив размером 1 024 байт, данные которого предостав­
ляются при обслуживании обращения r ead к устройству / dev / z ero.
Главная процедура rna i n (строка 1 1 672) сначала вызывает функцию локальной
инициализации, а затем переходит в цикл, принимающий сообщения, вызываю­
щий соответствующие процедуры и отсылающий ответы. До завершения работы
драйвера управление никогда не передается назад в rna in.
Следующая функция, rn_narne, исключительно проста - она всего лишь возвра­
щает строку rnernory.
Выполняя операцию чтения или записи, главный цикл драйвера делает три вы­
зова: один для подготовки устройства, один, чтобы запланировать операцию вво­
да-вывода, и еще один, чтобы завершить операцию. В данном случае первому
вызову соответствует функция rn_prepare. Она убеждается, что было выбрано
правильное вспомогательное устройство, и возвращает адрес структуры, содер­
жащей базовый адрес и размер запрошенной области памяти. Второй вызов об­
служивает функция rn_t rans f e r (строка 1 1 706), которая и выполняет всю ра­
боту. Как мы видели в файле dr i ver . с, все вызовы чтения и записи данных
преобразуются в вызовы чтения и записи множественных смежных блоков дан­
ных. Если требуется считать только один блок данных, вызов все равно преобра­
зуется в запрос нескольких блоков данных, где параметр количества блоков ра­
вен 1. В результате драйверу передается всего два вида запросов - DEV_GATHER
(чтение одного или нескольких блоков) и DEV_S CATTER (запись одного или не­
скольких блоков). Таким образом, получив номер вспомогательного устройства,
rn_t rans f e r запускает цикл, число итераций которого равно числу затребован­
ных передач. Цикл содержит ветви по типу устройства.
Первая ветвь - / dev / nu l l . Для запроса DEV_GATHER происходит немедлен­
ный возврат из функции, а для запроса DEV_S CATTER - переход в конец ветви
(это позволяет вернуть число переданных байтов, как в любой операции записи,
хотя для устройства / dev / nu l l оно всегда равно нулю).
Для всех типов устройств, обращающихся к физической памяти, выполняется
аналогичная обработка. Сначала проверяется, не приводит ли переданное сме-
3 . 6 . В иртуал ьн ые диски 31 1

щение к выходу за пределы памяти, выделенной устройству, а затем совершается


вызов ядра, копирующий данные из памяти или в память устройства. Тем не ме­
нее для выполнения последнего действия применяются два разных фрагмента
кода. Устройства / dev / r arn, / dev / krnern и / dev / bo o t используют виртуаль­
ные адреса, поэтому требуется получить из массива rn_s eg сегмент адреса облас­
ти памяти, к которой осуществляется доступ, а затем совершить вызов sys_
vircopy ядра (строки 1 1 640- 1 1 652). Устройство / dev /rnern использует физи­
ческий адрес, обрабатываемый вызовом sy s_phy s copy.
Оставшаяся операция выполняет чтение и запись в устройство / dev / z ero. При
чтении данные извлекаются из упомянутого ранее массива dev_z ero. Вы спро­
сите, зачем копировать нули в буфер, если их можно просто генерировать? По­
скольку копирование данных выполняется вызовом ядра, такой метод потребует
неэффективного копирования отдельных байтов из драйвера памяти в систем­
ное задание либо создания кода, генерирующего для системного задания нуле­
вые значения. Последнее решение усложнило бы код ядра - то, чего мы стара­
лись избежать при создании MINIX 3.
Для завершения операции чтения или записи памяти не нужен третий шаг, по­
этому соответствующий элемент rn_dt ab указывает на функцию nop_f ini sh.
За открытие виртуального диска отвечает функция rn_do_op en (строка 1 1 80 1 ) .
Самые главные действия в ней выполняет вызов rn_p r epare, который проверя­
ет, что устройство указано корректно. Любопытная деталь: комментарий внутри
функции относится к коду, который был в ней в предыдущих версиях MINIX.
Раньше в этом месте был реализован некий программный трюк. Вызов, от­
крывающий устройство / dev / rnern или / dev / krnern, волшебным образом дает
совершившему его пользовательскому процессу возможность исполнять коман­
ды доступа к портам ввода-вывода. У процессоров Pentium есть четыре уровня
привилегий, при этом пользовательские программы работают на последнем уров­
не. Если процесс пытается исполнить команду, недопустимую на его уровне при­
вилегий, процессор генерирует исключение общей защиты. Способ преодоления
этого ограничения был признан безопасным, поскольку доступ к устройствам
памяти имел лишь пользователь с максимальными привилегиями. В любом случае
подобная сомнительная 4Возможность• исключена в MINIX 3 операционная
-

система предоставляет вызовы ядра, обеспечивающие доступ к портам ввода-вы­


вода посредством системного задания. Комментарий сохранен в коде, чтобы ука­
зать на необходимость возвращения описанного 4трюка• в MINIX 3 при переносе
на аппаратную платформу с вводом-выводом, отображаемым на память. Соот­
ветствующая функция, enaЬ l e_i op , сохранена в коде ядра и демонстрирует
реализацию 4трюка•, хотя сейчас в ней нет необходимости.
Следующая функция, rn_ini t (строка 1 1 817), вызывается только один раз, когда
в первый раз делается вызов rnern_t a s k. Эта подпрограмма использует множество
вызовов ядра, и на ее основе можно выполнить целое исследование на тему взаимо­
действия драйверов с ядром посредством служб системного задания в MINIX 3.
Сначала вызов sy s_g e t k i n f o получает из ядра копию структуры kinfo. Из
нее функция копирует базовый адрес и размер / dev / krnern в соответствующие
31 2 Глава 3 . Ввод-вывод

поля структуры m_g e om. Вызов sys_s eg c t l преобразует физический адрес


и размер устройства / dev / kmem в дескриптор сегмента, чтобы память ядра вос­
принималась как виртуальная. Если образ загрузочного устройства включен в за­
грузочный образ системы, поле базового адреса / dev / bo o t будет ненулевым.
В этом случае информация для доступа к области памяти данного устройства ус­
танавливается так же, как и для / dev / kmem. Затем массив, используемый для
предоставления данных при доступе к устройству / dev / z ero, явно заполняется
нулями. Возможно, это является излишним, так как большинство компиляторов С
инициализируют создаваемые статические переменные нулевыми значениями.
Наконец, функция m_i n i t с помощью системного вызова sys_machine полу­
чает из ядра еще одну структуру, machine, содержащую различные атрибуты
аппаратного обеспечения. В данном случае необходимо узнать, поддерживает ли
процессор защищенный режим. В зависимости от этой информации и режима
работы MINIX 3 (8088 или 80386) размер устройства / dev / mem устанавлива­
ется равным 1 Мбайт (4 Гбайт 1 ) . Эти размеры являются максимальными,
-

поддерживаемыми MINIX 3, и не зависят от объема оперативной памяти, уста­


новленной на компьютере. Заданию подлежит только размер устройства; пред­
полагается, что компилятор корректно установит базовый адрес равным нулю.
Поскольку доступ к / dev / mem осуществляется как к физическому, а не вирту­
альному устройству, нет необходимости совершать системный вызов sys_
s eg c t l для задания дескриптора сегмента.
Перед тем как завершить рассмотрение функции m_i n i t , следует упомянуть
еще один неочевидный вызов ядра. Многие действия, выполняемые при ини­
циализации драйвера памяти, необходимы для корректного функционирования
MINIX 3. По этой причине выполняется несколько тестов, и в случае неудачного
исхода вызывается подпрограмма p an i c в данном случае это библиотечная
-

функция, инициирующая вызов ядра sy s_ex i t . Ядро, менеджер процессов (как


мы увидим) и файловая система используют собственные подпрограммы p an i c .
Драйверам устройств и прочим небольшим системным компонентам предостав­
ляется библиотечная функция с таким именем.
Интересная деталь: функция m_i n i t не инициализирует важнейшее устрой­
ство памяти / dev / ram. Этим занимается следующая функция, m_i oc t l (стро­
ка 1 1 863). Фактически, для виртуального диска определена одна операция
i o c t l MI OCRAМS I Z E, используемая файловой системой для установки вирту­
-

ального диска. Большая часть работы делается без служб ядра. Вызов a l l ocmem
(строка 1 1 1 87) является системным, но он не обращен к ядру, а обрабатывается
менеджером процессов, поддерживающим всю информацию, необходимую для
поиска доступных областей памяти. Тем не менее в конце без вызова ядра все
же не обойтись. Вызов sys_s egc t l ( строка 1 1 894) преобразует физический
адрес и размер, возвращаемый вызовом a l l o cmem, в сегментную информацию,
необходимую для будущего доступа.
Последняя функция в файле memo ry . с это m_geomet ry. Для виртуального
-

диска понятия цилиндра, дорожки и сектора не имеют смысла, но если система


3 .7. Реал ьн ые диски 31 3

запросит эту информацию, он должен притвориться, что у него есть 64 дорожки


и 32 сектора в каждой дорожке. Исходя из этого, данная функция подсчитывает
число «цилиндров� виртуального диска.

3 . 7 . Реал ьные диски


Все современные компьютеры, за исключением встроенных, оснащены жест­
кими дисками. По этой причине мы займемся их изучением. Мы начнем с аппа­
ратной части, затем скажем несколько общих слов о дисковом програм�ном
обеспечении, и, наконец, перейдем к рассмотрению вопросов управления диска­
ми в операционной системе MINIX 3.

3 . 7 . 1 . Ап паратное обеспечение диска


Все реальные диски организованы в цилиндры, каждый из которых содержит
столько дорожек, сколько вертикально установленных головок есть у устройст­
ва. Дорожки делятся на секторы, их количество обычно варьируется от 8 до 32
у гибких дисков и до нескольких сотен у жестких дисков. В простейших случаях
число секторов на всех дорожках одинаково. Все секторы содержат одинаковое
количество байтов, хотя нетрудно понять, что физическая длина секторов растет
с удалением от центра диска. Тем не менее время чтения и записи любого сектора
неизменно. Плотность данных возрастает от краев диска к центру, что вынужда­
ет изменять ток выборки, подаваемый на головки при чтении близких к центру
дорожек. Об этом заботится аппаратная часть дискового контроллера. Ни поль­
зователь, ни разработчик операционной системы не имеют к этому отношения.
Увеличение плотности данных по мере приближения дорожек к центру накла­
дывает ограничения на емкость диска и делает системы более сложными. Была
попытка создать гибкие диски, скорость вращения которых растет при переме­
щении считывающих головок на дорожки, удаленные от центра. Это позволило
бы разместить на таких дорожках больше секторов и увеличить объем диска. По­
добные диски не поддерживаются ни одной из платформ, для которых предна­
значена система M INIX 3. Современные жесткие диски большого объема также
имеют на внешних дорожках больше секторов, чем на внутренних. Эти диски от­
носятся к типу IDE (Integrated Drive Electronics - встроенный интерфейс диско­
вых накопителей). Они оснащены встроенной электроникой, ведущей с диском
сложную работу и скрывающую ее детали. С точки зрения операционной систе­
мы IDЕ-диски имеют простую физическую структуру с одинаковым числом сек­
торов на каждой дорожке.
Диск и электронные компоненты важны не меньше, чем механические. Главным
элементом дискового контроллера является специальная интегральная схема,
фактически представляющая собой микрокомпьютер. Раньше она располагалась
на плате, вставляемой в объединительную панель компьютера, однако в совре­
менных компьютерах дисковый контроллер размещается на материнской плате.
31 4 Глава 3 . Ввод- вывод

Контроллер современного жесткого диска может оказаться проще, чем контрол­


лер гибких дисков, если жесткий диск оснащен мощным встроенным электрон­
ным контроллером.
Важным свойством дисков, влияющим на дисковый драйвер, является возмож­
ность одновременного поиска на двух или более дисках, так называемого поиска
с перекрытием. В то время как контроллер и программное обеспечение ожидают
окончания операции поиска на одном устройстве, контроллер может иницииро­
вать поиск на другом устройстве. Многие контроллеры жестких дисков также
умеют совмещать операцию чтения или записи на одном диске с поиском на дру­
гом или даже нескольких дисках. Однако контроллеры гибких дисков не могут
одновременно читать и писать на двух дисководах. (Чтение или запись требуют
от контроллера перемещения битов с максимальной скоростью, на которую он
рассчитан, поэтому операция чтения или записи отнимает большую часть его
вычислительных возможностей. ) В случае I D Е-дисков с их встроенными мик­
роконтроллерами ситуация радикально меняется, поэтому в одной системе спо­
собны одновременно работать несколько жестких дисков, по крайней мере, пе­
реносить данные между диском и буфером контроллера. Вместе с тем, между
контроллером и оперативной памятью в каждый момент времени происходит
только одна операция по переносу данных. Способность параллельного выпол­
нения двух или более дисковых операций может существенно сократить среднее
время доступа.
Глядя на спецификации современных жестких дисков, следует помнить, что ука­
занная геометрия, используемая программным обеспечением драйвера, может
отличаться от физического формата. Если вы прочтете рекомендуемые парамет­
ры установки для жесткого диска большого объема, то, скорее всего, обнаружите
следующее: 16 383 цилиндра, 16 головок и 63 сектора на одну дорожку (при лю­
бом объеме диска). Эти цифры - для диска размером 8 Гбайт, однако они те же
для любого диска большего объема. Разработчики оригинальных машин IBM РС
выделили в ПЗУ B I O S только 6 бит под счетчик секторов, 4 бита под головку
и 14 бит под цилиндр. При размере сектора 5 1 2 байт получаем объем 8 Гбайт.
По этой причине установка жесткого диска на очень старый компьютер может
обернуться тем, что доступными окажутся лишь 8 Гбайт при значительно боль­
шем объеме диска. Обычный способ преодоления этого ограничения - логиче­
ская адресация блоков, согласно которой секторы диска попросту нумеруются,
начиная с нуля. Нумерация никак не связана с физическими особенностями диска.
В контексте современных жестких дисков описанная геометрия, на самом деле, -
не более чем фантастика. Поверхность современного диска разбита на 20 или бо­
лее зон. Чем ближе зоны расположены к центру, тем меньше в них секторов на
одной дорожке. По этой причине физическая длина сектора остается приблизи­
тельно одинаковой вне зависимости от его расположения на диске. Это повыша­
ет эффективность использования дисковой поверхности. Внутренняя адресация
контроллера диска включает четыре компонента: зону, цилиндр, головку и сек­
тор. Тем не менее все это скрыто от пользователя, и подробности редко можно
найти в публикуемых спецификациях. Вывод такой: адресация по цилиндру,
3 .7 . Реальные диски 31 5

головке и сектору ( Cylinder- Head-Sector, CHS) имеет смысл лишь в случае, если
вы имеете дело с устаревшим компьютером, не поддерживающим логическую
адресацию блоков. Кроме того, нет смысла приобретать новый диск размером
400 Гбайт для компьютера РС-ХТ, купленного в 1 983 году: он не позволит вам
использовать более 8 Гбайт дискового пространства.
Здесь самое время рассказать о путанице, касающейся спецификаций дисков. Спе­
циалисты в области вычислительной техники привыкли измерять емкость диска
в степенях с основанием 2. Так, килобайтом ( Кбайт) считается 2 1 0 = 1 024 байт,
мегабайтом ( Мбайт) - 2 20 = 1 024 2 байт и т. д. По такой логике 1 Гбайт равен
1 024 3 , или 2 3 0 байт. Однако производители дисков под гигабайтом понимают
1 09 байт, что означает заметное увеличение объема их продукции, но только на
бумаге. Таким образом, упомянутый ранее предел в 8 Гбайт на языке продавца
компьютерного магазина превращается в 8,4 Гбайт. В последнее время намети­
лась тенденция к использованию термина «гибибайт� ( GiВibyte ), обозначающе­
го 2 30 байт. Однако в этой книге авторы пользуются принятыми толкованиями
мега- и гигабайта, отдавая дань традиции и протестуя против искажения терми­
нов в угоду рекламе.

3 . 7 . 2 . RAI D
Несмотря на то что современные диски значительно быстрее своих предшест­
венников, производительность процессоров растет гораздо быстрее, чем дисков.
С течением времени специалистам неоднократно приходила в голову идея о том,
что параллельный дисковый ввод-вывод способен изменить ситуацию. Так по­
явился новый класс устройств ввода-вывода, обозначаемый аббревиатурой RAID
( Redundant Array of Independent Disks - избыточный массив независимых дис­
ков ). Разработчики RAID (университет Беркли) изначально понимали эту аб­
бревиатуру иначе: Redundant Array of Inexpensive Disks ( избыточный массив
дешевых дисков), позиционируя ее как противоположность другой архитектуре,
SLED ( Single Expensive Large Disk - единственный дорогой большой диск). Тем
не менее, когда технология RAID стала пользоваться коммерческой популярно­
стью, производители изменили расшифровку аббревиатуры, поскольку дорого
продавать продукт, в названии которого стояло слово «дешевый�, было затруд­
нительно. Первоначальная идея RAID состояла в установке рядом с компьюте­
ром (как правило, большим сервером) коробки, наполненной дисками, замене
дискового контроллера RАI D-контроллером, копировании данных в RАID-мас­
сив и продолжении обычной работы.
Независимые диски можно совместно использовать целым рядом способов. Из-за
ограничений объема книги мы не можем исчерпывающе описать их все, к тому
же MINIX 3 пока не поддерживает RАID-массивы. Тем не менее при знакомстве
с операционными системами нельзя обойтись без упоминания хотя бы некото­
рых из них. RAI D позволяет одновременно повысить скорость дискового досту­
па и безопасность данных.
Рассмотрим, к примеру, простейший RАID-массив, состоящий из двух дисков.
Когда требуется записать на «диск� несколько секторов данных, RАID-контроллер
31 6 Глава З. Ввод- вывод

посылает секторы О, 2, 4 и т. д. на диск 1 , а секторы 1 , 3, 5 и т. д. - на диск 2. Кон­


троллер разделяет данные и ведет независимую запись на оба диска одновременно,
тем самым удваивая скорость записи. При чтении обращение к обоим дискам
осуществляется также одновременно, но теперь контроллер собирает данные в над­
лежащем порядке, а с точки зрения остальной системы чтение просто происходит
с удвоенной скоростью. Этот метод называется расщеплением данных (stripping).
Данный пример демонстрирует простейший RАID-массив уровня О. На практи­
ке используются не два, а четыре или более устройств . RАI D -массив полезен
в случаях, когда данные в основном считываются или записываются большими
блоками. Очевидно, что при запросах на чтение одного сектора никакой выгоды
от использования RАID-массива нет.
Предыдущий пример показывает, как можно увеличить скорость доступа с по­
мощью нескольких дисков. А как же обстоит дело с надежностью? RAID уровня 1
работает так же, как и RAI D уровня О, за исключением дублирования данных.
Можно снова прибегнуть к простейшему массиву из двух дисков, но записывать
на них одни и те же данные. В этом случае прироста скорости не будет, однако
мы получим 1 00-процентную избыточность. Если при чтении произойдет ошиб­
ка, необходимости в повторной попытке не возникнет, если данные безошибочно
считаны со второго диска. Все, что нужно контроллеру, - убедиться в коррект­
ности данных, передаваемых системе. Тем не менее отказываться от повторных
попыток при неудачной записи, вероятно, не следует. Если ошибки происхо­
дят настолько часто, что отказ от повторного чтения значительно увеличивает
скорость считывания, можно уверенно говорить о неминуемости полного краха.
Как правило, дисководы RАI D-массивов допускают �горячую� замену, то есть
замену, не требующую отключения питания системы.
Более сложные дисковые массивы позволяют увеличивать скорость и надеж­
ность одновременно. Рассмотрим, к примеру, массив, состоящий из семи дисков.
Байты могут быть разбиты на 4 группы по 2 бита в каждой. Каждая группа за­
писывается на соответствующий диск, а на три оставшихся диска помещается
3-разрядный код коррекции: ошибок. Если один из дисков выйдет из строя и по­
требует �горячей� замены, отсутствующий диск будет эквивалентен одному ис­
каженному биту, поэтому система сможет продолжить работу в процессе уста­
новки другого диска. Ценой семи дисков вы обеспечите бесперебойную работу
системы с четырехкратным увеличением производительности:.

3 . 7 . 3 . П рограм м ное обеспечение


жестких дисков
В этом разделе мы изучим некоторые общие вопросы работы драйверов жестких
дисков. Прежде всего, выясним, сколько времени требуется для считывания дис­
кового блока. Это время складывается из трех слагаемых.
1 . Время поиска (перемещение головки на нужный цилиндр).
2. Задержка вращения (время, через которое нужный сектор пройдет под головкой).
3. Время передачи данных.
3 . 7. Реальные диски 31 7

Для большинства дисковых накопителей наибольший вклад в задержку имеет


первый показатель, поэтому снижение среднего времени поиска принципиально
важно для роста производительности системы.
Работа дисковых накопителей чревата ошибками. В силу этого в каждый сек­
тор диска всегда записывается та или иная проверочная информация, в виде
контрольной суммы или кода для циклического контроля избыточности ( Cyclic
Redundancy Check, CRC). Даже адреса секторов, которые записываются на диск
при форматировании, содержат проверочные данные. Контроллер гибкого диска,
обнаружив ошибку, сообщает о ней системе, которая должна решить, как посту­
пить. Контроллеры жестких дисков часто берут на себя значительную часть дей­
ствий по обработке ошибки.
Для жестких дисков последовательное чтение секторов в пределах одной дорожки
происходит очень быстро. Поэтому для повышения эффективности работы системы
имеет смысл считьmать больше секторов, чем запрошено, и кэшировать их в памяти.

Ал г оритм ы планирования перемещения головки


Если драйвер диска принимает и выполняет запросы по одному в порядке их по­
лучения, то есть по принципу FCFS ( Fiгst Соте, Fiгst Served - первым пришел,
первым обслужен), тогда мало что можно сделать для оптимизации времени по­
иска. Однако при высокой загрузке диска допустимо применение другой страте­
гии. В этом случае высока вероятность поступления новых запросов от других
процессов во время перемещения головки для обработки предыдущего запроса.
Многие дисковые драйверы содержат таблицу, индексированную по номерам
цилиндров, в которой в единый связанный список собираются все поступившие
и ждущие обработки обращения к цилиндрам.
С помощью подобной структуры данных можно создать более совершенный алго­
ритм планирования, чем простое обслуживание в очередности поступления запро­
сов. Рассмотрим, например, диск с 40 цилиндрами. Поступает запрос на чтение
блока с цилиндра 1 1 . Во время перемещения головки на этот цилиндр происхо­
дят запросы на чтение блоков с цилиндров 1 , 36, 1 6, 34, 9 и 1 2. Новые запросы
помещаются в таблицу, составленную из отдельных списков для каждого цилин­
дра. На рис. 3. 14 поступившие запросы помечены крестиками.

Начальная Необработанные
позиция запросы

1 lxl 1 1 1 1 1 1 1x
о 5
d1xbl1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 lxl lxl 1 1
10 15 20 25 30 35 Цилиндр
1

Последовательность
перемещения головки

Рис. 3. 1 4 . Алгоритм планирования SSF


31 8 Гл ава 3 . В в од- в ы вод

Выполнив первый запрос (обращение к цилиндру 1 1 ), драйвер диска должен вы­


брать на обслуживание следующий запрос. Если обслуживать запросы в порядке
поступления, то драйвер должен переместить головку на цилиндр 1 , затем на
цилиндр 36 и т. д. В результате выполнение этого алгоритма потребует переме­
щения блока головок на 10, 35, 20, 18, 25 и 3 цилиндра, что в сумме составит
1 1 1 цилиндров.
Время перемещения головки можно уменьшить, выбирая каждый раз ближай­
ший цилиндр. При той же серии запросов (см. рис. 3.14), последовательность их
обработки выглядит как ломаная кривая, представленная в нижней части рисунка.
При такой последовательности выполнения запросов потребуются перемещения
блока головок на 1, 3, 7, 1 5, 33 и 2 цилиндра, что суммарно равно 61 цилиндр.
Применение данного алгоритма, названного SSF ( Shortest Seek First - бли­
жайший запрос обслуживается первым), минимизирует суммарные перемещения
головок почти вдвое.
К сожалению, у данного алгоритма есть недостатки. Предположим, во время
обработки запросов, показанных на рис. 3 . 1 4 , поступили новые запросы. На­
пример, после обработки обращения к цилиндру 16 приходит новый запрос
к цилиндру 8. Этот запрос будет иметь приоритет над обращением к цилиндру 1.
Затем, если поступит запрос к цилиндру 13, головка опять начнет перемещаться
к цилиндру 13 для обработки нового запроса, а запрос к цилиндру 1 останется
необработанным. При сильной загрузке диска головка диска будет большую часть
времени находиться где-то в районе средних цилиндров, а запросам к крайним
цилиндрам диска придется ждать, пока нагрузка снизится и обращений к середи­
не диска станет меньше. В результате качество обслуживания запросов к цилинд­
рам, удаленным от срединной части, может оказаться низким. То есть в конфликт
вступают принцип минимизации времени отклика и принцип справедливости.
В высотных зданиях также приходится иметь дело с данной проблемой планиро­
вания обслуживания запросов лифта. Вызовы лифта постоянно поступают с раз­
ных этажей. Компьютер, управляющий лифтом, должен следить за последова­
тельностью поступления запросов и удовлетворять их либо в порядке подачи
заявок, либо обслуживая первым ближайший запрос.
В большинстве лифтов применяют различные алгоритмы, пытаясь примирить
конфликтующие цели эффективности и справедливости. Обычно лифт продол­
жает двигаться в одном направлении до тех пор, пока на этом направлении более
не остается запросов. Затем лифт меняет направление движения. Этот алгоритм,
называющийся элеваторнъш, требует от программного обеспечения отслежива­
ния всего одного бита, хранящего информацию о текущем направлении движе­
ния, вверх или вниз. Выполнив очередной запрос, диск или лифт проверяет зна­
чение бита. Если он требует движения вверх, кабина лифта (или блок головок)
перемещается в соответствующую сторону к следующему запросу. Если желаю­
щих подняться больше нет, состояние бита инвертируется.
Рисунок 3. 1 5 иллюстрирует выполнение элеваторного алгоритма с теми же се­
мью запросами, показанными в качестве примера на рис. 3.14. Предполагается,
что изначально бит направления движения указывал вверх. При этом цилиндры
3.7 . Реал ь ные диски 319

обслуживаются в порядке 12, 1 6, 34, 36, 9 и 1 , что соответствует перемещению


блока головок на 1 , 4, 18, 2, 27 и 8 цилиндров и в сумме составляет 60 цилинд­
ров. В данном случае элеваторный алгоритм оказался даже чуть лучше, чем алго­
ритм SSF, однако на практике он обычно работает несколько хуже. Достоинство
элеваторного алгоритма состоит в том, что при любом заданном наборе запросов
верхняя граница необходимых перемещений блока головок для выполнения всех
запросов фиксирована и ограничена удвоенным числом цилиндров.

Начальная
позиция

1
о
lxl 1 1 1
5
1 1 1 lx �lxl 1 1 lxl 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 lxl lxl 1 1 1
10 15 20 25 30 35 Цилиндр
Последовательность
'--.... перемещения головки
1

----·--
Рис. З. 1 5 . Элеваторный алгоритм планирования обращений к диску
Незначительная модификация этого алгоритма с меньшим разбросом времени
отклика состоит в том, чтобы сканировать цилиндры всегда в одном направле­
нии [ 1 18]. После обслуживания обращения к цилиндру с самым большим номе­
ром блок головок перемещается к самому нижнему запрашиваемому цилиндру
и далее продолжает движение вверх. Таким образом, самый нижний цилиндр как
бы считается расположенным выше самого верхнего.
Некоторые контроллеры дисков предоставляют программному обеспечению спо­
соб узнавать номер текущего сектора под головкой. Такие контроллеры позволя­
ют использовать дополнительный метод оптимизации. Если есть два или более
запросов к одному и тому же цилиндру, драйвер может выбрать из них тот, сек­
тор которого пройдет под головкой первым. Обратите внимание, что при нали­
чии нескольких головок у диска последовательные запросы могут обращаться
к различным дорожкам на одном цилиндре. Контроллер может практически
мгновенно переключаться с головки на головку, так как такое переключение не
требует ни перемещения блока головок, ни ожидания поворота диска.
По скорости передачи данных современные жесткие диски настолько превосходят
дисководы для дискет, что требуется автоматическое кэширование. Как правило,
при любом запросе на считывание сектора считываются все секторы до конца
текущей дорожки (включая требуемый) в зависимости от доступного свободного
места в кэше. В настоящее время размер кэша зачастую составляет 8 Мбайт и более.
При наличии нескольких дисководов необходимо поддерживать отдельные оче­
реди активных запросов для каждого из них. При простое диска следует выпол­
нить поиск и перевести его головку на цилиндр, который будет использоваться
320 Глава 3. Ввод- вывод

для следующего считывания (при условии, что контроллер поддерживает пере­


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

Обра ботка ошибок


Драйверу виртуального диска не нужно заботиться об оптимизации поиска сек­
тора, так как в любой момент можно прочитать или записать любой блок без ка­
ких бы то ни было механических движений. Обработка ошибок - еще одна об­
ласть, в которой виртуальный диск намного проще. Виртуальный диск работает
всегда, в то время как реальный диск нет. Реальные дисковые накопители под­
вержены самым разнообразным ошибкам. Вот самые распространенные из них:
+ программные ошибки (например, запрос несуществующего сектора);
+ вр е менная ошибка контрольной суммы (например, вызванная пылью, попав­
шей на головку);
+ постоянная ошибка контрольной суммы (физическое повреждение блока);
+ ошибка поиска (допустим, головка была отправлена на цилиндр 6, но оказа-
лась на цилиндре 7);
+ ошибка контроллера (например, контроллер отказался принимать команду).
Обрабатывать все эти ошибки наилучшим образом - задача драйвера диска.
Программные ошибки возникают, когда драйвер передает контроллеру команду
на переход к несуществующему цилиндру, на чтение несуществующего сектора,
использование несуществующей головки или на передачу данных в несущест­
вующую область памяти. Большинство контроллеров проверяют передаваемые
им параметры и уведомляют о неполадках. Теоретически таких ошибок возни­
кать не должно, но что делать, если контроллер все же сообщит об этом? Для
�доморощенной� системы лучше всего было бы вывести на экран текст �свяжи­
тесь с разработчиком� , чтобы ошибку можно было найти или исправить. Для
коммерческой системы, работающей на многих тысячах компьютеров по всему
миру, такое поведение менее привлекательно. Вероятно, лучшее, что можно сде­
лать в данном случае, это завершить текущий запрос с кодом ошибки и надеять­
ся, что она не будет случаться слишком часто.
Вр е менные ошибки контрольной суммы вызываются пылинками, которые оста­
ются в воздухе между головкой и поверхностью диска. С ними можно бороться,
повторив несколько раз операцию. Если же повторным чтением ошибка не уст­
раняется, блок помечается как поврежденный (bad Ыосk). Такой блок в дальней­
шем не используется.
Один из способов избежать работы с поврежденными блоками требует специаль­
ной программы, которая получает на входе список поврежденных блоков и по
3 .7 . Реал ьные диски 32 1

этим данным создает файл, состоящий целиком из таких блоков. Они помечают­
ся как занятые, и в результате ни одна программа не будет пытаться обращаться
к ним. В результате ни одна из программ не попытается прочитать файл сбой­
ных блоков, и ошибка не возникает.
Не читать поврежденные блоки - это проще сказать, чем сделать. Часто резерв­
ные копии дисков создаются путем подорожечного копирования содержимого
диска на ленту или другой диск. Если следовать этой процедуре, избежать повре­
жденных блоков невозможно. Пофайловое резервное копирование медленнее, но
может помочь решить проблему, если программа, зная имя файла поврежденных
блоков, не пытается его копировать.
Другая проблема, не решаемая при помощи файла поврежденных блоков, - это
нарушение системной структуры, которая должна занимать фиксированное поло­
жение на диске. Практически у любой файловой системы есть структура, поло­
жение которой должно быть одним и тем же с целью облегчения ее поиска. Если
файловая система разбита на разделы, то можно заново разбить ее и попробовать
обойти сбойный блок, но если повреждены первые несколько секторов диске­
ты или жесткого диска, накопитель становится непригодным к использованию.
4 Умные� контроллеры резервируют несколько дорожек, делая их недоступными
пользовательским программам. При форматировании диска контроллер опреде­
ляет, какие из секторов содержат ошибки, и автоматически замещает их одним
из запасных. Таблица, отображающая поврежденные блоки на запасные, хранит­
ся во внутренней памяти контроллера и на диске. Подмена происходит незамет­
но для драйвера, за исключением того, что не исключена потеря эффективности
тщательно отработанного элеваторного алгоритма, если контроллер будет 4Тайно�
использовать цилиндр 800 вместо запрошенного цилиндра 3. Технология произ­
водства магнитных поверхностей исключительно точна, но все равно не идеаль­
на. Поэтому механизм скрытия таких дефектов от пользователя имеет большое
значение. Для жестких дисков контроллер отслеживает появление новых сбой­
ных блоков и, если ошибка неустранима, заменяет их запасными блоками. Рабо­
тая с таким диском, драйвер практически никогда не будет видеть плохие блоки.
Дефектные секторы не являются единственным источником ошибок. Также воз­
никают ошибки поиска цилиндра, вызванные механическими проблемами блока
головок. Контроллер следит за положением блока головок. При установке голов­
ки на заданный цилиндр он выдает серию импульсов двигателю блока головок,
по одному импульсу на цилиндр. Когда блок головок устанавливается в требуе­
мое положение, контроллер считывает истинное значение цилиндра из заголовка
первого попавшегося сектора. Если блок головок оказывается не на той дорожке,
на которой нужно, возникает ошибка поиска и предпринимается некоторое кор­
ректирующее действие.
Практически все контроллеры жестких дисков автоматически исправляют ошиб­
ки поиска, но большинство контроллеров гибких дисков (включая установленные
на компьютерах IBM РС) просто выставляют бит ошибки и оставляют все осталь­
ное драйверу. Драйвер обрабатывает ошибку, выдавая команду r e c a l ibra t e .
При этом блок головок отодвигается н а самую внешнюю дорожку диска д о упора.
322 Глава 3 . Ввод-вывод

Это положение принимается контроллером за нулевую дорожку, таким образом,


контроллер снова начинает понимать, где находится блок головок. Обычно это
решает проблему. Если же нет, то либо нужно сменить диск, либо починить дис­
ковод.
Как мы видели, контроллер в действительности представляет собой небольшой
специализированный компьютер с полным комплектом программного обеспе­
чения, переменных, буферов и, по всей видимости, ошибок. Иногда необычное
стечение обстоятельств, например приход прерывания с одного устройства, в то
время как другое устройство выполняет команду r e c a l ibra t e , может разбу­
дить �спящую� до тех пор ошибку, в результате контроллер войдет в бесконеч­
ный цикл или забудет, что он делал. Разработчики контроллеров обычно гото­
вятся к худшему и предоставляют на всякий случай специальный контакт на
микросхеме, обращение к которому вызывает сброс контроллера. Если пика -
кие другие меры не помогают, драйвер может установить бит, отвечающий за
подачу сигнала на такой контакт, и сбросить контроллер. Если и это не реши­
ло вопроса, все, что драйверу остается сделать, - вывести сообщение об ошибке
и сдаться.

Кэширование дорожек
Время, требующееся на переход к новому цилиндру, обычно много больше, чем
время поворота диска, и всегда больше времени чтения или записи сектора. Дру­
гими словами, если драйвер решил перевести куда-либо головку, многое зависит
от того, будет ли читаться только один сектор или вся дорожка. Эффект в осо­
бенности заметен, если контроллер позволяет определить, над каким сектором
находится дорожка, чтобы драйвер мог отдать ему команду на чтение следующе­
го сектора, тем самым считывая дорожку целиком за один оборот диска. ( Обыч­
но в среднем на чтение отдельного сектора требуется половина оборота диска
плюс время на чтение одного сектора.)
Некоторые драйверы поддерживают специальный кэш дорожки, невидимый для
программ. Если запрошенный у контроллера сектор находится в этом кэше, об­
мениваться данными с диском не нужно. Недостаток такого подхода (помимо
усложнения драйвера и необходимости выделения памяти для буфера) состоит
в том, что передача данных пользовательской программе из кэша должна про­
изводиться процессором в программном цикле, а не путем прямого доступа к па­
мяти (DMA).
По данной причине многие современные контроллеры поддерживают кэш до­
рожки самостоятельно, в своей внутренней памяти. В этом случае кэширование
происходит прозрачно для драйвера и передачу данных можно производить пу­
тем прямого доступа к памяти. Если контроллер поддерживает подобную функ­
цию, не имеет большого смысла дублировать ее в драйвере. Заметьте, что обеспе­
чить чтение одной дорожки целиком могут как контроллер, так и драйвер диска,
но не пользовательская программа, так как для пользовательских программ диск
представляется линейной последовательностью блоков без разбиения на дорожки
и цилиндры. Точная геометрия диска известна лишь его контроллеру.
3. 7 . Реал ь ные диски 323

3 . 7 4 Драйвер жестких дисков в M I N IX 3


. .

Драйвер жесткого диска - это первая рассмотренная нами составная часть


MINIX 3, которая работает с широким диапазоном оборудования различных ти­
пов. Поэтому, прежде чем обсуждать детали драйвера, мы познакомимся с неко­
торыми проблемами, порождаемые различиями в аппаратном обеспечении.
Под обозначением •РС• в действительности скрывается семейство различных
компьютеров. Члены семейства имеют не только разные процессоры - весьма
существенные расхождения есть также в базовом аппаратном обеспечении. Опе­
рационная система MINIX 3 разрабатывалась для компьютеров, оснащенных
процессорами Pentiurn, однако даже между этими компьютерами имеются раз ­
личия. Так, к примеру, первые системы Pentiurn использовали 1 6-разрядную
шину АТ, изначально разработанную для процессоров 80286. Преимущество
шины АТ заключалось в том, что она поддерживала более старые 8-разрядные
периферийные устройства. Позднее для периферийных устройств появилась
шина PCI, однако разъем шины АТ все еще присутствовал в компьютерах. В но­
вых системах от поддержки АТ отказались, сохранив только PCI. Тем не менее
можно предположить, что пользователи компьютеров •определенного возраста•
будут устанавливать MINIX 3 вместе с периферийными устройствами различ­
ных разрядностей - 8, 1 6 и 32.
Для каждой шины имеется отдельное семейство адаптеров ввода -вывода. В уста­
ревших системах адаптеры ввода-вывода представляют собой отдельные печатные
платы, вставляемые в материнскую плату компьютера. В более новых компьюте­
рах многие стандартные адаптеры, особенно дисковые контроллеры, интегриро­
ваны в набор микросхем материнской платы. Для программиста это не является
проблемой, поскольку интегрированные адаптеры, как правило, обладают про­
граммным интерфейсом, идентичным интерфейсу съемных устройств. Кроме то­
го, чаще всего имеется возможность отключать интегрированные контроллеры,
устанавливая взамен более совершенные дополнительные устройства, например
SСSI-контроллеры. Чтобы воспользоваться подобной гибкостью, операционной
системе необходимо поддерживать более одного типа адаптеров.
В семействе I B M РС, как и в большинстве других компьютерных систем, каждая
шина сопровождается программно-аппаратной поддержкой в виде постоянной
памяти ( Read Only Mernory, R O M ) с базовой системой ввода-вывода ( Basic
Input/Output Systern, BIOS). Так преодолевается разрыв между операционной
системой и спецификой оборудования. Некоторые периферийные устройства
даже оснащены микросхемой памяти, содержащей расширение BIOS. Эта мик­
росхема расположена на плате устройства. Сложность, с которой приходится
сталкиваться разработчику операционной системы, заключается в том, что мик­
росхема BIOS в IВМ-совместимых компьютерах (разумеется, в ранних) разраба­
тывалась в расчете на MS- D O S - операционную систему, не поддерживающую
многозадачность и работающую в реальном режиме с разрядностью 16 бит. Имен­
но таковы характеристики •общего знаменателя• различных режимов, поддер­
живаемых процессорами семейства 80х86.
324 Глава З . Ввод-вывод

Таким образом, перед создателем новой операционной системы для I B M РС


встает несколько вопросов. Первый из них: использовать ли для поддержки обо­
рудования BIOS или писать новые драйверы �с нуля� . Для разработчиков ран­
них версий MINIX выбор был несложен, так как возможности BIOS во многом
не соответствовали потребностям MINIX. Конечно, для того чтобы выполнить
начальную загрузку MINIX 3 с дискеты, жесткого диска или CD-ROM, монитор
загрузки пользуется функциями B I O S , так как здесь практически нет другой
альтернативы. Но загруженная система с собственными драйверами ввода-выво­
да способна на гораздо больше, чем BIOS.
Второй вопрос звучит так: как без поддержки BIOS обеспечить работу драйверов
с различным оборудованием? Чтобы сделать вопрос более конкретным, рассмот­
рим два принципиально различных типа контроллеров жестких дисков, исполь­
зуемых в современных 32 -разрядных процессорах Pentium, для которых и разра­
батывалась операционная система MINIX 3: интегрированный I D Е-контроллер
и контроллеры SСSI-расширения для шины PCI. Если вы хотите воспользовать­
ся устаревшим аппаратным обеспечением и приспособить MINIX 3 к оборудова­
нию, на которое были рассчитаны предыдущие версии MINIX, могут быть при­
менены четыре типа контроллеров жесткого диска: оригинальный 8-разрядный
контроллер ХТ, 1 6-разрядный контроллер АТ и два различных контроллера для
компьютеров серий PS/2 . Есть несколько возможных путей решения данной
проблемы.
1 . Для каждого типа дискового контроллера компилировать свою версию опера­
ционной системы.
2. Встроить в ядро несколько различных драйверов и дать системе возможность
автоматически выбирать нужный во время загрузки.
3. Встроить в ядро несколько различных драйверов и предоставить пользовате-
лю право выбрать подходящий.
Как мы увидим, эти решения не являются взаимоисключающими.
Первый вариант оптимален при долговременной работе. Если операционная сис­
тема работает на конкретной машине, нет необходимости хранить в памяти код
драйверов, которые никогда не будут использованы. С другой стороны, такой
вариант очень неудобен для распространителя системы. Предоставлять несколь­
ко загрузочных дисков и инструктировать пользователя, какой из них в каком
случае использовать, - сложно и дорого. Таким образом, более предпочтитель­
ны два других решения, по крайней мере, для начальной установки.
При втором подходе ОС должна выяснить, какое оборудование имеется в нали­
чии, считывая ПЗУ на периферийных картах или обмениваясь с ними данными
через порты ввода-вывода. На некоторых системах это выполнимо, но для I B M
такое решение н е всегда работает, так как для этих систем выпущено слишком
много нестандартного оборудования. Попытка передать данные в порт ввода-вы­
вода, чтобы определиться с одним устройством, может активизировать другое уст­
ройство, которое захватит управление и заблокирует систему. Подобный подход
усложняет начальный код для каждого из устройств и к тому же работает не
3 .7 . Реал ь ные диски 325

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


предоставлять возможность вручную корректировать результаты автоматиче­
ского детектирования оборудования. Обычно это механизмы, подобные исполь­
зуемым в MINIX 3.
В третьем подходе, присущем MINIX 3, компилируются несколько драйверов,
один из которых выбирается по умолчанию. Монитор начальной загрузки считы­
вает различные параметры заqJузки. Эти параметры могут быть введены вручную
либо храниться на диске. Пусть, например, при загрузке ищется такой параметр:
l abel = АТ

Тогда используется драйвер IDЕ-контроллера (at_wi n i ) . Все зависит от драй­


вера at_wi n i , связанного с меткой. Метки назначаются при компиляции загру­
зочного образа.
В MINIX 3 есть еще два инструмента, позволяющие снизить остроту проблем,
связанных с различиями в драйверах жестких дисков. Прежде всего, это специ­
альный драйвер, который в своей работе использует функции BIOS. Этот драй­
вер гарантированно работает практически с любой системой. Чтобы выбрать его,
необходимо задать следующий параметр загрузки:
l abel = Ьios

Однако этот драйвер стоит рассматривать лишь как крайнее средство. В систе­
мах с процессором 80386 и выше MINIX 3 работает в защищенном режиме, а код
B I O S всегда выполняется в реальном режиме (режим 8086). Переключение ре­
жимов при каждом вызове функции B I O S требует очень много времени.
Кроме того, еще одна стратегия, которая используется в MINIX 3 при работе
с драйверами, сводится к тому, чтобы отложить инициализацию до самого по­
сдеднего момента. Таким образом, если ни один из драйверов жестких дисков не
работает с некоторой конфигурацией, мы все равно можем запустить MINIX 3
с дискеты и выполнить некоторые полезные действия. Это не выглядит особо
дружественным пользователю, но примите во внимание то, что если бы все драй­
веры пытались инициализироваться непосредственно при запуске системы, то
неправильная установка некоторых устройств, которые все равно лежат балла­
стом, могла бы полностью парализовать ОС. Отложив инициализацию драйверов,
система может работать только с тем, что реально функционирует, давая пользо­
вателю возможность исправить ситуацию.
Отступая в сторону, заметим, что этот урок тяжело нам дался. Ранние версии
MINIX пытались инициализировать жесткий диск при загрузке системы. Если
жесткого диска не было, система зависала. Такое поведение было особенно не­
приятным потому, что система MINIX рассчитана также на машины без жестко­
го диска, хотя это снижает и доступный объем памяти, и производительность.
В этом и следующем разделах мы будем рассматривать драйвер жесткого диска
в стиле АТ, устанавливаемый в MINIX 3 по умолчанию. Это - многоцелевой
драйвер, обеспечивающий поддержку широкого диапазона контроллеров, как
ранних, применявшихся в системах 286, так и современных с усовершенство­
ванным интерфейсом жестких дисков с интегрированной электроникой (Extended
326 Глава З. Ввод-вы вод

Integrated Drive Electronics, EIDE), работающих с дисками гигабайтного объема.


Современные ЕID Е-контроллеры также поддерживают стандартные дисководы
CD-ROM. Тем не менее, чтобы упростить обсуждение, расширения, обеспечиваю­
щие поддержку CD-ROM, были исключены из кода, представленного на сопро­
вождающем книгу компакт-диске. Общие аспекты работы жесткого диска, кото­
рые мы включим в рассмотрение, относятся и к остальным драйверам.
Главным циклом драйвера жесткого диска служит уже изученный нами единый
код, поддерживающий девять стандартных запросов. Запрос DEV_OPEN непри­
ятен потенциальным немалым количеством работы, так как на жестком диске
всегда есть разделы и с высокой вероятностью подразделы. При открытии уст­
ройства (то есть при первом обращении к нему) эта информация должна быть
прочитана. В случае поддержки C D - R O M при обработке запроса DEV_O PEN
должно проверяться наличие носителя в приводе. Для CD-ROM имеет значение
и операция DEV_CLOSE: она приводит к отпиранию дверцы привода и выбросу
диска. В случае сменного носителя есть и другие сложности, в большей степени
имеющие отношение к гибким дискам, поэтому мы рассмотрим их позже. В данном
случае, чтобы •выбросить� диск, используется операция DEV_I OCTL, устанавли­
вающая значение флага, который анализируется при обработке запроса DEV_
CLOSE. Кроме того, DEV_I OCTL применяется для записи и чтения таблиц разделов.
Как мы видели ранее, обработка каждого из запросов DEV_READ, DEV_WR I TE,
DEV_GATHER и DEV_SCATTER состоит из двух фаз - подготовки и передачи. в слу­
чае жесткого диска вызовы DEV_CANC EL и DEV_SELECT игнорируются.
Драйвер жесткого диска не выполняет планирования - это делает файловая сис­
тема, формирующая векторы запросов для объединенного и разрозненного вво­
да-вывода. Кэш файловой системы передает драйверу запросы DEV_GATHER или
DEV_SCATTER на множество блоков (в M INIX 3 по умолчанию 4 Кбайт) , однако
драйвер обрабатывает запросы по секторам ( 5 1 2 байт) , хотя и в любом количе­
стве. Как бы то ни было, главный цикл всех дисковых драйверов преобразует
совокупность запросов на отдельные блоки данных в единый вектор запросов.
Запросы на чтение и запись не объединяются в один вектор. Кроме того, запросы
не могут быть помечены как необязательные. Элементы вектора запросов соот­
ветствуют непрерывной последовательности секторов диска. Файловая система
сортирует вектор перед его передачей драйверу, чтобы указать лишь начальную
позицию на диске для совокупности запросов.
Предполагается, что драйвер успешно выполнит как минимум первый запрос
вектора и совершит возврат в случае неудачной обработки. Решение о дальней­
ших действиях принимается файловой системой; запись она попытается завер­
шить, а при чтении вернет столько данных, сколько удалось получить.
Сама файловая система за счет разрозненного ввода-вывода могла бы реализо­
вать что-либо наподобие элеватора [ 1 18] (в запросе разрозненного ввода-вывода
запросы сначала сортируются по номеру блока). Второй этап планирования
выполняется в контроллере современного диска. Подобные контроллеры дос­
таточно •умные� и умеют хранить в буфере большой объем данных, тем самым
3 .7 . Реальные диски 327

безотносительно к порядку поступления запросов обеспечивая максимальную


эффективность передачи больших объемов данных при помощи внутренних
алгоритмов.

З . 7 5 Реал изация драй вера


. .

жесткого диска в M I N IX З
Жесткие диски часто называют •винчестерами• . Существует несколько историй
о том, как родилось это название. Оно было у проекта компании IBM, разраба­
тывавшей технологию, в которой магнитные головки •парят• над поверхностью
вращающегося диска, опираясь на тонкую воздушную прослойку. Одно из тол­
кований названия в том, что у первых моделей было два герметически закрытых
корпуса: фиксированный (30 Мбайт) и сменный ( тоже 30 Мбайт) . Предполо­
жительно, это напомнило разработчикам винчестер •30-30•, ставший •героем•
многих вестернов. Каково бы ни было происхождение названия, основа техно­
логии остается той же самой, хотя типичные жесткие диски современных мик­
рокомпьютеров гораздо миниатюрнее и хранят гораздо больше данных, чем их
1 4-дюймовый прототип начала 1 970-х.
Драйвер жесткого диска, рассчитанного на шину АТ, находится в файле at_
wini . с (строка 1 2 1 00). Драйвер предназначен для сложного устройства и со­
держит несколько страниц макроопределений, задающих регистры контроллера,
биты состояния, команды, структуры данных и прототипы. Как и в случае с драй­
верами других блочных устройств, в драйвере винчестера создается структура
типа driver, хранящаяся в переменной w_dtab (строки 123 16- 1233 1). Эта струк­
тура заполняется адресами функций, которые и выполняют все основные функ­
ции драйвера. Код большинства функций находится в файле а t_wini . с, но так
как для жесткого диска не нужны специальные действия для завершения работы,
поле dr_c l e anup структуры содержит ссылку на функцию nop_c l eanup в фай­
ле dri ve r . с. Последняя используется всеми драйверами, которые не нуждаются
в особых действиях для завершения. Данный драйвер не нуждается еще в не­
скольких функциях; соответствующие указатели ссылаются на функции семей­
ства nop_. Точка входа в драйвер - это функция at_wi nche s t e r_t a s k (стро­
ка 1 2336), которая выполняет аппаратно-зависимую инициализацию и вызывает
главный цикл (файл d r i ve r . c ) , передавая ему адрес w_dt ab. Главный цикл
(функция drive r_t a s k файла l i Ьdr i ve r / dr i ve r . c ) исполняется бесконеч­
но, обслуживая поступающие запросы и вызывая функции, адреса которых запи­
саны в полях структуры w_dt ab.
Поскольку в настоящее время мы имеем дело с электромеханическими устройст­
вами хранения информации, функции i ni t__pa rarns (строка 1 2347) необходимо
сделать немало работы для инициализации драйвера жесткого диска. Различные
параметры жесткого диска хранятся в таблице wini (строки 12254- 1 2276). Таб­
лица включает элемент для каждого из восьми ( МAX_DRIVE S ) поддерживаемых
дисков - до четырех обычных дисков и до четырех дисков на шине PCI с IDE­
и SА ТА -контроллерами (аббревиатура SATA расшифровывается как Serial АТ
Attachrnent - последовательное АТ-подключение).
328 Глава 3. Ввод- вывод

Следуя политике откладывания тех шагов инициализации, которые могут вы­


звать сбой, до момента их действительной необходимости, функция ini t_
params не выполняет никаких действий, требующих доступа к дисковым устрой­
ствам. Основное ее предназначение - скопировать информацию о логической
конфигурации жесткого диска в массив wi n i . ROM B I O S компьютера Pentiurn
получает базовую конфигурационную информацию из СМОS-памяти при вклю­
чении компьютера раньше, чем начинается процесс загрузки MINIX 3. Копиро­
вание информации из B I O S осуществляется в строках 1 2366- 1 2392. Многие
используемые здесь константы определены в файле i nc lude / i bm / Ь i o s . h. Его
код вы можете найти на компакт-диске M INIX 3. Неудачная попытка получения
этой информации не обязательно приводит к аварии. Современные жесткие дис­
ки позволяют считывать конфигурационные данные при первом обращении. Сле­
дом за вводом информации, полученной из BIOS, вызывается функция i n i t_
drive, заполняющая дополнительные данные для каждого диска.
На старых системах с IDЕ-контроллерами диск функционирует так, как будто
он является периферийной АТ-платой, хотя он может быть интегрирован в мате­
ринскую плату. Современные дисковые контроллеры, как правило, работают как
РСI -устройства и используют 32-разрядный тракт данных к процессору вместо
1 6-разрядной шиной АТ. К счастью, после завершения инициализации интер­
фейс к обоим поколениям дисковых контроллеров одинаков с точки зрения про­
граммиста. Если необходимо, функция ini t__params__pc i (строка 1 2437) вызыва­
ется для получения параметров РСI-устройств. Мы не будем вдаваться в ее детали,
но отметим несколько аспектов. Во-первых, параметр загрузки a t a_ins t ance,
используемый в строке 1 236 1 , задает значение переменной w_i n s t ance. Если
параметр загрузки не задан явно, по умолчанию выбирается нулевое значение.
Если параметр задан и положителен, тест в строке 1 2365 пропускает опрос BIOS
и инициализацию стандартных IDЕ -дисководов. В этом случае зарегистрирован­
ными окажутся только диски, обнаруженные на шине PCI.
Второй аспект состоит в том, что контроллер, обнаруженный на шине PCI, иден­
тифицируется как устройства управления с идентификаторами c 0 d4 - c 0 d7 .
При ненулевом значении w_i ns t ance идентификаторы c O d O - c O dЗ пропуска­
ются, если только контроллер шины PCI не идентифицирует себя как �совмести­
мый•. Эти идентификаторы назначаются дискам, подключенным к совместимому
контроллеру шины PCI. Скорее всего, пользователи MINIX 3 могут проигнори­
ровать все эти сложности. Компьютер, оснащенный менее чем четырьмя дисками
(включая дисковод CD- ROM), с большой вероятностью будет иметь классическую
конфигурацию, в которой устройствам назначаются идентификаторы c O dO - c OdЗ
независимо от контроллера, к которому они подключены (IDE или PCI), и типа
соединителя - классического 40-контактного или более нового последователь­
ного. Тем не менее программное обеспечение, создающее всю эту иллюзию, от­
личается высокой сложностью.
После вызова общего основного цикла никаких событий до первой попытки дос­
тупа к диску может не происходить. При попытке доступа основной цикл полу­
чает сообщение, запрашивающее операцию DEV_O PEN, и происходит косвенный
3 . 7 . Реальные диски 329

вызов функции w_do_open (строка 1 252 1 ) . В свою очередь, она вызывает w_


prepare, чтобы определить, корректно ли указано устройство, а далее, чтобы
выяснить тип устройства и инициализировать некоторые дополнительные пара­
метры в массиве wini, вызывает w_i dent i fy. Счетчик в массиве wini нужен
для того, чтобы определить, является ли текущий вызов первым после запуска
MINIX 3. После каждого вызова значение счетчика увеличивается. Если обнару­
жено, что это - первая операция DEV_O PEN, вызывается функция part i t i on
из файла drv l i b . с .
Следующая функция, w_prepare (строка 1 2577), принимает один целочислен­
ный аргумент dev i c e , равный вспомогательному номеру устройства, и возвра­
щает указатель на структуру devi c e , которая содержит информацию о базовом
адресе и размере устройства. (В языке С идентификатор, означающий имя струк­
туры, разрешено использовать в качестве имени переменной. ) Тип устройства
(является ли оно диском, разделом или подразделом) можно определить по его
вспомогательному номеру. После того как w_p repare завершает свою работу,
нет необходимости вызывать какие-либо связанные с разбиением на разделы
функции, записывающие или считывающие данные. Как вы могли увидеть, w_
p repare вызывается в ответ на запрос DEV_OPEN. Это также является одной из
частей трехэтапного цикла подготовки, планирования и завершения, применяе­
мого во всех операциях обмена данными.
Программно совместимые со стандартом АТ диски находятся в употреблении
довольно давно, и функция w_i dent i fy ( строка 1 2 603 ) должна определить,
какой из появившихся за эти годы вариантов жесткого диска используется. На
первом шаге выясняется, доступны ли для чтения и записи порты ввода-вывода,
которые должны существовать для всех дисковых контроллеров одного семейст­
ва. Здесь мы впервые имеем дело с доступом пользовательского драйвера к пор­
там ввода-вывода, поэтому данная операция заслуживает описания. Дисковый
ввод-вывод реализуется с помощью структуры c ommand, определенной в стро­
ках 1 2 20 1 - 1 2208. Эта структура заполняется последовательностью байтовых ве­
личин. Более детально мы опишем их чуть позже. Пока имейте в виду, что из
двух присваиваемых значений одним является ATA_I DENT I FY - команда за­
проса идентификации А ТА -диска (аббревиатура АТ А расшифровывается как АТ
Attachrnent - АТ-подключение), а вторым - битовая комбинация, выбирающая
диск. Затем вызывается функция c orn_ s i rnp l e .
Эта функция выполняет всю работу по формированию вектора из семи адресов
портов ввода-вывода и байтов, которые необходимо записать в них, передаче этой
информации системному заданию, ожиданию прерывания и проверки возвра­
щенного состояния. Она проверяет работоспособность диска и считывает строку
1 6 -разрядных значений посредством вызова ядра sys_insw в строке 1 2 629.
Декодирование информации - весьма запутанный процесс, и мы не 'станем рас­
сматривать его в подробностях. Достаточно сказать, что объем извлекаемых дан­
ных весьма велик, и среди них - модель диска и предпочтительные физические
параметры цилиндра, головки и сектора. (Заметьте, что �физическая� конфигура­
ция в действительности может не совпадать с реальной физической структурой,
330 Глава 3. Ввод-вывод

но у нас нет другой альтернативы, кроме как верить в то, что заявляет диск. )
Кроме того, эти сведения содержат информацию о том, поддерживает л и диск
линейную адресацию блоков ( Linear Block Addressing, LBA). Если данная функция
поддерживается, драйвер вправе игнорировать цилиндры, дорожки и секторы и ад­
ресовать секторы диска просто по их абсолютным номерам, что намного проще.
Как уже отмечалось, не исключена ситуация, когда функция i n i t_p a r arns
окажется не в состоянии получить информацию о конфигурации диска из BIOS.
Если такое случилось, код в строках 1 2666- 1 2674 делает попытку сформиро­
вать подходящий набор параметров, основанных на тех данных, которые удалось
прочитать с самого диска. Основная идея состоит в том, что номера цилиндра,
дорожки и сектора не должны превышать соответственно 1 023, 255 и 63, то есть
учитывается заложенное в структуры данных BIOS ограничение на количество
битов, выделяемых под эти параметры.
Если команда ATA_I DENT I FY возвращает отрицательный результат, это может
просто означать, что вы столкнулись со старой моделью диска, который не под­
держивает саму команду. В таком случае все, что мы имеем, - это параметры ло­
гической конфигурации, ранее прочитанные функцией i ni t_pararns . Если они
корректны, то они и записываются в поля структуры wini , в противном случае
сообщается об ошибке, и система отказывается работать с диском.
Наконец, чтобы считать адреса в байтах, в MINIX 3 используется переменная
uЗ 2_t . Максимальный объем раздела, с которым умеет работать драйвер, огра­
ничен значением в 4 Гбайт. Тем не менее структура devi c e , используемая для
хранения базового адреса и размера раздела и определенная в файле dr ivers /
l ibdrive r / driver . h (строки 1 0856- 1 0858), содержит числа типа u 6 4_t . Для
подсчета объема диска применяется операция 64-разрядного умножения ( стро­
ка 1 2688). Далее базовый адрес и размер диска вводятся в массив wini и вызы­
вается (дважды при необходимости) функция w_sp e c i fy, передающая обратно
контроллеру его рабочие параметры (строка 1 2 69 1 ). Затем выполняются вызовы
ядра. Вызов sy s_i rqs et p o l i cy (строка 1 2699) гарантирует, что по окончании
обслуживания прерывания от контроллера диска прерывания будут разрешены
автоматически. После этого вызов sys_i rqenaЫ e фактически разрешает пре­
рывание.
Функция w_narne (строка 1 27 1 1 ) возвращает указатель на строку, содержащую
имя устройства: «AT-DO•, «AT-D 1 • , «AT-D2• или «AT- D3•. При необходимо­
сти генерации сообщения об ошибке данная функция указывает, какой из дис­
ков является ее источником.
Не исключено, что в силу какой-либо причины диск окажется несовместимым
с MINIX 3. Функция w_i o_t e s t (строка 12723) проверяет каждый из дисков
при первой попытке получения доступа к нему. Она пытается считать первый
блок диска с более коротким временем ожидания, чем при обычном функциони­
ровании. Если попытка не удается, диск помечается как недоступный.
Функция w_spe c i fy (строка 1 2775) в дополнение к передаче параметров кон­
троллеру выполняет повторную калибровку устройства (для старых моделей),
передавая команду поиска нулевого цилиндра.
3.7. Реал ь ные диски 33 1

Функция do_t ran s f e r (строка 1 2 8 1 4 ) заполняет структуру cornmand байтовы­


ми значениями, необходимыми для передачи фрагмента данных (до 255 секто­
ров), а затем вызывает функцию c orn_out , посылающую команду контроллеру
диска. Формат данных зависит от способа адресации диска - по цилиндру,
головке и сектору или посредством линейной адресации блоков. Внутри MINIX 3
адресация дисковых блоков линейна. Таким образом, при линейной адресации
блоков первые три байтовых поля заполняются сдвигом счетчика сектора на со­
ответствующее число битов вправо и маскирования для получения 8-разрядного
значения. Счетчик сектора является 28-разрядным, поэтому последняя операция
маскирования использует 4 -разрядную маску (строка 1 2830). Если диск не под­
держивает линейную адресацию блоков, значения цилиндра, головки и сектора
вычисляются согласно параметрам диска (строки 1 2833 - 1 2835).
Данный код рассчитан на будущее расширение. Линейная адресация блоков с 28-
разрядным счетчиком сектора ограничивает объем жесткого диска, доступный
операционной системе MINIX 3, значением 1 2 8 Гбайт (вы можете использо­
вать жесткие диски большего объема, но для MINIX 3 будет доступно лишь
1 28 Гбайт). Программисты работают над новым режимом LBA48, хотя готовой
его реализации пока нет. В LBA48 для адресации дисковых блоков используется
48 бит. В строке 1 2824 проверяется, применяется ли режим LBA48. В описывае­
мой здесь версии MINIX 3 результат этого теста всегда отрицателен, поскольку
для противного случая еще нет кода. Имейте в виду, что если вы захотите вос­
пользоваться режимом LBA48 самостоятельно, просто добавить сюда недостаю­
щие команды недостаточно. Чтобы обеспечить обработку 48-разрядных адресов,
вам придется изменить множество фрагментов программного кода. Проще дож­
даться, пока MINIX 3 будет перенесена на платформу с 64-разрядным процессо­
ром. Если диска объемом 128 Гбайт для вас недостаточно, LBA48 позволит вам
расширить объем дискового пространства до 128 Пбайт (петабайт).
Теперь мы кратко рассмотрим передачу данных на более высоком уровне. Сна­
чала вызывается уже описанная нами функция w__prepare. Если запрошенная
операция касается множества блоков (запрос DEV_GATHER или DEV_SCATTER ) ,
сразу после этого вызывается функция w_t rans f e r (строка 1 2848). Если же опе­
рация относится к одному блоку (запрос DEV_READ или DEV_WRI TE ) , то сначала
создается одноэлементный вектор запроса, а затем вызывается w_t ran s f e r.
Функция w_t rans f e r ожидает вектор запросов i ove c_t . Каждый элемент век­
тора включает адрес и размер буфера с ограничением, что размер буфера должен
быть кратен размеру дискового сектора. Вся остальная информация передается
в качестве аргумента при вызове и применяется к вектору запросов целиком.
Первое, что делает функция w_t rans f e r, - проверяет, выровнен ли начальный
адрес, затребованный для передачи, по границе сектора (строка 1 2863 ). Далее
следует внешний цикл функции, повторяемый для каждого элемента вектора за­
просов. Как мы многократно видели, в этом цикле совершаются многочисленные
предварительные проверки, и лишь затем начинается фактическая работа. Снача­
ла поля i ov_s i z e каждого элемента вектора запросов суммируются, чтобы
332 Глава 3 . Ввод-вывод

получить общее число запрашиваемых байтов. Результат проверяется на крат­


ность размеру сектора. Далее проверяется, что начальный адрес находится стро­
го внутри адресного пространства устройства. Если запрос выходит за рамки
устройства, его размер округляется. До этого момента все расчеты производятся
в байтах, однако в строке 1 2876 позиция блока на диске вычисляется с исполь­
зованием 64-разрядной арифметики. Хотя переменная и имеет название Ы о с k,
в ней хранится число блоков диска, то есть 5 1 2-байтных секторов, а не внутренних
�блоков� операционной системы MINIX 3 по 4096 байт каждый. Затем выпол­
няется еще одна �подстройка� . У каждого диска имеется ограничение на число
байтов, которые можно запросить одновременно, и при необходимости объем
запроса соответственно уменьшается. После проверки инициализации диска
и, возможно, ее повторения запрос фрагмента данных посылается вызовом функ­
ции do_t rans f e r (строка 1 2887 ).
После запроса на передачу осуществляется вход во внутренний цикл, повторяе­
мый для каждого сектора. При чтении и записи каждый сектор вызывает преры­
вание. При чтении прерывание означает, что данные готовы к передаче. Вызов
ядра sys_i nsw (строка 1 2 9 1 3 ) запрашивает системное задание периодически
считывать порт ввода-вывода, передавая данные по виртуальному адресу в про­
странстве данных указанного процесса. В случае записи порядок меняется на
противоположный. Вызов sy s_out sw несколькими строчками ниже записывает
строку данных в контроллер, а контроллер генерирует прерывание после завер­
шения передачи. Прием прерывания и при чтении, и при записи осуществляет
функция at_int r_wa i t, к примеру, в строке 1 2920 - после операции записи.
Несмотря на то что в нормальном режиме прерывание должно быть, функция
at_int r_wa i t способна обработать ситуацию сбоя, и прерывание никогда не
генерируется. Функция также считывает регистр состояния контроллера диска
и возвращает различные коды, проверяемые в строке 1 2933. Если при чтении
или записи происходит ошибка, команда break предотвращает выполнение сек­
ции, в которой записываются результаты, а указатели и счетчики готовят операцию
над следующим сектором. По этой причине в следующей итерации внутреннего
цикла предпринимается попытка повторно обработать неудачный сектор, если
это разрешено. Если контроллер диска указал, что сектор неисправен, функция
w_t rans f e r немедленно завершает работу. Для других ошибок значение счет­
чика увеличивается, и функции разрешается продолжить свою работу, если чис­
ло ошибок не достигло rnax_e rro r s .
Следующая функция, которую м ы рассмотрим - c orn_out . Она посылает коман­
ду контроллеру диска, однако прежде чем разбираться в том, как она это делает,
давайте поймем, как контроллер выглядит с точки зрения программы. Управле­
ние контроллером диска осуществляется с помощью набора регистров. В некото­
рых системах эти регистры отображаются на память, однако в I ВМ-совместимых
компьютерах они представлены как порты ввода-вывода. Мы рассмотрим эти реги­
стры и обсудим некоторые аспекты их применения (а также регистров управления
вводом-выводом в целом). В MINIX 3 имеет место дополнительная сложность,
связанная с тем, что драйверы выполняются в пользовательском пространстве
3 . 7 . Реал ь н ые диски 333

и не могут исполнять команды чтения и записи регистров. Это означает, что сле­
дует изучить вызовы ядра, снимающие данное ограничение.
Регистры, используемые стандартным контроллером жесткого диска, совмести­
мым с I BM-AT, показаны в табл. 3.4.
+ LBA - для режима адресации по цилиндре, головке и сектору ( CHS) - О, для
режима линейной адресации блоков ( LBA) - 1 ;
+ D - для главного диска (rnaster) - О , для подчиненного (slave) - 1 ;
+ нsn - для режима C H S - выбор головки, для режима LBA биты 24-27 вы­
бора блока.

Регистры управления IDЕ-контроллером жесткого диска. Номера в скобках


Табли ца 3 . 4 .
означают биты логического адреса блока, соответствующие каждому регистру в режиме LBA
Регистр Чтение Запись
о Данные Данные
1 Ошибка Предварительная компенсация записи
2 Количество секторов Количество секторов
3 Номер сектора (0-7) Номер сектора (0-7)
4 Цилиндр (младшие биты) (8-1 5) Цилиндр (младшие биты) (8- 1 5)
5 Цилиндр (старшие биты) ( 1 6-23) Цилиндр (старшие биты) ( 1 6-23)
6 Выбор привода/головки (24-27) Выбор привода/головки (24-27)
7 Состояние Команда
Мы уже неоднократно упоминали чтение и запись в порты ввода-вывода, ото­
ждествляя их с адресами памяти. Однако порты ввода-вывода зачастую ведут се­
бя совершенно иначе, чем адреса памяти. Вообще говоря, входные и выходные
регистры, которым соответствует один и тот же порт ввода-вывода, не обязатель­
но совпадают. То есть данные, записанные в некоторый порт, нельзя считать из
него последующей операцией чтения. Например, последний регистр в табл. 3.4
показывает состояние контроллера диска, если считывать из него данные, а при
записи в него данных передает команду контроллеру. Кроме того, часто сам факт
записи данных в порт ввода-вывода приводит к выполнению некоторых дей­
ствий независимо от характера передаваемых данных (например, для регистра
команд АТ-совместимого контроллера). При работе с ним параметры заносятся
в младшие регистры, а в регистр команд помещается код операции. Сигналом на­
чала операции является факт записи данных в регистр команд.
Этот случай является иллюстрацией ситуации, когда назначение регистров зави­
сит от режи:ма работы. В приведенном в табл. 3.4 примере режим выбирается
шестым битом шестого регистра, как показано в табл. 3.5. Данные, записывае­
мые или считываемые в регистры 3-5, а также младшие четыре бита регистра 6
по-разному интерпретируются в зависимости от значения бита LBA.

Табли ца 3 . 5 . Поля регистра выбора привода/головки


7 6 5 4 3 2 1 о
LBA D HS3 HS2 HS1 HSO
334 Глава З. Ввод-вывод

Теперь рассмотрим, как при помощи функции c orn_out (строка 1 2947) команда
передается контроллеру. Эта функция вызывается после заполнения структуры
crnd уже изученной нами функцией do_t rans f e r. Прежде чем менять какие­
либо регистры, программа узнает, занят ли контроллер, считывая бит STATUS_
BSY. Здесь важна скорость, и так как контроллер обычно должен быть свободен
или скоро освободится, здесь используется активное ожидание. В строке 12960
вызывается команда w_wa i t f or, тестирующая значение бита STATUS_BSY. Чтобы
проверить бит в регистре состояния, w_wa i t f o r делает вызов ядра, осуществ­
ляющий чтение порта ввода-вывода. Цикл активного ожидания продолжается до
тех пор, пока бит не установится в значение готовности либо не истечет интервал
ожидания. Как только диск готов, цикл быстро прекращается. В качестве возвра­
щаемого значения будет указана истина, если контроллер готов сразу, истина, ес­
ли контроллер оказывается готов с некоторой задержкой, и ложь, если контрол­
лер не приходит в состояние готовности в течение интервала ожидания. Более
подробно интервал ожидания рассматривается при описании функции w_wa i t for.
Контроллер способен обслуживать более одного диска, поэтому когда он освобо­
ждается, в регистры записывается байт, выбирающий привод, головку и режим
работы (строка 12966), и функция w_wa i t f o r вызывается снова. Иногда приво­
ду не удается выполнить команду или правильно вернуть код ошибки; в конце
концов, это - механическое устройство, которое может заесть или просто сломать­
ся, и для страховки совершается вызов sys_s e t a l arrn ядра, чтобы системное
задание запланировало вызов подпрограммы, разблокирующей драйвер. Следом
за этим команда передается контроллеру, для чего сначала все параметры за­
писываются в разные регистры, а затем в регистр команд кладется код самой
команды. Это делает вызов sys_voutb ядра, посылающий вектор пар (значение,
адрес ) системному заданию. Системное задание записывает каждое значение
в порт ввода-вывода, задаваемый адресом. Вектор данных для вызова sys_voutb
формируется с помощью макроса pv_s e t , определенного в файле inc lude /
rnirni x / devi o . h. Обработка начинается, когда в регистр команд записывается
код операции. По завершении генерируется прерывание и отправляется уведом­
ление. Если при выполнении команды истечет интервал ожидания, синхронное
уведомление активирует дисковый драйвер.
Следующие несколько функций, которые мы рассмотрим, меньше по объему.
Функция w_need_re s e t (строка 1 2999) вызывается при истечении интервала
ожидания прерывания или готовности диска. Все, что делает w_need_re s e t , -
задает значение переменной s t at e для каждого диска в массиве win i , чтобы
вызвать инициализацию при следующей попытке доступа.
Функция w_do_c l o s e (строка 13016) в отношении обычного жесткого диска не
делает почти ничего. Если необходима поддержка CD-ROM, следует включить
в нее дополнительный код.
Функция c orn_s irnp l e отправляет команду контроллеру и немедленно заверша­
ется без обмена данными. В данную категорию попадают команды, которые слу­
жат для идентификации диска, установки некоторых параметров и повторной
калибровки привода. Пример использования этой функции мы видели в функции
3 .7 . Реальные диски 335

w_ident i fy. Перед ее вызовом структура c omrnand должна быть корректно ини­
циализирована. Обратите внимание на то, что сразу же после вызова c orn_out
вызывается функция at_int r_wa i t . В конечном счете, она совершает вызов
r e c e i ve, который обеспечивает блокировку до тех пор, пока не будет получено
уведомление о прерывании.
Мы упомянули о том, что функция c orn_out выполняет вызов sys_s e t a l arrn
ядра перед тем, как обратиться к системному заданию за записью регистров,
устанавливающих и исполняющих команду. Как уже отмечалось, следующая опе­
рация re c e i ve должна получить уведомление о прерывании. Если уведомление
бьто активизировано, а прерывания не случилось, следующим сообщением бу­
дет SYN_ALARМ. В этом случае вызывается функция w_t irneout (строка 1 3046).
Конкретные действия, которые нужно выполнить, зависят от текущей команды
в w_c omrnand. Возможно, от предыдущей команды остался истекший интервал
ожидания, и значение w_c omrnand равно CMD_I DLE, что указывает на заверше­
ние работы диска. В этом случае никаких действий выполнять не нужно. Если
работа не завершена и операция является чтением или записью, размер запросов
ввода- вывода можно сократить. Это делается в два этапа: количество запра­
шиваемых секторов уменьшается сначала до 8, затем - до 1. Во всех случаях
истечения времени ожидания на экран выводится сообщение и для повторной
инициализации всех приводов при следующей попытке доступа вызывается
функция w_need_re s e t .
Когда требуется сброс, вызывается функция w_re s e t (строка 1 3076). Эта под­
программа использует библиотечную функцию t i c kde l ay, устанавливающую
сторожевой таймер и ожидающую его истечения. Сначала w_r e s e t делает на­
чальную задержку, чтобы дать приводу время вернуться в исходное состояние
после предыдущих действий. Затем стробируется бит регистра управления кон­
троллера диска, то есть сначала на заданное время бит устанавливается в 1, а за­
тем возвращается в О. Далее вызывается w_wa i t f o r, чтобы позволить диску
прийти в состояние готовности. Если сброс не завершился успехом, на экран вы­
водится сообщение и возвращается код ошибки.
Команды, затрагивающие обмен данными с диском, обычно завершаются генера­
цией прерывания, которое отправляет сообщение обратно драйверу. Фактически
прерывание генерируется каждый раз, когда считывается или записывается
сектор. Функция w_int r_wa i t (строка 1 3 1 23) циклически вызывает rece ive,
и если получено сообщение SYN_ALARМ, вызывается w_t irneout . Помимо со­
общения S YN_ALARM, функция w_i nt r_wa i t воспринимает лишь один тип
сообщений - HARD_INT. При его получении выполняется считывание регистра
состояния, и прерывание инициализируется заново вызовом ack_arg s .
Функция w_int r_wa i t н е вызывается впрямую; при ожидании прерывания вы­
зывается следующая функция, at_int r_wa i t (строка 1 3 1 52). После получения
ею прерывания выполняется быстрая проверка битов состояния диска. Штатным
считается состояние, в котором сброшены биты занятости, сбоя записи и ошиб­
ки. В противном случае выполняется более тщательный анализ. Если регистр
вовсе не удалось прочитать, это считается сбоем системы. Если неполадка вызвана
336 Глава 3 . Ввод-вывод

неисправным сектором и возвращен определенный код ошибки, передается код


общей ошибки. Во всех случаях устанавливается бит STATUS_ADMBSY, который
позднее должен быть сброшен источником вызова.
Имеется несколько мест, где для активного ожидания бита регистра состояния
контроллера диска вызывается функция w_wa i t f o r (строка 1 3 177). Она исполь­
зуется в ситуациях, когда предполагается, что при первой проверке бит может
быть сброшен и желательно протестировать его быстро. Для скорости в ранних
версиях MINIX задействовали макрос, впрямую считывавший порт ввода-вывода.
Разумеется, это невозможно в MINIX 3, где драйверы выполняются в пользова­
тельском пространстве. Выходом является применение цикла do ...whi l e с ми­
нимумом накладных расходов перед первой проверкой. Если проверяемый бит
сброшен, выход из цикла производится немедленно. Чтобы принять в расчет воз­
можный сбой, в цикле предусмотрен интервал ожидания с учетом числа тактов
часов. При истечении интервала ожидания вызывается функция w_need_re s e t .
Параметр t irneou t , используемый функцией w_wa i t f o r, определен констан­
той DEF_T IMEOUT_T I CKS, равной 300 тактам или 5 секундам (строка 1 2228).
Аналогичный параметр WAKEUP (строка 1 2 2 16), применяемый при планирова­
нии �пробуждения» драйвера по сигналу таймерного задания, равен 3 1 с. Это -
очень большие периоды времени для активного ожидания, если сравнивать их
с обычным процессом, которому на работу дается 1 00 мс. Такие большие за­
держки обусловлены существующими стандартами для АТ-совместимых дисков.
Согласно этим стандартам, диску может потребоваться до 3 1 с, чтобы разо­
гнаться. Конечно, на практике такое время требуется только в самом худшем
случае, а разгон диска в большинстве систем происходит при включении питания
или после длительных периодов бездействия. Возможно, когда будет добавлена
поддержка CD-ROM (или других устройств, у которых раскрутка происходит
часто), данный вопрос станет более актуальным.
В файле at_wini . с есть еще несколько функций. Функция w_georne t ry воз­
вращает максимальное значение номеров цилиндров, дорожек и секторов для
выбранного жесткого диска. В отличие от виртуального диска, где эти значения
имитировались, здесь в числа вложен реальный смысл. Функция w_other выпол­
няется для всех нераспознанных команд и вызовов управления вводом-выводом.
В текущем выпуске MINIX 3 она не используется, поэтому, вероятно, следовало
бы исключить ее из кода, представленного на сопровождающем книгу компакт­
диске. Функция w_hw_int вызывается при неожидаемом аппаратном прерыва­
нии. В обзоре мы упомянули, что подобная ситуация может произойти из-за того,
что прерывание генерируется после истечения интервала ожидания. � опоздав­
шее» прерывание разблокирует операцию r e c e i ve, блокированную его ожида­
нием, однако уведомление о прерывании может быть обнаружено следующим
вызовом recei ve. Единственное, что нужно сделать, - это снова разрешить пре­
рывания. Для этого вызывается функция ack_i rqs (строка 1 3297). Она обраба­
тывает все обнаруженные диски в цикле, выполняя для каждого из них вызов
sys_i rqenabl e ядра, который гарантированно разрешает прерывания. Наконец,
в файле at_wi n i . с имеются две странные маленькие функции, s t r s t a t u s
3.7. Реал ьные диски 337

и s t rerr. Они объединяют коды ошибок со строками при помощи указанных


перед ними макросов (строки 133 1 3 и 1 33 14). Эти функции в описываемой вер­
сии MINIX 3 не используются.

3 . 7 6 Дисковод гибких дисков


. .

Драйвер дисковода гибких дисков сложнее, чем драйвер жесткого диска, а объем
его кода больше. Это на первый взгляд парадоксально, так как механизмы диско­
вода гибких дисков должны быть проще, чем механизмы винчестера, но кон­
троллер первого сложнее и требует к себе больше внимания от операционной
системы. К тому же, дополнительные сложности вносит сменный носитель. В теку­
щем разделе мы рассмотрим некоторые вопросы, которые могут оказаться полез­
ными для программиста, имеющего дело с гибкими дисками. В основных дета­
лях код драйвера дисковода сходен с кодом драйвера жесткого диска. Детально
же разбирать код, ввиду его сложности, мы не будем.
Одна из проблем, с которыми мы не столкнемся, работая с приводами дискет, -
это необходимость поддерживать много типов контроллеров, что было обяза­
тельно для жестких дисков. Хотя использующиеся сейчас гибкие диски с высо­
кой плотностью записи не поддерживались оригинальными машинами IBM РС,
все контроллеры гибких дисков обслуживаются одним драйвером. Такое от­
личие от жестких дисков произошло, видимо, потому, что на дисководы гибких
дисков не оказывалось то давление, которое заставляло разработчиков повышать
производительность жестких дисков. Дискеты редко используются как рабочее
хранилище в компьютерной системе, их скорость и емкость слишком ограничены
по сравнению с винчестерами. Дискеты продолжают использоваться, но только
для переноса небольших файлов, поэтому такими дисководами все еще оснаще­
но большинство компьютеров.
Драйвер дисковода гибких дисков не включает в себя сложных алгоритмов пла­
нирования, таких как S S F или элеваторный алгоритм. Запросы выполняются
строго последовательно, причем следующий запрос не принимается, пока не вы­
полнен текущий. Изначально при разработке MINIX предполагалось, что эта
система предназначена для персональных компьютеров, где большую часть вре­
мени будет активен только один процесс, и вероятность появления одного запро­
са во время обработки другого мала. Таким образом, поддержка очередей запро­
сов не дала бы заметного увеличения производительности, зато потребовала бы
значительного усложнения кода. Сейчас это тем более не имеет смысла, так как
дискеты по большей части применяют для переноса отдельных файлов с одной
системы на другую.
Драйвер дисковода гибких дисков, как и любое блочное устройство, способен об­
рабатывать запросы разрозненного ввода-вывода. Тем не менее массив запросов
у. него меньшего объема, чем у драйвера жесткого диска. Количество запросов
в массиве ограничено максимальным числом секторов на одной дорожке дискеты.
Простота устройства аппаратной части дисковода гибких дисков приводит к услож­
нению его драйвера. Использовать в дешевых, медленных и обладающих малой
338 Глава 3 . Ввод-вывод

емкостью дисководах сложные контроллеры, входящие в современные жесткие


диски, неоправданно. Поэтому программному обеспечению приходится явно учи­
тывать все аспекты взаимодействия с диском. В качестве примера, показываю­
щего сложность работы с дисководом, рассмотрим процесс позиционирования
магнитной головки на нужную дорожку при выполнении операции S EEK. Для
жесткого диска драйверу вообще никогда не требуется явно вызывать операцию
S ЕЕК. Номера цилиндров, головок и секторов, видимые программисту, могут не
совпадать с реальным физическим устройством жесткого диска. Реальная гео­
метрия может быть весьма запутанной, например, на внешних цилиндрах может
быть больше секторов, чем на внутренних. Тем не менее пользователь этого не
замечает. Жесткие диски способны поддерживать логическую адресацию блоков
(LBA), когда сектор указывается его абсолютным номером на диске, как альтерна­
тиву традиционной адресации по цилиндру, головке и сектору ( CHS). Но даже
при традиционной адресации можно использовать любую геометрию, раз кон­
троллер сам вычисляет, куда переместить головку, и при необходимости выпол­
няет операцию поиска.
Что же касается дискет, для них операция SEEK требует явного программирова­
ния. Если команда S EEK завершается неудачей, требуется вызвать подпрограм­
му, выполняющую операцию RECALBRATE, которая принудительно перемещает
головку на нулевой цилиндр. Это позволяет контроллеру заново найти нужную
дорожку, по шагам перемещая головку на известное количество дорожек. Конеч­
но же, подобные действия необходимы и для жесткого диска, но контроллер же­
сткого диска обходится без детальных инструкций драйвера.
Вот некоторые из особенностей дисководов для гибких дисков, которые услож­
няют драйвер.
1. Сменный носитель.
2. Различные форматы дисков.
3. Необходимость управления мотором.
Некоторые контроллеры жестких дисков предусматривают работу со сменным
носителем (например, контроллер CD-ROM), и обычно контроллер справляется
с большинством сложностей без помощи драйвера. В случае привода дискет встро­
енной поддержки нет, и это при том, что нужна она здесь еще больше. Дискеты
используются для переноса файлов и установки программ, и часто бывает, что
нужно менять дискеты, извлекая одну и вставляя следующую. Если данные, ко­
торые должны быть записаны на один диск, оказались на другом, неприятностей
не избежать. Драйвер обязан пойти на все во избежание таких проблем, хотя это
не всегда возможно, так как не все приводы позволяют определить, открывалась
ли дверца с момента последнего обращения. Другая проблема, к которой приво­
дит использование сменного носителя, - это обращение к приводу без дискеты,
что опасно зависанием системы. Такой проблемы можно избежать, если сущест­
вует возможность определить, открыта ли дверца привода, но так как это не все­
гда гарантируется, необходимо предусмотреть прерывание операции по истече­
нии интервала времени.
3 . 8 . Терми нал ы 339

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


носители могут иметь множество различных форматов. MINIX поддерживает
как 3,5-дюймовые, так и 5,25-дюймовые диски, отформатированные с различной
плотностью записи, от 360 Кбайт до 1 ,2 Мбайт (для 5-дюймовых дискет) или
1 ,44 Мбайт (для 3-дюймовых).
MINIX 3 работает с семью различными форматами. Существует два способа ре­
шения проблемы множества форматов. При первом подходе каждому формату
соответствует отдельное устройство с собственным вспомогательным номером.
Он использовался в ранних версиях MINIX. Были определены 14 различных
устройств, начиная с устройства / dev /pc O , которому сопоставлен 5-дюймовый
диск объемом 360 Кбайт, и заканчивая устройством /d ev / PS l , то есть 3-дюймо­
вой дискетой на 1 ,44 Мбайт. Такое решение громоздко, и в MINIX 3 применяется
альтернативный подход. Когда к дисководу обращаются через файл / dev / fdO
или / dev / f dl (второй дисковод), драйвер тестирует дискету, чтобы определить
ее формат. У разных форматов разное число цилиндров и секторов, и чтобы оп­
ределить формат, делается попытка прочитать последние секторы и дорожки.
Нужный вариант определяется путем исключения. Конечно, для этого требуется
некоторое время, однако на современных компьютерах, как правило, установле­
ны 3,5-дюймовые дискеты объемом 1 ,44 Мбайт, по этому такой формат проверя­
ется первым. Кроме того, диск, имеющий сбойные секторы или защиту от копи­
рования, может быть идентифицирован неправильно. Для тестирования дисков
имеется специальная утилита; средствами операционной системы это делается
слишком медленно.
Последняя сложность при работе с дисководом гибких дисков - это управление
двигателем. Считывать или записывать данные на дискету невозможно, если она
не вращается. Жесткие диски разрабатываются так, чтобы работать тысячи часов
без износа, но если мотор дисковода все время оставлять в движении, дискета
быстро придет в негодность. Если при обращении к диску мотор оказывается
выключенным, то прежде чем пытаться считывать данные, необходимо подать
команду для его включения, после чего подождать примерно полсекунды. На
включение и выключение двигателя уходит много времени, поэтому MINIX
оставляет двигатель работающим в течение еще нескольких секунд после обра­
щения. Если за это время к диску происходят новые обращения, таймер запуска­
ется заново. В противном случае мотор отключается.

3 . 8 . Терминал ы
В течение десятилетий пользователи взаимодействуют с компьютерами при по­
мощи устройств, состоящих из клавиатуры и дисплея. Посредством клавиатуры
пользователь вводит в компьютер данные, а компьютер отображает результаты
на дисплее. Долгое время клавиатура и дисплей объединялись в отдельное уст­
ройство, подключаемое к компьютеру проводом. Большие мэйнфреймы, используе­
мые в финансовой и туристической отраслях, до сих пор имеют такие терминалы.
340 Глава 3. Ввод-вывод

Как правило, терминал соединяется с мэйнфреймом при помощи модема, особенно


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

3 . 8 . 1 . Аппаратное обеспечение терминал а


С точки зрения операционной системы, терминалы можно разбить на три катего­
рии, в зависимости от того, как ОС взаимодействует с ними. Первую категорию
представляют терминалы, отображаемые на память и состоящие из клавиатуры
и дисплея, которые подключаются к компьютеру. Такая модель применяется во
всех персональных компьютерах. Во вторую группу входят терминалы, подклю­
чаемые через последовательную линию передачи данных стандарта RS-232, чаще
всего через модем. Такая модель до сих пор используется в некоторых мэйнфрей­
мах, однако имеются и персональные компьютеры с последовательным интер­
фейсом. Третью группу образуют терминалы, подключаемые через сеть. Разно­
образие терминалов иллюстрирует рис. 3 . 1 6.

Сетевой
интерфейс

Х-терминал
Рис. 3. 1 6 . Типы терминалов
Отображаемые на память терминал ы
Первая широкая категория терминалов (см. рис. 3. 1 6) - это отображаемые на
память терминалы. Такие терминалы представляют собой составную часть самого
компьютера, в особенности персонального. Они состоят из клавиатуры и мани:-
3 . 8. Терми налы 341

тора. Доступ к такому терминалу происходит посредством специальной области


памяти, называемой видеопамятъю, которая входит в общее адресное простран­
ство компьютера и адресуется так же, как и остальная память (рис. 3. 17).

Графический
Процессор Память адаптер

Шина ,,,,. Аналоговый


видеосигнал

араллельныи (то есть1 6 МГц)
п
порт Клавиатура
Рис. 3 . 1 7 . Отображаемые на память терминалы, доступ через видеопамять

На видеокарте имеется также микросхема, называемая видеоконтромером. На ее


вход подаются коды символов, а на выходе она генерирует видеосигнал, который
воспринимается монитором. Электронно-лучевая трубка монитора генерирует
электронный луч, горизонтально сканирующий экран, рисуя на нем линии. Обыч­
но экран имеет разрешение от 480 до 1 200 линий по вертикали и от 640 до
1920 точек в линии. Эти точки называют пикселами. Сигнал от видеоконтролле­
ра модулирует электронный луч и определяет, будет ли пиксел светлым или тем­
ным. У цветных мониторов есть три независимо модулируемых луча, отдельно
для красного, зеленого и синего цветов.
Жидкокристаллический дисплей имеет кардинально иное внутреннее устройство,
однако существуют жидкокристаллические дисплеи, воспринимающие те же ви­
део- и синхронизирующие сигналы, что и электронно-лучевые мониторы. Эти
сигналы используются для управления жид:r<им кристаллом каждого пиксела.
У простого монохромного монитора каждый символ (включая межсимвольный
промежуток) помещается в прямоугольник размером 9 пикселов в ширину и 14
в высоту, всего на экране получается 25 строк по 80 символов в каждой. При
этом дисплей должен иметь разрешение 350 строк по 720 пикселов в каждой. Ка­
ждый кадр обновляется от 45 до 70 раз в секунду. Видеоконтроллер при этом
мог бы считывать из видеопамяти первые 80 символов, генерировать для них
14 строк растра, затем считывать следующую строку символов, генерировать для
них растр и т. д. Фактически же, большинство контроллеров считывают символ
из видеопамяти один раз на строку растра, чтобы не было необходимости в буфе­
ризации. Рисунки символов размером 9 х 14 хранятся в П З У видеоконтролле­
ра (или О З У, чтобы отображать пользовательские шрифты). Эта область памя­
ти адресуется 1 2 -разрядным адресом, в котором 8 бит определяют код символа,
а 4 бита задают номер строки. 8 бит каждого байта памяти определяют 8 пиксе­
лов, а девятый пиксел всегда пустой. Таким образом, для отображения на экране
одной строки текста требуется 14 х 8 1 1 20 обращений к видеопамяти. То же
=

количество обращений делается к ПЗУ генератора символов.


342 Глава 3. Ввод-вывод

В IBM РС поддерживаются несколько режимов работы экрана. Простейший из


них представляет собой символьный дисплей для консоли. На рис. 3. 18, а мы
видим область видеопамяти. Каждый символ на рис. 3. 18, 6 представлен в видео­
памяти двумя байтами. Младший байт - это АSСП-код символа, а старший -
байт атрибутов, который может определять цвет, инверсию, мигание и прочие
атрибуты символа. В этом режиме весь экран размером 25 х 80 символов занима­
ет 4000 байт видеопамяти. Такой режим поддерживается всеми современными
дисплеями.

Видеопамять Экран
Адрес ОЗУ

т
AB C D
о 1 2 3

".х3х2х1х0
... xDxCxBxA
l.-- 1 eo символов -..!
ОхВООАО
ОхВОООО
!.-- 80 символов -..!
i�·
а б
Рис. 3. 1 8 . Область видеопамяти: а содержимое видеопамяти для монохромного
-

дисплея IBM; б соответствующее изображение на экране


-

Современные растровые дисплеи основаны на том же принципе, за исключением


того, что позволяют управлять цветом каждого пиксела на экране по отдельно­
сти. В простейшем варианте для монохромного дисплея каждому пикселу соот­
ветствует один бит видеопамяти. В другом предельном случае цвет пиксела опи­
сывается 24 -разрядным словом, в котором по 8 бит выделяется на красный,
зеленый и синий компоненты. Чтобы просто хранить содержимое экрана раз­
решением 768 х 1024 пикселов и глубиной цветности 24 бит/пиксел, требуется
2 Мбайт видеопамяти.
У отображаемого на память терминала клавиатура полностью отделена от экра­
на. Она может подключаться через последовательный или параллельный порт.
При каждом срабатывании клавиши генерируется прерывание, и драйвер кла­
виатуры считывает из порта ввода-вывода код нажатого символа.
У персонального компьютера клавиатура содержит встроенный микропроцессор,
который через специальный последовательный порт взаимодействует с чипом
контроллера на материнской плате. Прерывание генерируется каждый раз, когда
клавиша нажимается или отпускается. Все, что сообщает клавиатура, - это но­
мер нажатой клавиши, а не ее АSСП-код. Например, когда нажимается клавиша А,
в регистр ввода-вывода помещается код 30. А означает ли это символ верхнего
регистра, нижнего регистра, комбинацию Ctrl +A, Alt+A, Ctrl+Alt+A или другую
комбинацию, должен знать драйвер клавиатуры. У него имеется достаточно ин­
формации для выполнения этой работы, так как он в курсе, какие клавиши были
нажаты, но не были отпущены (например, Shift). Так как клавиатурный интер-
3 . 8 . Тер м и нал ы 343

фейс перекладывает все сложности на программное обеспечение, он очень гибок.


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

Те р м и н ал ы RS-232
Терминалы RS-232 представляют собой устройства, состоящие из клавиатуры
и экрана, которые взаимодействуют через последовательный интерфейс. Такой
интерфейс позволяет передавать по одному биту за раз (рис. 3. 19). Эти термина­
лы подключаются через 9- или 25-контактный разъем, в котором один контакт
служит для передачи данных, один для приема и один для заземления. Осталь­
ные контакты отвечают за разного рода управляющие функции, и большинство
из них не используется. Чтобы передать символ, терминал RS-232 должен от­
правлять по одному биту, предварив передачу стартовым битом и завершив ее
одним или двумя стоповыми битами. Также может быть добавлен бит контроля
четности, который позволяет организовать простейшую проверку правильности
передачи, хотя обычно он требуется только для взаимодействия с мэйнфреймами.
Э тот бит может находиться перед стоповыми битами. Стандартно применяются
скорости передачи 14 400 и 56 ООО бит/с: первая для факсимильной информа­
ции, вторая - для данных. Терминалы RS-232 обычно подключаются к удален­
ной системе при помощи модема и телефонной линии.

Компьютер

Интерфейс
Процессор Память RS-232

Рис. 3. 1 9 . Терми нал RS-232 взаимодействует с компьютером через последовательную


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

Так как на внутреннем уровне как терминал, так и компьютер представляют


информацию в виде символов, а передают по последовательной линии, были
разработаны микросхемы, осуществляющие преобразование символа в последова­
тельность битов, и наоборот. Эти чипы называются UART (Universal Asynchronous
Receiver Transmitter - универсальный асинхронный приемопередатчик). UART
подключаются к компьютеру при помощи интерфейсных карт RS-232, соединен­
ных с шиной, как показано на рис. 3.19. На современных компьютерах интерфей­
сы RS-232 и UART зачастую являются частью набора микросхем материнской
платы. Не исключена возможность отключения встроенной микросхемы UART
и использования вместо нее модемной интерфейсной платы либо сосуществова­
ния двух устройств. Модем осуществляет передачу по телефонной линии вместо
344 Глава 3. Ввод-вывод

последовательного кабеля и может быть оснащен устройством UART (возмож­


но, встроенным в состав многофункциональной микросхемы наряду с другими).
С точки зрения компьютера, UART выглядит неизменно при любой среде пере­
дачи - будь то последовательный кабель или телефонный провод.
Терминалы RS-232 постепенно вымирают, и на смену им приходят персональ­
ные компьютеры, однако их все еще можно встретить в старых мэйнфреймах,
особенно в банковской сфере, системах предварительного заказа авиабилетов и т. д.
Чтобы вывести символ на экран, драйвер терминала записывает этот символ в ин­
терфейсную карту, в которой она буферизируется, после чего поразрядно выдви­
гается в последовательную линию универсальным асинхронным приемопередат­
чиком. Например, для аналогового модема, работающего со скоростью 56 ООО бит/с,
для передачи одного символа требуется немного более 140 мкс. Поскольку эта
скорость невелика, драйвер обычно передает в интерфейсную карту RS-232 один
символ. Затем драйвер блокируется и ждет прерывания, которое инициирует ин­
терфейс, передав символ и перейдя в состояние готовности к приему следующе­
го символа. Микросхема UART способна одновременно передавать и принимать
символы. Прерывание также генерируется при получении символа, и обычно не­
сколько принятых символов могут сохраняться в буфере. Получив прерывание,
драйвер терминала должен проверить регистр, чтобы определить причину пре­
рывания. Некоторые интерфейсные карты имеют собственный процессор и па­
мять и способны одновременно поддерживать несколько линий, разгружая тем
самым центральный процессор.
Терминалы с интерфейсом RS-232 подразделяются на три категории. Наиболее
простыми являются печатающие терминалы, или телетайпы. Символы, набирае­
мые на клавиатуре, посылаются компьютеру. Символы, посланные компьютером,
печатаются на бумаге. Такие терминалы уже давно считаются устаревшими и поч­
ти не встречаются.
Простейшие электронно-лучевые терминалы работают похожим образом, но
вместо бумаги они выводят символы на экран. Такой терминал также называют
�стеклянным телетайпом• (glass tty), поскольку функционально он аналогичен
печатающему телетайпу. Термин �tty• является сокращением имени компании
Teletype, бывшей пионером в области компьютерных терминалов. Теперь сокра­
щение �tty• используется для обозначения любого терминала. � стеклянные» те­
летайпы также устарели.
�Умные» электронно-лучевые терминалы на самом деле представляют собой не­
большие специализированные компьютеры. У них есть процессор и память. Они
также содержат программное обеспечение, хранящееся, как правило, в П З У.
С точки зрения операционной системы, основное различие между �стеклянным»
телетайпом и �умным» терминалом состоит в том, что последний понимает
управляющие последовательности символов, называемые ЕSС-последователь­
ностями. Передав такому терминалу АSСП-символ ESC (033) и следом несколь­
ко других символов, можно управлять выводом на экран терминала. Напри­
мер, с помощью ЕSС-последовательности можно переместить курсор на новую
позицию, вывести текст в любое заданное место экрана, очистить экран и т. д.
3 . 8 . Терминалы 345

3 . 8 . 2 . П рограммное обеспечение терминала


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

П ро г раммное обеспечение ввода


Основная работа клавиатурного драйвера состоит в сборе вводимых с клавиату­
ры символов и передаче их программам, читающим с терминала. Существует две
концепции, описывающие работу драйвера. Согласно первой, задача драйвера
заключается в сборе ввода и передаче его программам без всяких изменений.
Программа, читающая с терминала, получает необработанные последовательно­
сти АSСП-символов. ( Передавать пользовательским программам коды нажатых
клавиш нельзя, так как они в большой степени зависят от конкретной машины.)
Эта философия хорошо удовлетворяет потребности таких сложных текстовых
редакторов, как ema c s , который позволяет пользователю связать любое дейст­
вие с любым символом или последовательностью символов. Однако это озна­
чает, что если пользователь вместо dat e наберет на клавиатуре команду ds t e,
а затем исправит ошибку, удалив три последние символа, допечатав символы
a t e и нажав клавишу Enter, пользовательская программа получит одиннадцать
АSСП-символов.
Не все программы способны разобраться в этих сложностях. Чаще всего им нуж­
на уже исправленная строка, а не вся последовательность введенных символов.
Таким образом, формируется вторая концепция: драйвер выполняет все редак­
тирование внутри строки, а пользовательской программе передает уже готовый
результат. Первая концепция ориентирована на символы, вторая - на строки.
Изначально эти режимы работы драйвера назывались режи.м,ом без обработки
и режи.м,ом с обработкой. В стандарте POSIX режим с обработкой называется
каноническим, режи.м,ом. Неканонический режим, соответствует режиму без обра­
ботки, хотя многие детали поведения терминала могут различаться. Совмести­
мые со стандартом P O S I X системы предоставляют несколько библиотечных
функций, обеспечивающих выбор любого из этих двух режимов, а также измене­
ние многих аспектов конфигурации терминала. В операционной системе MINIX 3
данные функции поддерживает системный вызов i o c t l .
Итак, основная задача клавиатурного драйвера состоит в сборе символов. Если ка­
ждое нажатие клавиши вызывает прерывание, драйвер может получать введенный
символ во время обработки прерывания. Если прерывания преобразуются низ­
коуровневым программным обеспечением в сообщения, каждый полученный сим­
вол может включаться в сообщение. В качестве альтернативы символ может
помещаться в небольшой буфер в памяти, а сообщение использоваться только
для извещения драйвера о том, что что-то прибьто. Второй подход более надежен,
особенно если сообщение посылается только ожидающему его процессу, а драйвер
клавиатуры занят обработкой предыдущего символа.
346 Глава 3. Ввод -вывод

Получив символ, драйвер должен начать его обработку. Если клавиатура переда­
ет информацию о номере нажатой клавиши, а не о коде символа, который нужен
для прикладных программ, драйверу требуется преобразовать номер в код сим­
вола при помощи таблицы. Не все IВМ-совместимые клавиатуры имеют одина­
ковую нумерацию клавиш, поэтому драйвер, чтобы обеспечить поддержку раз­
личных клавиатур, должен иметь разные таблицы перекодировки для разных
клавиатур. Простейший подход - скомпилировать драйвер с таблицей, предна­
значенной для преобразования кодов клавиш в кодировку A S C I I (American
Standard Code for Information Interchange - американский стандартный код об­
мена информацией). К сожалению, такое решение неудовлетворительно для не­
англоязычных пользователей. В разных странах приняты разные раскладки кла­
виатур, и стандартного набора АSСП-символов недостаточно для большинства
людей, населяющих восточное полушарие. Например, тем, кто разговаривает на
французском, португальском и испанском языках, требуются буквы с надстроч­
ными знаками и знаки препинания, которых нет в английском. Чтобы обеспечить
гибкую работу с клавиатурными раскладками для различных языков, во многих
операционных системах имеется возможность загружать различные кодовые
страницы. Они позволяют выбирать (при загрузке или позже), как клавиатур­
ные коды должны преобразовываться в коды символов.
Если терминал работает в каноническом режиме (режиме с обработкой), введен­
ные символы должны храниться в буфере до тех пор, пока не будет завершена
вся строка, поскольку пользователь может решить удалить ее часть. Даже если
переключить терминал в неканонический режим, может оказаться, что програм­
ма еще не запрашивала входные данные, поэтому введенные символы все равно
должны буферизироваться, чтобы позволить пользователю производить упреж­
дающий ввод. (Разработчиков систем, не позволяющих пользователям вводить
символы с клавиатуры без возможности исправления, следует обмазывать дег­
тем и вываливать в перьях, ибо заставить их пользоваться собственной системой
было бы слишком жестоким наказанием.)
Для буферизации символов обычно применяются два метода. В первом случае
в драйвере содержится центральный пул буферов, в каждом из которых хра­
нится около 1 О символов. С каждым терминалом связана структура данных, со­
держащая, среди прочего, указатель на цепочку буферов, в которых находятся
символы, введенные с данного терминала. Чем больше символов введено, тем
больше выделяется буферов, соединенных в цепь. Когда символ передается
пользовательской программе, буферы удаляются и память возвращается цен­
тральному пулу.
Другой подход заключается в том, что буферизация производится прямо в струк­
туре данных терминала, без центрального пула буферов. Поскольку пользовате­
ли часто печатают команду, обработка которой требует некоторого времени (на­
пример, на перекомпиляцию и сборку большой программы), а затем печатают
еще несколько строк, буфер драйвера должен вмещать не меньше 200 символов
для каждого терминала. В большой системе разделения времени с сотней терми­
налов выделение по 20 Кбайт на буфер ввода с каждой клавиатуры кажется
3 . 8 . Терминалы 347

чрезмерным, поэтому центрального пула буферов размером около 5 Кбайт будет,


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

Структура данных Структура данных


терминала терминала
Центральный
Терминал пул буферов Терминал

{
.------.

2 Буферная область
--------------

3
о для терминала О

а б
Рис. 3 . 20 . Варианты буферизации ввода:а центральный пул буферов;
-

б- выделенный буфер для каждого терминала


Хотя клавиатура и экран являются логически разделенными устройствами, мно­
гие пользователи привыкли сразу же видеть на экране вводимые с клавиатуры
символы. Некоторые (старые) терминалы должны были автоматически (аппа­
ратно) отображать все, что вводилось с клавиатуры, что не только крайне не­
удобно при вводе паролей, но также значительно ограничивает гибкость слож­
ных редакторов и других программ. К счастью, на большинстве терминалов при
нажатии клавиши ничего автоматически не отображается. Отображением симво­
лов на экране занимается исключительно программное обеспечение. Этот про­
цесс называется эхопечатъю.
Эхопечать неудобна тем, что во время нажатия пользователем клавиши програм­
ма может выполнять вывод на экран. По меньшей мере, драйвер должен решить,
где поместить «эхо» так, чтобы оно не исчезло под выходным потоком программы.
Кроме того, если пользователь вводит более 80 символов в одной строке, вывод
«эха» на 80-символьном экране может выглядеть по-разному. В зависимости от
приложения переход на следующую строку может оказаться приемлемым либо
неприемлемым. Некоторые драйверы просто усекают все введенные строки до
80 символов, игнорируя все после колонки 80.
Еще одна проблема заключается в обработке символов табуляции. Обычно драй­
вер вычисляет текущую позицию курсора, учитывая как вывод программы, так
и «эхо » , после чего вычисляет число отображаемых до позиции табулятора
пробелов.
348 Глава 3 . Ввод-вывод

Наконец, существует проблема эквивалентности устройств. Логически в конце


строки текста требуется символ возврата каретки, чтобы переместить курсор
обратно к колонке 1, и символ перевода строки для перемещения курсора на
следующую строку. Требовать от пользователя вводить оба символа - вряд ли
удачная мысль, хотя на некоторых терминалах имеется специальная клавиша,
посылающая оба символа с 50-процентной вероятностью в том порядке, в кото­
ром их ожидает программа. Преобразование всего, что поступает с клавиатуры
в стандартный внутренний формат, используемый операционной системой, яв­
ляется одной из задач драйвера.
Если стандартом регламентируется хранение только символов перевода строки
(соглашение UNIX), тогда символы возврата каретки должны преобразовывать­
ся в символы перевода строки. Если внутренний формат предусматривает хране­
ние обоих символов (соглашение Windows), тогда драйвер должен формировать
символ перевода строки при получении символа возврата каретки, и наоборот.
Независимо от внутренних правил, терминал может требовать наличия обоих
символов для корректного управления выводом на экран. Поскольку к большо­
му компьютеру могут оказаться подключенными терминалы различных типов,
драйвер клавиатуры должен обеспечивать преобразование всех комбинаций сим­
волов возврата каретки и перевода строки во внутренний стандарт, а также сле­
дить за правильностью эхопечати.
И это еще не все. Есть еще одна проблема, связанная с перемещением на но­
вую строку. Некоторым терминалам на вывод специальных символов требуется
больше времени, чем на отображение �нормальных� символов (то есть букв или
цифр). К примеру, если микропроцессору в терминале, чтобы выполнить про­
крутку экрана, нужно скопировать большой блок текста, то перевод строки
может выполняться медленно. Если механической печатающей головке надо
вернуться к левому полю документа, на вывод символа возврата каретки потре­
буется больше времени. В обоих случаях драйвер терминала может вставлять
в выходной поток си.м,волы заполнения (пустые символы) или же просто приоста­
навливать вывод на некоторое время, чтобы терминал успел за потоком симво­
лов. Необходимая задержка часто связана с быстродействием терминала. На­
пример, на скорости 4800 бит/с и ниже задержка вряд ли нужна, а на 9600 бит/с
и выше может понадобиться один символ заполнения. Терминалам с аппаратной
поддержкой табуляции, особенно тем, которые выдают печатный документ, до­
полнительная задержка может потребоваться после символа табуляции.
При работе в каноническом режиме некоторые вводимые символы имеют особое
значение. В табл. 3.6 показаны все специальные символы, определенные стан­
дартом POSIX, а также дополнительные, распознаваемые системой MINIX 3. По
умолчанию все они являются управляющими символами, которые не должны
конфликтовать с вводимым текстом или кодами, используемыми программами.
Однако все символы, кроме последних двух, допустимо изменять с помощью ко­
манды s t ty. Многие специальные символы, принятые по умолчанию в UNIX,
отличаются от приведенных здесь.
3 . 8 . Терминалы 349

Табл и ца 3 . 6 . Специальнр1е символы канонического режима


Клавиши Имя в POSIX Комментарий
Ctrl+D EOF Конец файла
EOL Конец строки (не определен)
Ctrl + H ERAS E Удалить один символ слева
Ctrl +C I NTR Прервать процесс ( S I G I NT)
Ctrl+U K I LL Удалить всю введенную строку
Ctrl+\ QUIT Принудительный дамп памяти ( S I GQU IT)
Ctrl+Z SUSP Приостановить (игнорируется M I N IX)
Ctrl+Q START Начать вывод
Ctrl+S STOP Остановить вывод
Ctrl +R REPRI NT Заново отобразить ввод (расширение M I N IX)
Ctrl +V LN EXТ Интерпретировать следующий символ буквально
(расширение M I N IX)
Ctrl +O D I SCARD Отмена вывода (расширение M I N IX)
Ctrl +M CR Возврат каретки (неизменный символ)
Ctrl+J NL Перевод строки (неизменный символ)
Символ E RA SE позволяет пользователю удалить один только что введенный
символ. В MINIX 3 этому символу соответствует клавиша забоя (Ctrl+H). Этот
символ не добавляется к очереди символов, а, наоборот, удаляет предыдущий
символ из очереди. Печать эха для такого символа должна выглядеть как после­
довательность трех символов: перемещение курсора на позицию влево, пробел
и еще раз возврат на позицию, чтобы удалить с экрана предьщущий символ. Если
же предыдущим символом был символ табуляции, требуется определить, какой
символ предшествует ему. В большинстве систем забой удаляет символы только
текущей строки. Символы возврата каретки и перевода строки не удаляются.
Во многих старых системах, не позволяющих перемещать курсор влево и вправо,
если пользователь обнаруживал ошибку в начале введенной строки, единствен­
ным способом ее исправления являлось удаление всей строки. Для удаления
строки целиком используется специальный символ K I L L (Ctrl+U в MINIX 3 ) .
В MINIX 3 строка стирается с экрана, хотя в некоторых системах удаленная стро­
ка выводится с символами возврата каретки и конца строки, поскольку некото­
рые пользователи хотят ее видеть. Таким образом, способ отображения символа
KI LL дело вкуса. Как и в случае с ERASE, действие KILL не распространяется
-

дальше текущей строки. При удалении блока символов драйвер может как воз­
вращать, так и не возвращать буферы в пул (если он используется).
Иногда символы ERASE или K I LL должны быть введены в строку как обычные
данные. Для этого служит символ LNEXT, действующий в качестве префиксноzо
сим.вола. В M INIX 3 ему по умолчанию соответствует сочетание клавиш Ctrl+V.
В более старых версиях UNIX для ввода символа K I LL часто использовалась
клавиша @, но впоследствии этот символ стал составной частью адресов элек­
тронной почты Интернета (например, linda@cs.washington.edu). Те, кому привычнее
350 Глава 3. Ввод- вы вод

старые соглашения , могут переопределить символ KILL как @, но тогда при набо­
ре адреса электронной почты им придется вводить символ @, нажимая сочетание
клавиш Ctrl+V, @. Сам символ LNEXT может быть введен, если дважды нажать
клавиши Ctrl+V. Встретив символ LNEXT, драйвер установит флаг, означающий,
что следующий символ не следует подвергать специальной обработке. Сам сим­
вол LNEXT не помещается в очередь символов.
Чтобы приостановить и продолжить вывод на экран, также предоставляются спе­
циальные управляющие коды. В MINIX 3 это символы STOP (Ctrl+S) и START
(Ctrl+Q). Они не хранятся в буфере, но используются для установки и сброса
флага в структуре данных терминала. При каждой операции вывода на экран
проверяется значение этого флага. Если флаг установлен, вывод не производит­
ся. Эхопечать при этом, как правило, также подавляется.
Часто возникает необходимость прервать выполнение отлаживаемой програм­
мы. Для этой цели могут использоваться символы INTR (Ctrl+C) и QUIT (Ctrl+\).
В MINIX 3 сочетание Ctrl+C посылает сигнал прерывания S I G INT всем процес­
сам, запущенным с этого терминала. Реализация может быть непростой. Наи­
более сложной является передача информации от драйвера в ту часть системы,
которая занимается обработкой сигналов, поскольку она не ожидает получения
подобной информации. Результат нажатия клавиш Ctrl+\ аналогичен нажатию
клавиш Ctrl+C, с той разницей, что процессам посьтается сигнал S I GQUI T, вы­
зывающий прекращение работы процесса с сохранением дампа ядра, если этот
сигнал специально не перехватывается процессом или не игнорируется.
При нажатии любого из этих сочетаний клавиш драйвер должен вывести эхо
в виде символов перевода строки и возврата каретки, а также очистить свой бу­
фер с накопленными символами, чтобы позволить начать новый ввод. Во многих
UNIХ-системах символ INТR по умолчанию генерируется клавишей Del. Посколь­
ку программы зачастую используют Del наравне с клавишей забоя при редакти­
ровании, в настоящее время предпочтение отдается комбинации Ctrl+C.
Специальный символ EOF (Ctrl+D) в MINIX 3 удовлетворяет активный запрос на
чтение и передает все содержимое буфера, даже если он пуст. Нажатие клавиш
Ctrl+D в начале строки приводит к тому, что программа считывает О байт. При
считьmании файла это обычно воспринимается и обрабатывается как конец файла.
Некоторые драйверы терминала предоставляют возможность более сложного ре­
дактирования строки, чем бьто описано здесь. Они имеют специальные управ­
ляющие символы, позволяющие удалять целиком слова, перемещать курсор впе­
ред и назад по символам и по словам, вставлять текст в середину уже набранной
строки и т. д. Добавление подобных функций к драйверу значительно увели­
чивает его. Кроме того, эти функции чаще всего оказываются неиспользуемыми
экранными редакторами, предпочитающими работать с драйверами клавиатуры
в режиме без обработки.
Стандарт POSIX требует того, чтобы в стандартной библиотеке бьти доступны
несколько функций, позволяющих программам управлять параметрами терми-
3 . 8 . Терминал ы 351

нала. Самые важные и з них - t c ge t a t t r и t c s et a t t r. Первая получает от


системы копию структуры t e rrni o s , показанной в листинге 3.3. Эта структу­
ра содержит всю информацию, необходимую для задания режимов работы, на­
стройки специальных символов и управления характеристиками терминала.
Программа может узнать текущие значения параметров и изменить их на свой
вкус. Функция t c s et a t t r позволяет передать эту структуру обратно драйверу
терминала.
Л и стинг 3 . 3 . Структура termios. В MINIXЗ 3 тип c_fl a g_t эквивалентен типу short,
тип speed_t типу iпt, а тип cc_t типу char
- -

s t ruc t t e rm i o s {
t c f l ag_t c_i f l ag ; / * Режимы ввода * /
t c f l ag_t c _o f l a g ; / * Режимы вывода * /
t c f l ag_t c_c f l ag ; / * Режимы упра вления * /
t c f l ag_t c _l f l ag ; / * Локальные реж имы * /
speed_t c _ i s p e e d ; / * Скорость ввода * /
speed_t c_o speed ; / * Скоро с т ь вывода * /
c c _t с_с с [ NCC S J ; / * Управляющие с имволы * /
} ;

Стандарт POSIX не определяет, как реализовывать свои требования - при по­


мощи системных вызовов или библиотечных подпрограмм. В MINIX имеется
системный вызов i o c t l , записываемый следующим образом:
i o c t l ( f i l e_de s c r i p t o r , reques t , argp ) ;

Этот вызов позволяет определять и изменять конфигурацию многих устройств


ввода-вывода. Функции t c get at t r и t cget at t r реализованы на его основе.
Здесь переменная r e qu e s t указывает, считывать или записывать структуру
t errni o s (для записи также указывается, нужно ли выполнять действие немед­
ленно или отложить до завершения текущей очереди запросов). Переменная argp
содержит указатель на структуру t errni o s вызывающей программы. Такой под­
ход к взаимодействию драйвера с программой был выбран по большей мере для
совместимости с UNIX, чем из соображений удобства.
Следует сделать несколько замечаний о структуре t errni o s . Четыре управляю­
щих слова в ней обеспечивают большую гибкость. Отдельные биты поля с_
i f l ag отвечают за то, как обрабатываются входные данные. Например, бит ICRNL
управляет преобразованием символов CR в NL при вводе. В MINIX 3 этот флаг
установлен по умолчанию. Поле c_o f l ags содержит флаги, управляющие выво­
дом. Например, флаг OPOST разрешает обработку выводимых данных. Этот бит,
как и бит ONLCR, который обеспечивает преобразование символов NL в последо­
вательность CR NL, в MINIX 3 устанавливается по умолчанию. Поле c_c f l ag
содержит флаги управления. Параметры MINIX 3, установленные по умолчанию,
разрешают передачу 8-разрядных символов, кроме того, модем должен 4:КЛасть
трубку• при отключении пользователя. Поле c_l f l ag - это флаги локального
режима. Бит ЕСНО управляет выводом эха (при входе пользователя в систему
эту функцию можно отключить, чтобы обезопасить ввод пароля). Один из самых
важных битов - бит I CANON, разрешающий канонический режим. Когда бит
352 Глава 3 . Ввод- вывод

I CANON сброшен, существует несколько возможностей. Если сохранить значе­


ния по умолчанию для всех остальных параметров, терминал переходит в режим,
идентичный традиционному режиму с прерыванием. В этом режиме символы
передаются программе, не дожидаясь ввода всей строки, но управляющие коды
INTR, QUIT, START и STOP сохраняют свое действие. Этот режим можно отклю­
чить, сбросив значения других флагов, и получить эквивалент обычного режима
без обработки.
Различные специальные символы, значения которых можно изменить (включая
расширения MINIX 3), хранятся в массиве с_с с . Кроме того, в нем хранятся два
параметра, используемые в неканоническом режиме. Значение MIN, помещаемое
в с_с с [VМIN ] , задает минимальное количество символов, достаточное для вы­
зова read. Величина TIME, хранящаяся в с_с с [ VTIME ] , задает лимит времени
для этого вызова. К какому результату приводят разные комбинации значе­
ний, показано в табл. 3.7, иллюстрирующей обработку вызова, запрашивающего
N байт. Если значение T IME равно О, а MIN 1, поведение аналогично режиму
-

без обработки.

Параметры MIN и TIME определяют, как выполняется чтение в неканоническо м


Табли ца 3 . 7 .
режиме. N число запрошенных байтов
-

TIME = О TIME > О


MIN = О Вызов завершается немедленно, Сразу же запускается таймер. Возвращается
возвращая имеющееся число первый полученный байт или ни одного,
байтов, от О до N если истекло время
MIN > О Вызов возвращает от MIN После первого байта запускается
до N байт. Возможна межбайтовый таймер. Возвращается
бесконечная блокировка N байт, если они уложились во временной
интервал, но не меньше 1 байта. Возможна
бесконечная блокировка

П рограммное обеспечение вывода


Терминальный вывод несколько проще ввода, однако драйверы терминалов RS-
232 радикально отличаются от драйверов терминалов, отображаемых на память.
Как правило, метод, используемый для терминалов с интерфейсом RS-232, со­
стоит в том, что для каждого терминала выделяется выходной буфер. Эти буфе­
ры могут входить в тот же пул буферов, что и входные буферы, или представ­
лять собой выделенные буферы. Когда программы выводят данные в терминал,
вывод сначала копируется в буфер. После того как символы оказываются в вы­
ходном буфере, первый символ выводится на терминал, а затем драйвер блоки­
руется. Когда происходит прерывание, извещающее драйвер о готовности терми­
нала принять следующий символ, посылается этот символ и т. д.
Отображаемые на память терминалы позволяют применять еще более простую
схему. Символы, которые должны быть напечатаны, один за другим извлекаются
из пользовательского пространства и записываются непосредственно в видеопа­
мять. В случае с терминалами RS-232 символ просто передается в последова-
3 . 8 . Терминалы 353

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


лы нуждаются в дополнительной обработке. Среди них символы забоя, возврата
каретки, перевода строки и звукового сигнала (Ctrl+G). Драйвер отображаемого
на память терминала должен программно отслеживать текущее положение кур­
сора, обновляя его после вывода печатного символа. При выводе специальных
символов он должен соответствующим образом менять положение курсора.
В частности, переводя строку внизу экрана, необходимо выполнять прокрут­
ку его содержимого. Работу прокрутки иллюстрирует рис. 3 . 1 8. Если видео­
контроллер всегда располагает начало видеопамяти по адресу ОхВОООО, единст­
венный способ сдвинуть содержимое - скопировать 24 х 80 символов (каждый
символ представляется двумя байтами) из области, начинающейся по адресу
ОхВООАО, в область ОхВОООО. На это требуется время. В растровом режиме си­
туация еще хуже.
К счастью, обычно аппаратное обеспечение несколько упрощает эту операцию.
У большинства видеоконтроллеров имеется регистр, указывающий, какому адре­
су в видеопамяти соответствует первая строка экрана. Поэтому чтобы прокру­
тить экран вверх на одну строку, достаточно изменить значение этого регистра
так, чтобы видимая область начиналась по адресу ОхВООАО, а не ОхВОООО. Тогда
единственное, что должен сделать драйвер, - просто записать новые символы
в следующую строку в видеопамяти. Когда видеоконтроллер достигает верхней
границы видеопамяти, он переходит в ее конец, начиная вновь с нижнего адреса.
Подобная аппаратная поддержка предоставляется и в растровом режиме.
Еще одна вещь, о которой должен заботиться драйвер отображаемого на память
терминала, - позиционирование курсора. Опять же, упрощает дело наличие ап­
паратного регистра, указывающего, куда должен переместиться курсор. Наконец,
остается проблема обработки звукового сигнала, который получается путем по­
дачи прямоугольной или синусоидальной волны на внутренний динамик, имею­
щий мало общего с видеопамятью.
Перед экранными редакторами и другими сложными программами иногда воз­
никают более трудные задачи перерисовки экрана, нежели обычная прокрутка
текста в конец дисплея. Для этого многие драйверы терминала поддерживают
различные управляющие последовательности. Хотя некоторые терминалы поль­
зуются уникальными наборами управляющих последовательностей, желатель­
но иметь стандарт, который позволил бы переносить программное обеспечение
с одной системы на другую. Американский национальный институт стандартов
(American National Standard Institute, ANS I ) разработал набор стандартных
управляющих последовательностей. MINIX 3 поддерживает подмножество этого
набора, представленное в табл. 3.8 и достаточное для многих распространенных
операций. Когда драйвер встречает символ начала управляющей последователь­
ности, он устанавливает флаг и дожидается ее окончания, а затем передает соот­
ветствующий символ программе. Вставка и удаление текста требуют перемеще­
ния блоков символов в видеопамяти. Здесь аппаратная поддержка практически
отсутствует, за исключением прокрутки и отображения курсора.
354 Глава 3 . Ввод-вывод

Управляющие последовательности, воспринимаемые драйвером терминала


Таблица 3 . 8 .
при выводе. ESC означает символ начала управляющей последовательности (Ох1 В), а п, m
и s - дополнительные численные параметры
ЕSС- последовательность Значение
ESC [nA Переместить курсор вверх на n строк
ESC [nB Переместить курсор вниз на n строк
ESC [nC Переместить курсор вправо на n позиций
ESC [nD Переместить курсор влево на n позиций
ESC [m;nH Переместить курсор в позицию (m, n)
ESC [sJ Очистить экран от позиции курсора (О - до конца,
1 - до начала, 2 - весь)
ESC [sK Очистить строку от позиции курсора (О - до конца,
1 - до начала, 2 - всю)
ESC [nL Вставить n строк в позицию курсора
ESC [nM Удалить n строк в позиции курсора
ESC [nP Удалить n символов в позиции курсора
ESC [n@ Вставить n символов в позицию курсора
ESC [nm Разрешить выделение текста (О - нормальный текст,
4 - полужирный текст, 5 - мерцающий текст,
7 - инверсный текст)
ESC М Прокрутка экрана в обратную сторону, если курсор
находится в верхней строке

3 . 8 . З . Драйвер терми нал а в M I N IX 3


Код драйвера терминала в MINIX находится в четырех С-файлах (если включе­
на поддержка псевдотерминалов RS-232, то в шести). Вместе они образуют са­
мый большой и сложный драйвер в MINIX 3. Большой размер частично объяс­
няется тем, что драйвер должен обслуживать одновременно клавиатуру и экран,
которые сами по себе являются сложными устройствами, а также два других до­
полнительных типа терминалов. Но многих все равно удивляет, что обслужива­
ние терминального ввода-вывода требует в тридцать раз больше кода, чем зани­
мает планировщик. ( Это удивление подкрепляется многочисленными книгами,
в которых обсуждению работы планировщика отводится в тридцать раз больше
места, чем обсуждению ввода-вывода.)
Драйвер терминала принимает около полутора десятков типов сообщений. Пере­
числим наиболее важные.
1. Чтение с терминала (от файловой системы по поручению пользовательского
процесса).
2. Вывод на терминал (от файловой системы по поручению пользовательского
процесса).
3. Установка параметров терминала для i o c t l (от файловой системы по пору­
чению пользовательского процесса).
4. Прерывание от клавиатуры (нажатие или отпускание клавиши).
3. 8 . Терминалы 355

5. Отмена предыдущего запроса (от файловой системы, когда возникает сигнал).


6. Открытие устройства.
7. Закрытие устройства.
Остальные типы сообщений используются для специальных целей, например
для отображения диагностических данных по нажатию функциональных клавиш
или для генерации дампов при сбоях.
Сообщения с командами на запись и чтение имеют тот же формат, что и сообще­
ния, показанные в табл. 3.3, за тем небольшим исключением, что не требуется
поле POS I T I ON. Работая с диском, программа должна указывать, какой блок не­
обходимо считать. При работе с терминалом такого выбора нет: всегда считыва­
ется следующий введенный пользователем символ. Клавиатуры не поддержива­
ют произвольную запись (позиционирование).
Функции стандарта POSIX, t c s et a t t r и t c get a t t r, необходимые для опре­
деления и изменения параметров (атрибутов) терминала, поддерживаются при
помощи системного вызова i o c t l . Хороший стиль программирования требует,
чтобы для управления терминалом использовались эти и другие функции из
файла i n c l ude / t errni o s . h, а преобразование этих вызовов в системный вы­
зов i o c t l было бы оставлено стандартной библиотеке языка С. Тем не менее
MINIX 3 необходимы некоторые управляющие операции, которые не охватыва­
ются стандартом POSIX, например загрузка альтернативной раскладки клавиа­
туры. Для таких операций программисту не остается ничего другого, как явно
выполнять вызов i o c t l .
Сообщение, которое i o c t l отправляет драйверу терминала, содержит код за­
прашиваемой функции и указатель. Библиотечной функции t c s et a t t r соот­
ветствуют коды функций TC S ETS, TC S ETSW и TC S ETSF, а указатель должен ссы­
латься на структуру t errni o s (см. листинг 3.3). В данном случае вызов замещает
текущие значения атрибутов новыми, различие между тремя функциями в том,
что запрос TC S ETS приводит к немедленному изменению атрибутов, TC S ETW вы­
полняется только после того, как завершается вывод, а T C S E T S F сначала до­
жидается окончания вывода, а затем очищает все еще не считанные входные
данные. Функция t cget a t t r транслируется в вызов i o c t l с кодом операции
TCGETS и возвращает сделавшей вызов программе заполненную атрибутами
структуру t e rrni o s , позволяя определить параметры устройства. Те вызовы
i o c t l , у которых нет аналогов в стандарте POSIX, могут требовать ссылки на
другие структуры. Например, для операции K I OC SМAP, загружающей новую рас­
кладку клавиатуры, требуется ссылка на 1 536-байтовую структуру keyrnap_t
( 1 6-разрядные коды для 128 клавиш х 6 модификаторов). То, как вызовы стан­
дарта POSIX преобразуются в системные вызовы i o c t l , показано в табл. 3.9.
У драйвера терминала имеется одна центральная структура данных, t ty_t aЫ e,
представляющая собой массив структур t ty , по одной на терминал. У стан­
дартного персонального компьютера есть только одна клавиатура и экран, но
MINIX 3 поддерживает до восьми виртуальных терминалов, в зависимости от
объема памяти у контроллера. Это позволяет с одной консоли входить в систему
несколько раз, переключая клавиатуру между разными «пользователями�. Если
356 Глава 3 . Ввод-вывод

есть две виртуальные консоли, то нажав клавиши Alt+ F2, можно переключиться
на вторую, а нажатие клавиш Alt+ F1 активизйрует первую консоль. Для пере­
ключения можно также использовать комбинацию клавиши Alt и клавиш управ­
ления курсором. Кроме того, последовательные линии обеспечивают поддерж­
ку двух удаленных пользователей, подключающихся через модем или кабель
RS-232, а псевдотерминалы позволяют пользователям подключаться через сеть.
Драйвер написан так, чтобы можно было легко добавлять новые терминалы.

Табли ца 3 . 9 . РОSIХ-вызовы и операции системного вызова ioctl


РОSIХ-функцин РОS IХ-операцин Тип ioctl Параметр ioctl
Tcdraiп (нет) TCDRAIN (нет)
Tcflow TCOOFF TCFLOW iпt=TCOOFF
Tcflow TCOON TCFLOW iпt=TCOON
Tcflow TCIOFF TCFLOW iпt=TCIOFF
Tcflow TCION TCFLOW int=TCION
Tcflush TCIFLUSH TCFLSH int=TCIFLUSH
Tcflush TCOFLUSH TCFLSH int=TCOFLUSH
Tctlush TCIOFLUSH TCFLSH int=TCIOFLUSH
Tcgetattr (нет) TCGEТS termios
Tcsetattr TCSANOW TCSEТS termios
Tcsetattr TCSADRAIN TCSEТSW termios
Tcsetattr TCSAFLUSH TCSEТSF termios
Tcsendbreak (нет) TCSBRK int=duration
Каждая из структур t ty в массиве t ty_t aЫ e отслеживает как ввод, так и вы­
вод. При вводе она поддерживает очередь символов, которые были введены
пользователем, но еще не считаны, информацию о запросе на чтение символов
и об интервалах таймера, чтобы драйвер не блокировался, если не нажато ни од­
ного символа. При выводе она хранит параметры запросов на вывод, которые
еще не были выполнены. Также есть другие поля с различной общей информа­
цией, например с описанной ранее структурой t e rmi o s , от которой зависят
многие характеристики ввода и вывода. Кроме того, в структуре t ty есть указа­
тель, который ссылается на информацию, необходимую для конкретного класса
устройств, но не требуемую для всех терминалов. Так, завязанной на аппаратное
обеспечение части кода драйвера терминала требуется информация о текущем
положении вывода в видеопамяти, но при работе с линией RS-232 эта информа­
ция не нужна. Дополнительно для каждого типа устройств имеются собственные
структуры данных, содержащие информацию о расположении буферов, в кото­
рые помещают свои данные обработчики прерываний. Медленным устройствам,
таким как клавиатуры, не нужны такие большие буферы, как быстрым.

Терминальный ввод
Чтобы лучше понять, как работает драйвер терминала, сначала рассмотрим, как
напечатанный пользователем символ прокладывает себе путь к ожидающей его
программе. Хотя этот раздел является обзорным, мы дадим читателю ссылки на
3 . 8 . Терми налы 357

строки исходного кода с используемыми функциями. Возможно, изучение кода


покажется вам излишне динамичным, поскольку файлы t ty . с , keyboard . с
и coris o l e . с слишком велики для детального изучения.
Когда пользователь входит с системной консоли, для него создается оболочка,
которая для ввода, вывода и ошибок использует файл / dev / c ons o l e. Запус­
тившись, оболочка пытается считать данные стандартного ввода при помощи
библиотечного вызова r e ad. Эта процедура отправляет файловой системе сооб­
щение, содержащее дескриптор файла, адрес буфера и количество считываемых
байтов . На рис. 3.2 1 это сообщение обозначено цифрой 1 . Отправив сообще­
ние, оболочка блокируется, ожидая ответа. (Пользовательские процессы испол­
няют только примитив s endr e c , который комбинирует вызов s end с вызовом
r e c e i ve от того процесса, которому было послано сообщение.)

Рис . 3 . 2 1 . Запрос на чтение с терминала в отсутствие введенных символов. Драйвер


терминала получает сообщение при каждом нажатии клавиши и ставит в очередь коды
опроса по мере их ввода; затем коды опроса интерпретируются, полученные АSСll­
коды помещаются в буфер, который копируется в пользовательский процесс
Файловая система получает сообщение и определяет местонахождение индекс­
ного узла, соответствующего указанному дескриптору файла. В данном случае
это индексный узел для специального символьного файла / dev / c ons o l e, со­
держащий вспомогательный и основной номера устройства терминала. Основ­
ной номер для терминалов равен 4, вспомогательный номер для консоли равен О.
Файловая система при помощи карты устройств, dmap, определяет номер драй­
вера терминала. Затем она отправляет ему сообщение, которое на рис. 3.2 1 поме­
чено цифрой 2. Обычно к этому моменту пользователь еще ничего не напеча­
тал, и драйвер терминала не в состоянии выполнить запрос. Поэтому драйвер
не.медленно отправляет файловой системе ответ, чтобы разблокировать ее и со­
общить, что ни одного символа пока не введено (цифра З на рисунке). Файловая
система фиксирует в таблице t ty_t aЫ e тот факт, что процесс ожидает ввода
358 Глава 3 . Ввод-вывод

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


этом остается заблокированной, пока не прибудут введенные символы.
Когда, наконец, с клавиатуры вводится символ, генерируются два прерывания,
одно - когда клавиша нажимается, и одно - когда отпускается. Важным является
то, что клавиатура персонального компьютера при нажатии клавиши генерирует
не АSСП-код, а код опроса. При отпускании генерируется еще один код, семь
младших разрядов которого совпадают с кодом опроса. Таким образом, единст­
венное отличие заключается в старшем бите: при нажатии клавиши он равен О,
а при отпускании - 1 . Это же правило относится и к клавишам-модификаторам,
таким как Shift и Ctrl, которые сами по себе не приводят к получению АSСП-ко­
дов, но все равно генерируют два кода опроса, идентифицирующих клавишу
(при желании драйвер может отличить правую клавишу от левой), и вызывают
два прерывания.
Клавиатуре соответствует прерывание I RQ1 . Эта линия прерывания недоступна
через системную шину и не может использоваться другим адаптером ввода­
вывода. Когда функция _hwi nt O l (строка 6535) вызывает функцию int r_
hand l e (строка 822 1 ) , проход по списку обработчиков прерываний быстро вы­
являет, что уведомление должно быть послано драйверу терминала. На рис. 3.2 1
показано, что источником уведомления 4 является системное задание, посколь­
ку его генерирует функция gener i c_handl er из файла sy s t em/ do_i rqc t l . с
(не представлен) . Тем не менее прямой вызов этой функции осуществляют
низкоуровневые процедуры обработки прерывания, а системное задание не акти­
визируется. После получения сообщения HARD_INT функция t ty_t a s k (стро­
ка 1 3740) вызывает функцию kbd_ i n t e r rup t (строка 1 5335 ) , а та, в свою
очередь, - функцию s c an_keyboard (строка 15800). Функция s c an_keyboard
совершает три вызова ядра (5, 6 и 7), чтобы системное задание выполнило чте­
ние и запись в несколько портов ввода-вывода. В результате возвращается код
опроса и помещается в циклический буфер. Затем устанавливается флаг t ty_
event s , показывающий, что буфер не пуст и содержит символы.
Здесь необходимости в использовании сообщений нет. Каждый раз главный
цикл t ty_t ask запускает другой цикл, анализирующий флаг t ty_event s каж­
дого терминала и вызывающий функцию handl e_event s (строка 14358) для
всех терминалов, флаг которых установлен. Флаг t ty_event s может указывать
на разные ситуации (хотя наличие ввода - наиболее вероятная из них), поэтому
функция handl e_event s всегда вызывает специфичные для устройства функции
как в случае ввода, так и в случае вывода. При вводе с клавиатуры вызывается
функция kb_read (строка 1 5360), которая отслеживает наличие кодов управ­
ляющих клавиш Ctrl, Sh ift и Alt и преобразует коды опроса в АSСП-коды. Функ­
ция kb_read, в свою очередь, вызывает функцию in__pro c e s s (строка 14486),
которая обрабатывает АSСП-коды, учитывает специальные символы и значения
различных флагов, включая флаги, задающие канонический режим. В результате
символ, как правило, просто добавляется во входную очередь клавиатуры в t ty_
t аЫ е , хотя некоторые коды, например BACKSPAC E, могут приводить к другому
эффекту. Кроме того, обычно функция in__pro c c e s s осуществляет эхопечать
вводимых ASCII -символов на дисплее.
3 . 8 . Терминалы 359

Получив достаточно символов, драйвер терминала выполняет вызов ядра (8), за­
прашивающий у системного задания копирование данных по адресу, затребован­
ному оболочкой. Эта операция также не является передачей сообщения, поэтому
обозначена на рис. 3.2 1 многоточием (9). На рисунке изображено несколько
линий с цифрой 9, поскольку до того, как пользовательский запрос будет полно­
стью выполнен, данные могут быть переданы несколько раз. Когда операция
полностью завершится, драйвер терминала отправляет файловой системе со­
общение о том, что работа выполнена ( 10), а файловая система в результате от­
правляет сообщение оболочке и выводит ее из состояния блокировки ( 1 1).
Определение количества обрабатываемых символов зависит от режима термина­
ла. В каноническом режиме запрос считается выполненным, когда поступает
символ перевода строки, конца строки или конца файла. Также длина строки не
может превышать величину входной очереди, чтобы входные данные могли пра­
вильно обрабатываться. В неканоническом режиме может запрашиваться гораз­
до большее количество символов, и функции in_proc e s s может потребоваться
передавать данные несколько раз перед тем, как файловой системе будет возвра­
щено сообщение, означающее завершение работы.
Обратите внимание на то, что системное задание копирует данные напрямую из
своего адресного пространства в адресное пространство оболочки. Данные не пе­
редаются через файловую систему. При блочных операциях ввода-вывода ин­
формация сначала поступает файловой системе, чтобы та могла поддерживать
кэш недавно запрошенных блоков. Если запрошенный блок оказывается в кэше,
файловая система может самостоятельно выполнить пользовательский запрос
без реального обращения к диску.
При работе с клавиатурой кэширование не имеет смысла. Кроме того, запрос фай­
ловой системы к драйверу диска всегда может быть обслужен максимум за не­
сколько сотен миллисекунд, поэтому нет большой проблемы в том, что файловой
системе приходится немного подождать. Операции с клавиатурой могут длиться
часами или вообще могут не завершаться (в каноническом режиме драйвер ждет
ввода полной строки, а в неканоническом ждет достаточно длинной строки, в за­
висимости от параметров MIN и T IME) . Поэтому файловая система не должна
блокироваться, ожидая выполнения запросов терминального ввода-вывода.
Вполне вероятно, что пользователь введет какой-либо текст заранее, и символы
благодаря предыдущим прерываниям и событию 4 окажутся доступными до то­
го, как они будут запрошены. В этом случае все события 1, 2 и 5- 1 1 происходят
сразу же после запроса, а события 3 вообще не происходит.
Читатели, знакомые с ранними версиями операционной системы MINIX, возмож­
но, помнят, что в них драйвер терминала и все прочие драйверы были объединены
с ядром. У каждого драйвера был собственный обработчик прерываний в про­
странстве ядра. Обработчик прерываний драйвера клавиатуры осуществлял бу­
феризацию определенного количества кодов опроса и выполнял предваритель­
ную обработку (большую часть кодов, полученных при отпускании клавиши,
можно отбросить, поскольку их буферизация необходима только при нажатии
360 Глава 3 . Ввод-вывод

управляющих клавиш). Сам обработчик не посылал сообщения драйверу тер­


минала, поскольку с большой вероятностью терминал не блокировался вызовом
rec e i ve и не мог получать сообщения в любой момент времени. Вместо этого
обработчик прерывания от таймера периодически активизировал драйвер терми­
нала. Эти методы требовались для того, чтобы избежать потери вводимых с кла­
виатуры данных.
Ранее мы упоминали о различиях в обработке ожидаемых прерываний (напри­
мер, от дискового контроллера) и неожидаемых прерываIJий (например, от кла­
виатуры). Тем не менее складывается ощущение, что в MINIX 3 не предпринято
особых мер для разрешения проблем с неожидаемым:и прерываниями. В чем же
дело? Первое, что следует иметь в виду, - огромная разница в производительно­
сти между компьютерами, для которых создавались первые версии MINIX, и со­
временными системами. Тактовая частота процессоров выросла, а число тактов
на одну команду сократилось. Минимальная рекомендация для процессора при
использовании MINIX 3 - 80386. Даже медленный компьютер 80386 выполняет
команды приблизительно в 20 раз быстрее, чем первые машины IBM РС. Процес­
сор Pentium с тактовой частотой 100 МГц работает в 25 раз быстрее, чем 80386.
Таким образом, скорости современных процессоров вполне достаточно.
Следующий аспект заключается в том, что по компьютерным меркам ввод с кла­
виатуры осуществляется очень медленно. Печатая 1 00 слов в минуту, машини­
стка вводит менее 10 символов в секунду. Даже при высокой скорости печати
драйвер терминала, скорее всего, будет успевать посылать сообщения о прерыва­
нии для каждого введенного символа. Другие устройства способны вводить дан­
ные с гораздо большей скоростью. Так, последовательный порт, подключенный
к модему скоростью 56 Кбит/с, предоставляет данные в 1000, а возможно, и бо­
лее раз интенсивнее, нежели машинистка. В течение такта часов может быть по­
лучено приблизительно 120 символов, однако чтобы обеспечить сжатие данных
в модемной линии, последовательный порт должен поддерживать обработку как
минимум вдвое большего их числа.
Следует иметь в виду, что по последовательному порту передаются не коды опро­
са, а символы, поэтому каждое нажатие клавиши вызывает одно, а не два, прерыва­
ния даже при устаревшем UАRТ-устройстве, не поддерживающем буферизацию.
Более новые компьютеры оснащены приемопередатчиками с буферизацией 16,
а иногда и 128 символов. Таким образом, прерывание для каждого нажатия кла­
виши на самом деле не требуется. К примеру, UART с 1 6-символьным буфером
можно настроить так, что прерывание будет генерироваться при наличии в буфе­
ре 14 символов. Сети Ethernet способны доставлять символы с гораздо большей
скоростью, чем последовательная линия, однако Ethernet-aдaптepы буферизуют
целые пакеты, и генерация прерывания требуется лишь для всего пакета.
В завершение нашего обсуждения терминального ввода подведем итог и рассмот­
рим все события, которые происходят, когда драйвер терминала впервые активи­
зируется по запросу на чтение или когда он повторно получает управление после
3 . 8 . Терминалы 361

клавиатурного ввода (рис. 3.22). В первом случае, когда драйвер терминала полу­
чает сообщение, запрашивающее вводимые с клавиатуры символы, главная про­
цедура, t ty_t a s k (строка 1 3740), вызывает процедуру do_read (строка 13953),
которая и выполняет запрос. Если имеющейся в буфере информации недостаточно
для выполнения запроса, процедура сохраняет параметры запроса в t ty_t aЫ e.

Прием сообщения Прием сообщения


от пользовательского отчасов
процесса

iп_traпsfer

Прочие функции
Обработка ввода драйвером терминала. Левая часть дерева соответствует
Ри с . 3 . 2 2 .
обработке запроса на чтение символов, а правая - передаче сообщения от клавиатуры
драйверу раньше, чем пользователь затребовал ввод
После этого, чтобы получить уже введенные данные, вызывается функция in_
t r ans f er (строка 144 1 6), а затем - функция handl e_events (строка 14358), ко­
торая, в свою очередь, вызывает функцию kb_r ead через указатель * tp - >t ty_
devread (строка 1 5360) и еще раз функцию in_t rans f er, чтобы получить из
входного потока еще несколько символов. Функция kb_r ead несколько раз вы­
зывает другие подпрограммы, не показанные на рис. 3.22. В результате пользова­
телю копируются все доступные данные. Если доступных данных нет, ничего не
копируется. Когда вызов in_t r ans f er или handl e_event s полностью выпол­
няет запрос, файловой системе отправляется сообщение, чтобы она могла раз­
блокировать запросивший данные процесс. Если чтение не завершено (не введе­
но ни одного символа или введено недостаточное количество), функция do_
report информирует об этом файловую систему, чтобы та могла либо заблоки­
ровать вызвавший процесс, либо, если было запрошено неблокирующее чтение,
отменить запрос.
На правой части рис. 3.22 воспроизведены все события, возникающие, когда
драйвер терминала •просыпается» вследствие прерывания от клавиатуры. Когда
напечатан символ, •обработчик» прерываний kbd_int errup t (строка 1 5335) вы­
зывает функцию s c an_k eybo ard, которая обращается к системному заданию за
выполнением ввода-вывода (мы заключили слово •обработчик» в кавычки пото­
му, что функция kЬd_int errupt фактически обработчиком не является. Она вы­
зывается, :когда t ty_t a s k получает сообщение от функции gener i c_handl er
362 Глава 3 . Ввод-вывод

системного задания). Функция kbd_int errupt помещает код опроса в клавиа­


турный буфер ibu f и устанавливает флаг, позволяющий определить, какому из
устройств направлен ввод. Когда kЬd_in t e rrup t возвращает управление t ty_
t ask, команда c ont i nu e инициирует новую итерацию главного цикла. Флаги
событий всех терминалов проверяются, и для терминалов с установленным фла­
гом вызывается функция hand l e_even t . Последняя для клавиатуры вызывает
функции kb_r ead и i n_t r ans f e r , как это делается и при получении исход­
ного запроса на чтение. Отраженные в правой части рис. 3.22 события могут
происходить несколько раз, до тех пор пока не поступит достаточное для выпол­
нения запроса количество символов. Этот запрос поступает к do_re ad после
первого сообщения от файловой системы. Если файловая система пытается ини­
циировать новый запрос до того, как устройство обслужило предыдущий, воз­
вращается ошибка. Естественно, все устройства независимы; запрос на чтение
с удаленного терминала обрабатывается отдельно от запроса чтения с клавиатуры.
На рис. 3.22 не показаны функции, вызываемые функцией kb_r ead: функция
map_key (строка 1 5303) преобразует в АSСП-коды коды клавиш (коды опроса),
генерируемые аппаратным обеспечением; функция mak e_break (строка 1 543 1 )
отслеживает состояние клавиш-модификаторов (таких как Shift); функция i n_
proc e s s (строка 14486) ответственна за такие возможности, как удаление не­
правильно введенного символа, обработка различных специальных символов
и управление параметрами терминала. Кроме того, i n_proc e s s вызывает функ­
цию t ty_echo (строка 14647), чтобы вводимые символы отображались на тер­
минале.

Терминальный вывод
Вообще говоря, консольный вывод проще ввода, так как здесь действиями управ­
ляет операционная система, и не нужно бояться, что события произойдут в не­
подходящее время. Более того, процесс еще больше упрощается за счет того, что
в MINI X 3 консолью является отображаемый на память экран. Прерывания
не нужны, вывод производится путем копирования данных из одной области
памяти в другую. Однако в этом случае все операции по управлению экраном,
включая обработку управляющих последовательностей, ложатся на программ­
ное обеспечение драйвера. Как и при изучении ввода в предыдущем разделе,
мы проследим, какие действия происходят при выводе символа на консольный
дисплей. В этом примере предполагается, что вывод производится на актив­
ный дисплей. Небольшое усложнение, вносимое виртуальными консолями, мы
обсудим позже.
Обычно, когда процесс хочет что-нибудь напечатать, он вызывает функцию
p r i n t f . Эта функция делает системный вызов wr i t e, который отправляет фай­
ловой системе сообщение с указателем на выводимые символы (но не сами
символы) . Затем файловая система посылает сообщение драйверу терминала,
а тот извлекает указанные ему символы и копирует их в видеопамять. Основ ­
ные подпрограммы, из которых складывается терминальный вывод, показаны
на рис. 3.23.
3 . 8 . Терминалы 363

tty_task

do_write

haпdle_eveпts

,
,

,
,
1

«Простые» ,' 1
/ Escape- Специальные
символы /
1
последовательности Конец строки

'
'
'
'
'

Основные подпрограммы терминального вывода. Штриховой линией обозначено


Рис. 3 . 2 3 .
прямое копирование символов в буфер ra mqueue подпрограммой coпs_write
Когда драйвер терминала получает сообщение, требующее от него вывести что­
либо на экран, вызывается подпрограмма do_wr i t e (строка 14029), которая со­
храняет параметры в массиве t ty_t aЫ e структуры t ty, соответствующей вы­
бранной консоли. Затем вызывается функция handl e_event (та же функция,
которая вызывается при обнаружении установленного флага t ty_event s ) . Эта
подпрограмма при каждом вызове выполняет для указанного устройства как за­
пись, так и чтение. В случае с консолью это означает, что сначала будут обрабо­
таны еще не принятые клавиатурные данные. Если необработанного ввода нет,
переданные символы добавляются к другим символам, ожидающим вывода.
Затем вызывается процедура c ons_wr i t e (строка 1 6036), выполняющая вывод
для отображаемых на память экранов. Последняя использует подпрограмму
phy s_c opy, чтобы скопировать блок символов от пользовательского процесса
в локальный буфер, причем, возможно, это действие будет повторено несколько
раз, так как локальный буфер вмещает только 64 байта. Когда буфер укомплек­
товывается, каждый 8-разрядный байт переносится в другой буфер, r arnqueue,
представляющий собой массив 1 6-разрядных слов. Дополнительные байты за­
полняются текущим значением байта атрибутов, который определяет цвет сим­
волов, цвет фона и некоторые другие параметры текста. Когда это возможно,
символы копируются напрямую в буфер rarnqueue, но некоторые символы, на­
пример управляющие символы, образующие ЕSС-последовательности, необходимо
364 Глава 3. Ввод- вывод

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


позиция символа превышает ширину экрана или буфер r arnqu eu e заполнен.
В этом случае, чтобы передать символы и выполнить необходимые дополнитель­
ные действия, вызывается функция ou t_char (строка 1 6 1 19). Так, каждый раз
когда на последней строке экрана принимается символ перевода строки, делается
вызов подпрограммы s c r o l l_s c r een (строка 1 6205), а p a r s e_e s c ap e при­
нимает символы, образующие ЕSС-последовательность. Обычно out_char вы­
зывает подпрограмму f lush (строка 1 6259), которая копирует r arnqueue в ви­
деопамять при помощи ассемблерной подпрограммы rnern_v i d_c opy. Функция
f l u s h также вызывается после того, как в r arnqu e u e передается последний
символ, чтобы гарантировать, что отображены все символы. Конечный результат
работы f l ush - команда, предаваемая микросхеме видеоконтроллера 6845 с це­
лью отобразить в нужном месте курсор.
Байты, поступающие от пользовательского процесса, было бы логично выводить
по одному, в цикле. Тем не менее для систем класса Pentium с защитой памяти
более эффективно предварительно накапливать их в буфере rarnqueue, а затем
копировать весь блок вызовом rnern_v i d_c opy. Интересно, что этот прием при­
менялся в ранних версиях MINIX еще тогда, когда использовались более старые
процессоры, не поддерживающие защищенного режима. Предшественник rnern_
v i d_copy решал другую проблему - проблему синхронизации. Дело в том, что
у старых экранов запись в видеопамять необходимо было производить при очист­
ке экрана, во время вертикального обратного хода луча, чтобы избежать появления
на экране 4Мусора�. Сейчас такое устаревшее оборудование не поддерживается
MINIX 3, так как это приводит к слишком большим потерям производительно­
сти. Тем не менее сама техника копирования целого блока из r arnqueue приме­
няется и поныне, хотя и для другой цели.
Доступная консоли область видеопамяти задается полями c_s t a r t и c_l irni t
структуры cons o l e. Текущее положение курсора хранится в полях c_c o l urnn
и c_row. Координатам ( 0 , 0 ) соответствует верхний левый угол экрана, с это­
го места аппаратное обеспечение начинает заполнять экран. Изображение на­
чинается с адреса, задаваемого значением c_o rg, и занимает 80 х 25 символов
(4000 байт). Другими словами, микросхема 6845 извлекает из видеопамяти сло­
во по адресу со смещением c_o rg и отображает в левом верхнем углу байт сим­
вола, а второй байт исnользует для задания цветов, мигания и прочих атрибутов.
Затем чип извлекает из памяти следующее двухбайтовое слово и отображает
символ в позиции ( 1 , О ) . Этот процесс продолжается до тех пор, пока не дос­
тигнута позиция (79, О), после чего начинается рисование второй строки экрана,
начиная с координат (О, 1 ).
При включении компьютера экран очищается, и данные записываются в видео­
память, начиная с положения c_s t ar t , а c_org присваивается то же значение,
что и c_s t art . Таким образом, первая строка текста выводится в верхней строке
экрана. Когда вывод переходит на следующую строку в результате либо прихода
символа перевода строки, либо заполнения верхней строки экрана, новые симво­
лы помещаются по адресу, равному c_s t ar t плюс 80. Со временем все 25 строк
3 . 8 . Терминалы 365

э:крана оказываются заполненными и требуется прокрутка экрана. Некоторым


программам, например текстовым редакторам, требуется возможность прокручи­
вать экран и в обратном направлении, когда курсор находится в верхней строке
э:крана и нужно перейти выше по тексту.
Существует два подхода к решению задачи прокрутки экрана. Когда применяет­
ся проzра.ммная прокрутка, символу с координатами (О, О) всегда соответствует
один и тот же адрес видеопамяти, с нулевым смещением относительно c_s t art.
Видеоконтроллер всегда выводит этот символ на одном месте благодаря тому,
что в c_org хранится один и тот же адрес. Когда необходимо выполнить про­
крутку, область видеопамяти, начинающаяся со второй строки экрана ( смеще­
ние 80 относительно c_s t art), копируется в начало занимаемой консолью облас­
ти (смещение О). Положение самой занимаемой экраном области памяти не ме­
няется. В результате содержимое экрана перемещается на одну строку вверх. Эта
операция занимает время, необходимое процессору для сдвига 80 х 24 1920 слов
=

памяти. При аппаратной прокрутке данные никуда не перемещаются. Вместо


этого контроллер получает команду выводить данные с другого адреса, например
со слова 80. Для этого новое значение записывается в c_org, чтобы его можно
было использовать в дальнейшем, а также копируется в нужный регистр видео­
контроллера. Такая схема работает, если контроллер достаточно интеллектуален
и при достижении конца видеопамяти (адрес в c_l irni t ) переходит на ее начало
(адрес в c_s t ar t ), или же при такой емкости видеопамяти, когда в ней помеща­
ется более одного экрана, то есть 80 х 2000 слов.
У старых адаптеров дисплея обычно была небольшая память, зато они умели
переходить на начало памяти, достигнув конца, и благодаря этому поддерживали
аппаратную прокрутку. У современных контроллеров памяти обычно больше,
чем необходимо для хранения одного экрана, но перескакивать на начало они не
научены. Так, контроллер с видеопамятью объемом 32 768 байт может хранить
в памяти 204 полные строки по 1 60 байт каждая и 1 79 раз выполнить аппарат­
ную прокрутку, прежде чем невозможность перехода в начало станет проблемой.
Затем потребуется копирование области памяти с целью переместить данные
с последних 24 строк в начало видеобуфера. При любом методе нижняя строка
экрана заполн;яется пробелами, чтобы гарантировать ее очистку.
При конфигурировании виртуальных консолей доступная видеопамять поровну
делится между всеми консолями, для чего соответствующим образом инициали­
зируются поля c_s t art и c_l irni t каждой консоли. Это оказывает влияние на
прокрутку. Любому видеоконтроллеру, обладающему достаточно большой для
поддержки нескольких виртуальных консолей памятью, время от времени необ­
ходима программная прокрутка, хотя номинально поддерживается аппаратная.
Чем меньший объем памяти доступен каждому виртуальному дисплею, тем чаще
требуется программная прокрутка. Предел достигается, когда создается макси­
мально возможное количество виртуальных консолей. При этом каждая про­
крутка оказывается программной.
Положение курсора относительно начала видеопамяти можно вычислить, исхо­
дя из значения в полях c_c o l urnn и c_row, но быстрее хранить его в явном виде
366 Глава 3. Ввод- вы вод

( c_cur ) . Когда на экран выводится символ, он записывается в память по адресу


c_cur, который после вывода обновляется, как и значение в поле c_c o lurnn.
Все поля структуры cons o l e, оказывающие влияние на текущее положение вы­
вода и начало экрана в памяти, перечислены в табл. 3 . 1 О.

Таблица 3 . 1 О. Поля структуры coпsole, связанные с текущей позицией на экране


Поле Значение
c_start Начало видеопамяти для консоли
c_limit Предел видеопамяти для консоли
c_columп Номер текущего столбца (0-79), считая слева
c_row Номер текущей строки (0-24), считая сверху
c_cur Смещение курсора в видеопамяти
c_org Область памяти, отводимая под экран. В микросхеме 6845 эта область
адресуется регистром базы
При обработке символов, изменяющих текущую позицию (то есть символов
перевода строки, забоя и т. д.), соответственным образом изменяются значения
полей c_c o l urnn , c_row и c_cur. Это делается в конце кода процедуры f l ush,
где вызывается функция s e t_6 8 4 5 , которая устанавливает регистры видео­
контроллера.
Драйвер терминала поддерживает ЕS С-последовательности, то есть гибкие воз­
можности управления экраном для редакторов и прочих интерактивных про­
грамм. Поддерживаемые последовательности являются подмножеством стандарта
ANSI и достаточны для простого переноса на MINIX 3 большинства программ,
написанных для другого аппаратного обеспечения и других операционных систем.
Есть две категории ЕSС-последовательностей: те, которые никогда не имеют па­
раметров, и те, которые могут содержать варьируемые данные. Единственный
представитель первой категории, реализованный в MINIX 3, это последователь­
ность ESC м, выполняющая обратный перевод строки. При этом курсор подни­
мается на одну строку вверх, а если курсор уже находится на верхней строке,
экран прокручивается на строку вниз. У последовательностей второй категории
могут быть один или два численных параметра. Все такие цепочки символов на­
чинаются с символов ESC [ . Символ [ является преамбулой управляющей после­
довательности. Список последовательностей, описываемых стандартом ANSI
и поддерживаемых MINIX 3, приведен в таблице 3.8.
Анализ управляющих последовательностей не так прост. В MINIX 3 корректные
последовательности могут содержать от двух символов, как E S C м, до 8, как
последовательности с двумя двухразрядными параметрами. Например, последо­
вательность ESC [ 2 О ; 6 ОН перемещает курсор на строку 20, в столбце 60. Если
параметр один, его можно опускать, при наличии двух параметров позволено
опустить оба. Когда параметр не указывается или указывается значение, превы­
шающее граничное, используется значение по умолчанию, которое равно наи­
меньшему допустимому значению.
3 . 8 . Терминалы 367

Посмотрим, какие ЕSС-последовательности позволяют переместить курсор в верх­


ний левый угол экрана.
+ Последовательность ESC [ Н. Так как оба параметра опущены, берутся наи­
меньшие значения.
+ Последовательность E S C [ 1 ; l H переместит курсор в строку 1 , столбец 1
(в ANSI нумерация столбцов и строк начинается с единицы).
+ Опустив только один из параметров, можно получить последовательности
ESC [ 1 ; н и ESC [ ; lH. Отсутствующий параметр по умолчанию будет счи­
таться равным 1 .
+ К тому же результату приведет последовательность E S C [ О ; О Н, так как в ней
оба параметра меньше минимально допустимой величины. Они будут замене­
ны значениями по умолчанию.
Эти примеры не нужно рассматривать как рекомендацию специально использо­
вать некорректные значения параметров; их следует расценивать как иллюстра­
цию того, что интерпретирующий эти последовательности код нетривиален.
Для разбора ЕSС-последовательностей в MINIX 3 реализован конечный авто­
мат. Переменная состояния этого автомата c_e s c_s t a t e в структуре cons o l e
обычно имеет значение О . Когда функция out_char обнаруживает символ ESC,
она устанавливает переменную состояния c_e s c_s t a t e в 1 , и последующие
символы интерпретируются функцией p a r s e_e s c ap e (строка 1 6293). Если сле­
дующий полученный символ является преамбулой Е SС-последовательности,
выполняется переход в состояние 2 ; в противном случае последовательность
считается завершенной и вызывается функция do_e s c ap e (строка 1 6352). В со­
стоянии 2, пока на вход поступают цифровые символы, вычисляется значение
числовых параметров. А именно: предыдущее значение (которое изначально рав­
но О) умножается на 1 0, и к нему добавляется значение полученного числового
символа. Значения параметров хранятся в массиве, и когда поступает символ
точки с запятой, осуществляется переход к следующей ячейке массива. В MINIX 3
этот массив содержит всего два элемента, но принцип тот же. Когда полученный
символ оказывается нечисловым и не точкой с запятой, последовательность счи­
тается завершенной и снова вызывается do_e s c ape. Символ, являющийся теку­
щим на момент обращения к do_e s c ap e , используется для того, чтобы опреде­
лить, какое именно действие необходимо выполнить и как интерпретировать
параметры, будь то значения по умолчанию или считанные из потока симво­
лов данные. Подробнее этот конечный автомат рассматривается в пункте 3.8.6.

П од г ружаемые раскладки клавиатуры


Клавиаtура I B M РС не генерирует АSСП -коды напрямую. Клавиши иденти­
фицируются по их номерам, начиная с клавиш в верхнем ряду оригинальной
клавиатуры IBM РС. Соответственно, клавиша Esc имеет номер 1, клавиша 1 -

номер 2 и т. д. Номера присвоены всем клавишам, включая клавиши-модифика­


торы, такие как левая и правая клавиши Shift, которые соответственно имеют но­
мера 42 и 54. Когда нажимается клавиша, MINIX 3 извлекает ее номер из кода
опроса клавиатуры. Код опроса генерируется и в том случае, когда клавиша
368 Глава 3 Ввод- вывод

отпускается, но здесь устанавливается старший значащий бит кода (что эквива­


лентно добавлению числа 1 28 к номеру) Благодаря этому операционная система
может различать, была клавиша нажата или отпущена Поскольку система от­
слеживает состояние клавиш-модификаторов, возможно множество комбина­
ций Для большинства целей достаточно двухклавишных сочетаний, таких как
Shift+A или Ctrl+ D, удобных для тех, кто при печати привык пользоваться обеими
руками, но в некоторых специальных случаях могут применяться комбинации из
трех (и более) клавиш, например Ctrl+Sh ift+A или Ctrl+Alt+Del (последняя ис­
пользуется в РС для сброса системы)
Сложность клавиатуры РС обеспечивает гибкость ее использования В стандарт­
ном наборе присутствуют 47 клавиш с типовыми символами (26 алфавитных
символов, 10 цифр и 1 1 знаков препинания) Если мы будем поддерживать ком­
бинации из трех клавиш-модификаторов, таких как Ctrl+Alt+Shift, получается 376
(8 х 47) вариантов Это ни коим образом не предел возможного, но сейчас мы
не будем акцентировать внимание на различиях между левыми и правыми кла­
вишами-модификаторами, а также учитывать функциональные клавиши или
клавиши цифровой клавиатуры Конечно, никто не заставляет нас использовать
в качестве модификаторов только клавиши Ctrl, Sh1ft и Alt Мы вправе исключить
какую-либо клавишу из стандартного набора и использовать ее в качестве моди­
фикатора, для чего нужно написать драйвер, поддерживающий такую систему
Чтобы определить, какой код передать программе при нажатии определенной
клавиши, операционные системы, работающие с подобными клавиатурами, при­
меняют рас'Кllадки Юlавиатуры, или карты Юlавиш В MINIX 3 типичная карта
клавиш логически является массивом, имеющим 1 28 строк для каждого кода
опроса (такой размер был выбран для поддержания японских клавиатур, у евро­
пейских и американских клавиатур нет такого числа клавиш) и 6 столбцов
Столбцы сопоставлены следующим состояниям нет модификаторов, нажата
клавиша Shift, нажата клавиша Ctrl, нажата левая клавиша Alt, нажата правая кла­
виша Alt, нажата комбинация Alt+S h ift Такая схема при соответствующей кла­
виатуре позволяет генерировать ( 1 28 - 6) х 6 720 различных кодов символов
=

Конечно, каждая запись в таблице должна содержать 1 6-разрядное значение


У американских клавиатур столбцы для левой и правой клавиш Alt идентичны
Для других языков правая клавиша Alt называется Altgr, и на клавишах многих из
подобных клавиатур нарисован третий символ, для ввода которого и использу­
ется этот модификатор
Стандартная карта клавиш встроена в ядро MINIX 3 и в файле keyboard с оп­
ределяется строкой
# inc l ude keymap s / u s - s t d s r c

Для загрузки альтернативной раскладки по адресу keymap используется систем­


ный вызов i o c t l следующего вида
i oc t l ( O , K I OC SМAP , keymap )

Полная раскладка занимает 1 536 байт ( 1 28 х 6 х 2 ) Дополнительные расклад­


ки хранятся в сжатой форме Чтобы создать новую сжатую карту, запускается
3 . 8 . Терминалы 369

программа genmap. При компиляции в genmap включается код keymap . s rc для


определенной раскладки. Обычно программа genmap запускается сразу же после
компиляции и записывает уплотненную версию в файл, после чего исполняемый
файл программы удаляется. Команда l o adkey s загружает указанную карту кла­
виш, выполняет декомпрессию и делает системный вызов i o c t l , передавая рас­
кладку в память ядра. MINIX 3 может автоматически вызывать команду loadkeys
при старте. Кроме того, ее в любой момент может вызвать пользователь.
Исходный код раскладки описывает большой инициализированный массив .
С целью экономии места мы исключили файл карты клавиш из файлов, пред­
ставленных на с0провождающем книгу компакт-диске. В табл. 3. 1 1 показано
содержимое нескольких строк файла s rc / kerne l / keymap s / us - s t d . s rc. На
клавиатурах IBM РС нет клавиши, генерирующей код опроса О. Коду 1 соответ­
ствует клавиша Esc. Из первой записи в таблице видно, что возвращаемый код
для этой клавиши не зависит от состояния модификатора Shift или Ctrl, но если
удерживается клавиша Alt, при нажатии клавиши Esc генерируется другой код.

Таблица 3. 1 1 . Несколько записей из файла раскладки клавиатуры


Код Символ Без Shift Л евая П равая Alt+Shift Ctrl
опроса модификации клавиша Alt клавиша Alt
00 Нет о о о о о о
01 ESC С( ' [ ' ) С( ' [' ) СА( ' [' ) СА( ' [ ' ) СА( ' [" ) с ( ' [' )
02 '1 ' '1 ' '!' А( ' 1 ' ) А( ' 1 ' ) А( '!' ) С( 'А' )
13 '=' '=' · +· А( '=' ) А( '=' ) А( '+' ) С( ' @ ' )
16 'q ' L( 'q ' ) 'Q' A( 'q ' ) A( ' q ' ) A( 'Q' ) C( 'Q' )
28 CR/LF С( ' М ' ) С( ' М ' ) СА( ' М ' ) СА( ' М ' ) СА( ' М ' ) C( 'J ' )
29 CTRL CTRL CTRL CTRL CTRL CTRL CTRL
59 F1 F1 SF1 AF1 AF1 ASF1 CF1
1 27 ??? о о о о о о

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


файла inc lude / m i n i x / keymap . h:
# de f ine С (с} ( ( с ) ) & Oxl F ) / * Преобра з о вание в управляющий код * /
ifde f ine А(с) ( ( с ) ) 1 Ох 8 0 ) / * Ус танавливается во сьмой бит ( Al t ) * /
# de f ine СА ( с } А(С (с} ) / * Ctrl +Alt * /
# d e f ine L (c) ( ( с ) 1 НASCAP S ) / * Добавляется признак Caps Lock * /

Первые три макроса манипулируют битами переданного символа, вырабатывая


нужный код. Последний устанавливает бит HAS CAPS в старmем байте 1 6 -раз­
рядного кода. Этот флаг указывает на необходимость проверки, фиксируется ли
верхний регистр букв (клавиша Caps Lock), и возможную модификацию кода.
В табл. 3. 1 1 записи для кодов опроса 2, 13 и 16 иллюстрируют, как обрабаты­
ваются типичные клавиши с цифровыми, алфавитными символами и знаками
препинания. У кода 28 можно видеть одну особенность. Обычно клавиша Enter
порождает код CR ( OxOD ) , который записывается здесь как с ( м ) . Так как ' '

в UNIX символом новой строки является код LF (ОхОА) и иногда его необходимо
370 Глава 3. Ввод- вывод

вводить напрямую, комбинация Ctrl+ Enter обрабатывается особым образом и про­


изводит код с ( J ) . 1 1

Код опроса 29 - это один из кодов модификаторов, который должен распознавать­


ся всегда, поэтому независимо от состояния прочих модификаторов возвращается
значение CTRL. Функциональные клавиши не генерируют обычных АSСП-сим­
волов, и строка таблицы для кода опроса 59 содержит символьную запись значе­
ний, возвращаемых при нажатии клавиши F1 в комбинации с различными мо­
дификаторами. Это следующие коды: Fl (Ох0 1 10), S F l (Ох 1 0 1 0), AF l (Ох08 1 0),
AS F l (ОхО С 1 0), CFl (Ох02 10). Последняя запись в таблице соответствует коду
опроса 127 - типичному для конца массива маркеру. У большинства клавиатур,
используемых в Европе и Америке, недостаточно клавиш, чтобы генерировать
такие коды, поэтому соответствующие записи в таблице заполняются нулями.

Заг ружаемые шрифты


В ранних персональных компьютерах шаблоны, по которым генерировались изо­
бражения символов на экране, хранились только в ПЗУ. Теперь у контроллеров,
которыми оснащаются современные системы, образы символов можно загружать
в оперативную память. В MINIX 3 эта возможность поддерживается с помощью
системного вызова i o c t l следующего вида:
i o c t l ( O , T I OC FON , font )

MINIX 3 поддерживает видеорежим 80 столбцов на 25 строк, и файлы со шриф­


тами содержат 4096 байт. Каждый байт такого файла управляет отрезком из
восьми пикселов, при этом подсвеченному пикселу соответствует 1 , погашен­
ному - О. Для генерации символа необходимо 16 таких отрезков. Тем не ме­
нее контроллер дисплея для хранения изображения каждого символа отводит
32 байта, чтобы работать при более высоком разрешении, не поддерживаемом
сейчас MINIX 3. Для того чтобы преобразовать такой файл в 8 1 92-байтовую
структуру f ont , адрес которой передается в системный вызов i o c t l , предна­
значена команда l oadfont . Как и карты клавиш, шрифты могут загружаться
при запуске системы или же позднее, в любой момент нормальной работы. Одна­
ко загружать шрифт не обязательно, у каждого видеоадаптера в ПЗУ имеется
встроенный шрифт, доступный по умолчанию. Встраивать шрифт в саму систе­
му нет необходимости, поэтому единственное, что присутствует в ядре для под­
держки загружаемых шрифтов, - это код, выполняющий операцию T I OC S FON
системного вызова i o c t l .

3 . 8 . 4 . Реал изация ап паратно- независимого


драйвера терминала
В этом разделе мы начнем подробное изучение кода драйвера терминала. Рассмат­
ривая блочные устройства, мы видели, что драйверы, обслуживающие несколько
различных устройств, могут иметь общий базовый код. В случае драйвера тер­
минала ситуация примерно та же, с той разницей, что один драйвер поддержи­
вает несколько типов терминальных устройств. Мы начнем изучение с кода, не
3.8. Те рмин алы 37 1

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


с клавиатурой и отображаемым на память экраном.

Структуры данных драйвера тер м инала


В файле t ty . h содержатся определения, используемые в коде драйверов терми­
нала. Так как драйвер поддерживает большое число различных устройств, при
вызове необходимо различать эти устройства. Для этого используются вспомога­
тельные номера устройств, определенные в строках 13405 - 1 3409.
Определения макросов O_NOC TTY и O_NONBLOCK (значения необязательных
флагов для системного вызова open ) в файле t ty . h повторяют определения,
имеющиеся в файле inc l ude / f cnt l . h. Это сделано с той целью, чтобы не при­
соединять другие заголовочные файлы. Типы devfun_t и devfunarg_t (стро­
ки 1 3423 и 1 3424) применяются для передачи указателей на функции, которые
используются в механизмах косвенного вызова для главного цикла дисковых
драйверов, аналогичных рассмотренным нами ранее.
Большую часть объявленных в этом файле переменных можно идентифициро­
вать по префиксу t ty_. Самое главное описание в файле t ty . h - это структура
t ty (строки 1 3426- 13488). Для каждого терминального устройства, рассматри­
ваемого в целом (консольный дисплей и клавиатура образуют один терминал),
существует одна такая структура. Первое ее поле, t ty_event s , представляет со­
бой флаг, который устанавливается в результате прерывания по факту измене­
ний, требующих от драйвера терминала уделить внимание устройству.
Остальные поля структуры t ty объединены в группы, связанные с операциями
ввода и вывода, состоянием устройства и информацией о незавершенных опера­
циях. В секции операций ввода первая пара переменных, t ty_inhead и t ty_
int a i l , задают очередь, в которой буферизуются принимаемые символы. Поле
t ty_incount является счетчиком количества символов в этой очереди, а в поле
t ty_eo t c t подсчитываются строки символов. Все вызовы, зависящие от кон­
кретного устройства, являются косвенными, за исключением вызовов процедур
инициализации терминала, которые и устанавливают указатели для косвенных
вызовов. Указатели на специфичный для данного терминала код хранятся в по­
лях t ty_devread и t ty_i canc e l . Это - соответственно указатели на функ­
цию чтения данных и функцию отмены чтения. Значение поля t ty_rnin сравни­
вается с t ty_e o t c t . Когда значение t ty_eot c t становится равно значению
t ty_rnin, операция чтения считается завершенной. При каноническом вводе
t ty_rni n равно 1 , а в t ty_eot c t подсчитываются введенные строки. При нека­
ноническом вводе в t ty_eot c t подсчитываются символы, а t ty_rnin присваи­
вается значение из поля MIN структуры t e rrni o s . Таким образом, сравнение
этих двух полей позволяет определить, когда завершен ввод строки или получе­
но минимально необходимое количество символов (в зависимости от режима).
Поле t ty_trnr является таймером данного терминального устройства, исполь­
зуемым для поля TI ME структуры t e rrni o s .
Поскольку постановка данных в очередь вывода выполняется по-разному для раз­
ных устройств, секция вывода структуры t ty не содержит переменных, а состоит
372 Глава 3. Ввод- вывод

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


жащие для вывода, эхопечати, отправки сигнала прерывания и отмены вывода.
В секции состояния поля t ty_rep r i n t , t ty_e s c aped и t ty_i nhi Ь i t ed слу­
жат флагами, индицирующими, что последний полученный символ несет специ­
альную нагрузку. Так, когда получен символ LNEXT (Ctrl+V), флаг t ty_e s caped
устанавливается в 1, чтобы обозначить, что любое особое значение следующего
полученного символа необходимо игнорировать.
Следующая часть структуры хранит информацию о текущих операциях DEV_
READ, DEV_WRI TE и DEV_I OCTL. Каждая из этих операций затрагивает два про­
цесса. Обслуживающий системный вызов сервер (обычно - файловая система)
идентифицируется полем t ty_inca l l er (строка 13458). Сервер вызывает драй­
вер терминала от лица другого процесса, заинтересованного в операции ввода­
вывода, и этот процесс идентифицируется полем t ty_inproc (строка 1 3459).
Как показано на рис. 3.2 1 , при выполнении вызова read символы копируются
напрямую в приемный буфер в адресном пространстве сделавшего вызов про­
цесса. Положение буфера задается полями t ty_inp r oc и t ty_i n_vi r. Две
следующие переменные, t ty_in l e f t и t ty_incum, фиксируют число симво­
лов, которое еще нужно и которое уже получено. Аналогичный набор перемен­
ных предусмотрен и для системного вызова wr i t e. При выполнении вызова
i o c t l может потребоваться немедленная передача данных между запрашиваю­
щим процессом и драйвером, для чего нужен виртуальный адрес, но информа­
цию о состоянии этой операции сохранять не надо. К примеру, возможна задерж­
ка обслуживания i o c t l до окончания текущего вывода, однако затем запрос
выполняется за одну операцию.
Наконец, структура t ty содержит еще несколько переменных, которые не попа­
дают ни в какую другую категорию. Это - указатели на функции, выполняющие
операции DEV_I OCTL и DEV_CLOSE на уровне устройства, структура t e rmi o s ,
описанная в POSIX, и структура wins i z e, обеспечивающая поддержку экранов
с оконным интерфейсом. В последней части структуры зарезервировано место
для самой входной очереди в массиве t ty_i nbu f . Обратите внимание, что
это массив значений типа u l б_t , а не 8-разрядных символов char. Хотя при­
ложения и устройства используют для представления символов 8-разрядные
коды, язык С ужесточает требования, считая, что функция get char должна воз­
вращать данные более длинного типа, чтобы помимо 256 допустимых значений
байта она могла возвращать код EOF.
Массив t ty_t aЫ e, состоящий из структур t ty, объявлен со спецификатором
ext e rn (строка 1349 1 ) . Для каждого терминала в этом массиве резервируется
по одному элементу, а количество элементов определяется константами NR_
CONS , NR_RS_LINES и NR_PTYS, заданными в файле inc lude /minix / con f i g . h.
В обсуждаемую в книге конфигурацию включены две консоли, но MINIX можно
перекомпилировать, обеспечив поддержку до двух последовательных интерфей­
сов и до 64 псевдотерминалов.
В файле t ty . h есть еще одно внешнее ( ext ern ) определение. Переменная t ty_
t imers (строка 135 16) представляет собой указатель, который ·используется
3 . 8 . Терми налы 373

таймером для поддержания связанного списка полей t irne r_t . Файл t ty . h


включается во многие другие файлы, и область памяти для t ty_t aЫ e и t ty_
t irne r s резервируется при компиляции t ty . с .
В конце файла находится текст двух макросов, bu f l en и bu f end (строки 13520
и 1352 1 ) . Эти макроопределения многократно используются в коде драйвера
терминала, выполняющего много операций копирования в буферы и из них.

Н езависимый от устройств драйвер терминала


Основные функции драйвера терминала и все аппаратно-независимые вспомога­
тельные функции сконцентрированы в файле t ty . с. Далее следуют еще не­
сколько макроопределений. Если устройство не инициализировано, указатели
на специфические для данного устройства функции содержат нулевые значения,
которые записываются компилятором. То есть можно создать макрос t ty_
a c t ive (строка 1 3687), который возвращает FAL S E в случае, когда обнаружи­
вается нулевой указатель. Естественно, код инициализации устройства нельзя
вызвать косвенно, собственно, одна из задач такого кода - инициализация ука­
зателей, которые своим присутствием делают возможными косвенные вызовы.
Строки 1 3690 - 13696 содержат условные макроопределения, заменяющие адреса
функций инициализации терминала RS-232 или псевдотерминала нулевыми
адресами, если соответствующие устройства не включены в конфигурацию. Это
позволяет полностью пропустить код для означенных устройств, когда в дейст­
вительности он не нужен.
Так как терминал настраивается множеством параметров, а в сетевой системе
терминалов несколько, декларируется и заполняется значениями по умолчанию
структура t e rrni o s_de f au l t s (строки 1 3720- 13727). Сами значения задаются
в файле / inc l ude / t e rrni o s . h. Эта структура копируется в запись t ty_t aЫ e
при инициализации или повторной инициализации терминала. Значения по умол­
чанию для специальных символов были показаны в табл. 3.6. В табл. 3. 1 2 пред­
ставлены значения по умолчанию для различных флагов. Далее в коде следуют
строки, где аналогичным образом объявляется структура win s i z e_de fau l t s.
Ее поля инициализируются нулями компилятором С; это подразумевает, что раз­
мер окна неизвестен и нужно использовать файл / e t c / t errncap.

Табли ца 3 . 1 2 . Значения по умолчанию для флагов структуры termios


Поле Значение по умолчанию
c_iflag BRl<INT ICRNL IXON IXANY
c_oflag OPOST ONLCR
c_cflag CREAD CS8 HUPCL
c_lflag ISIG IEXТERN ICANON ЕСНО ЕСНОЕ
Последним набором определений перед началом исходного кода являются об­
щедоступные ( puЬ l i c ) глобальные переменные, ранее объявленные внешними
( ext e rn ) в файле t ty . h (строки 1373 1 - 13735).
374 Глава 3. Ввод- вывод

Точкой входа для драйвера терминала является функция t ty_task (строка 137 40).
Перед тем как начать свой главный цикл, она вызывает функцию t ty_i n i t
(строка 13752). Информация о главном компьютере, необходимая для инициа­
лизации клавиатуры и консоли, предоставляется вызовом sys_getrna c h i ne
ядра, а затем выполняется сама инициализация при помощи подпрограммы kb_
ini t_once. Такое имя было выбрано, чтобы отличить эту подпрограмму от дру­
гой подпрограммы инициализации, вызываемой позднее для каждой виртуаль­
ной консоли. Наконец, на вывод посылается один символ О, чтобы проверить вывод
и исключить неинициализированные устройства раньше, чем будут предприня­
ты первые попытки их использования. В коде вызывается функция print f , но
она отличается от одноименной функции, вызываемой обычными программами.
Она вызывает локальную функцию putk драйвера консоли.
Главный цикл (строки 1 3764 - 1 3876) почти аналогичен главному циклу любого
другого драйвера. Принимается сообщение, при помощи инструкции swi t ch
для каждого типа сообщения вызывается соответствующая функция обработки,
после чего генерируется ответное сообщение. Однако есть и отличия. Первое со­
стоит в том, что с момента последнего прерывания могут накопиться считанные
или выведенные на запись символы. Поэтому, прежде чем пытаться получить со­
общение, главный цикл проверяет флаги tp - > t ty_event s для всех термина­
лов. Если необходимо позаботиться о незавершенных делах, вызывается функ­
ция handl e_event s, и только если не найдено ни одного требующего внимания
терминала, делается вызов rece ive.
Диаграмма с типами сообщений, представленная в комментариях в начале файла
t ty . с, содержит лишь наиболее распространенные типы. В ней отсутствуют мно­
гие типы, требующие особой обработки драйвером терминала. Те же, которые на
ней изображены, не являются специфичными для какого-либо конкретного уст­
ройства. Главный цикл t ty_t a s k проверяет общие сообщения и обрабатывает
их раньше, чем специфичные. Первая проверка относится к типу S YN_ALARM,
и если сообщение относится к нему, выполняется вызов exp i re_t irne r s , запус­
кающий подпрограмму сторожевого таймера. Затем следует команда c ont i nue.
Фактически, все следующие варианты, которые мы рассмотрим, расположены
после этой команды. Скоро мы скажем об этом более подробно.
Следующий тип, соответствие которому проверяется для сообщения, HARD_
-

INT. Сообщения такого типа чаще всего обусловлены нажатием или отпускани­
ем клавиши на локальной клавиатуре. Сообщения HARD_INT также генерируются
при получении байтов по последовательному порту в случае, если он включен.
В конфигурации, описываемой в книге, последовательные порты не используют­
ся, однако мы сохранили в файле условный код, чтобы продемонстрировать, как
система функционировала бы при поддержке последовательных портов. Битовое
поле сообщения позволяет определить источник прерывания.
Затем проверяется тип SYS_S IG. Системные процессы (драйверы и серверы),
как правило, блокируются и ожидают сообщений. Обычные сигналы способны
получать лишь активные процессы, поэтому стандартный метод использова­
ния сигналов UNIX неприменим к системным процессам. Для передачи сигнала
3.8. Терминалы 375

системному процессу и служит сообщение типа S Y S_S I G . Сигнал, посылае­


мый драйверу терминала, может означать, что ядро собирается завершить ра­
боту ( S I GKSTOP ) , выполнение драйвера терминала должно быть прекращено
( S I GTERМ ) либо ядру необходимо вывести сообщение на консоль ( S IGКМE S S ) .
В каждом из случаев вызываются соответствующие подпрограммы.
Последняя группа сообщений, не зависящих от устройств, включает сообщения
PANI C_DUMPS, D I AGNO S T I C S и FKEY_C ONTROL. Мы расскажем о них более под­
робно при изучении функций, предназначенных для их обслуживания.
Теперь несколько слов об инструкции c ont i nue. В языке С команда cont inue
выполняет �короткое замыкание� цикла и возвращает управление в его начало.
Как только для сообщения устанавливается один из перечисленных типов, оно
обрабатывается соответствующим образом, после чего управление передается
в начало главного цикла (строка 1 3764). Снова выполняется проверка событий,
а затем вызов rec e i ve приводит к ожиданию нового сообщения. Быстрое вос­
становление готовности отклика важно, причем особенно - в случае ввода. Кро­
ме того, если любая из проверок типа в первой части цикла дает положительный
результат, смысл в проверках, следующих после первой в инструкции swi t ch,
теряется.
Ранее мы говорили о сложностях, с которыми приходится иметь дело драйверу
терминала. Еще одна сложность состоит в том, что драйвер обслуживает не­
сколько устройств. Если прерывание не аппаратное, то чтобы определить, какое
устройство должно ответить на сообщение, используется поле сообщения ТТУ_
L INE. Младший номер устройства декодируется при помощи серии сравнений,
и в tp помещается адрес корректной записи в таблице терминалов t ty_t aЫe
(строки 1 3834 - 13847 ). Если устройство является псевдотерминалом, вызывает­
ся функция do_pty (из файла p ty . с ) , после чего главный цикл перезапускает­
ся. В этом случае ответное сообщение генерируется функцией do_p ty. Конечно,
если в конфигурации нет псевдотерминалов, данный вызов при помощи макроса
заменяется пустым, как было описано ранее. Можно было бы понадеяться на от­
сутствие попыток обращения к несуществующим устройствам, но всегда проще
добавить еще одну упреждающую проверку, чем при ошибке разбираться со всей
остальной системой. В ситуации, когда устройство не существует или не скон­
фигурировано, генерируется ответное сообщение с кодом ENXIO, и управление
опять передается в начало цикла.
Оставшуюся часть драйвера составляет то, что мы уже видели в главной функ­
ции других драйверов, - инструкция swi t ch, проверяющая значение типа сооб­
щения (строки 1 3862 - 1 3875 ). Для запроса каждого типа вызывается соответст­
вующая функция, do_rea d, do_wr i t e и т. д. В каждом из случаев функции
сами создают ответное сообщение, вместо того чтобы возвращать необходимую
информацию обратно в главный цикл. Ответное сообщение генерируется в кон­
це цикла только при условии, что было получено сообщение неправильного ти­
па. В этом случае оно содержит код ошибки E I NVAL. Так как отправлять сооб­
щения требуется во многих местах кода, реализована общая подпрограмма t ty_
rep ly, которая отвечает за детали создания ответного послания.
376 Глава 3 . Ввод- вывод

Если полученное функцией t ty_t a sk сообщение имеет корректный тип, а также


н:е является результатом прерывания и не пришло от псевдотерминала, инструк­
ция swi t ch в конце главного цикла перенаправляет его одной из функций do_
read, do_wr i t e, do_i o c t l , do_open, do_c l o s e do_s e l e c t или do_cance l .
У этих функций есть два арrумента: указатель t p на структуру t ty и адрес сооб­
щения. Прежде чем рассматривать эти функции по отдельности, отметим то,
что у них общего. Так как драйвер терминала обслуживает несколько устройств,
все функции должны срабатывать быстро, чтобы главный цикл мог продол­
жить работу.
Однако функции do_read, do_wr i t e и do_i oc t l не всегда могут обеспечить
выполнение всех запрошенных действий сразу. Чтобы позволить файловой сис­
теме обслуживать другие вызовы, от функций требуется немедленный ответ. По­
этому, если запрос не может быть удовлетворен немедленно, в поле состояния
ответного сообщения доставляется код SUSPEND. На рис. 3.2 1 это соответствует
сообщению (3) и приводит к приостановке процесса-инициатора вызова, в то
время как файловая система вьtходит из блокировки. Сообщения под номера­
ми 7 и 8 отправляются позже, после завершения операции. Если операция может
быть выполнена полностью или произошла ошибка, то в поле состояния ответ­
ного сообщения возвращается либо количество переданных байтов, либо код
ошибки. В последнем случае файловая система немедленно отправляет сделав­
шему вызов процессу ответное сообщение, пробуждая его.
Чтение с терминала фундаментально отличается от чтения с диска. Драйвер дис­
ка отдает команды аппаратному обеспечению, и в итоге тоже возвращаются дан­
ные, если не случился механический или электрический сбой. В случае термина­
ла компьютер может вывести на экран приглашение, но не существует способа
принудить сидящего за клавиатурой человека начать печатать хотя бы потому,
что нет никакой гарантии, что за клавиатурой кто-то сидит. Чтобы обеспечить
быстрый возврат, функция do_read (строка 1 3593) начинает с того, что сохра­
няет информацию о запросе. Благодаря этому запрос можно выполнить позже,
когда прибудет недостающая информация. Сначала должен быть проведен кон­
троль ошибок. Ошибка возникает в случае, если устройство все еще ожидает
ввод, чтобы выполнить предыдущий запрос, или если переданнь�е в сообщении
параметры неправильны (строки 13964- 13972 ). После окончания проверки ин­
формация готова к записи в поля записи tp - >t ty_taЫe, соответствующей уст­
ройству (строки 13975- 1 3979). Последним шагом в переменную tp - > i n l e f t
записывается количество полученных символов. Это - важный шаг, так как
переменная tp - > in l e f t необходима для определения момента завершения
чтения. В каноническом режиме значение tp - > i n l e f t декрементируется с ка­
ждым возвращенным символом до тех пор, пока не будет получен символ новой
строки, после чего эта переменная сразу обнуляется. В неканоническом режиме
она изменяется по-другому, но так или иначе, когда запрос выполнен, по тайм­
ауту или после получения минимального необходимого количества символов,
в эту переменную записывается нуль. Когда значение tp - > t ty_i n l e f t достига­
ет нуля, отправляется ответное сообщение. Как мы увидим, ответное сообщение
3. 8 . Терминалы 377

может генерироваться в нескольких местах кода. Кроме того, иногда необходимо


удостоверцться, что выполняющцй чтение процесс ожидает ответа. Здесь инди­
катором служит ненулевое значение tp - > t ty_i n l e f t .
В каноническом режиме терминал ожидает ввода д о тех пор, пока не введено за­
прошенное количество символов или не получен символ конца строки или кон­
ца файла. Чтобы проверить, находится ли терминал в каноническом режиме, тес­
тируется значение бита I CANON структуры t e rrni o s (строка 1398 1 ). Если бит не
установлен, проверяется значение переменных MIN и TIME с целью определить­
ся в дальнейших действиях.
Как мы видели в табл. 3.7, при чтении с терминала поведение определяют раз­
личные комбинации параметров MIN и T I ME. Сначала проверяется значение па­
раметра T IME (строка 13983). Нулевое значение соответствует левому столбцу
таблицы, и в этом случае других проверок в текущий момент не нужно. Если
значение TI ME не равно нулю, проверяется параметр MIN. Если это - нуль, дела­
ется вызов s e t t irne r для запуска таймера, который по истечении заданного ин­
тервала времени прервет запрос DEV_READ, даже если не будет прочитано ни од­
ного байта. В переменную tp - >t ty_rnin в этом случае записывается 1, чтобы
вызов завершился немедленно после того, как до истечения тайм-аута будет по­
лучен хотя бы один сим.вол. В этой точке не проверяется, имеются ли уже вве­
денные символы, поэтому может оказаться, чтQ запроса уже ожидают несколько
символов. Как только обнаруживаются уже введенные символы, они возвраща­
ются процессу, но не больше, чем задано при вызове read. В том случае, если
оба значения, MIN и TIME, не равны нулю, таймер ведет себя по-другому - отсчи­
тывает время между символами. Он запускается только после того, как введен
первый символ, и перезапускается с каждым новым. В переменной tp - > t ty_
e o t c t в неканоническом режиме подсчитываются введенные символы. Ее зна­
чение проверяется, и при равенстве нулю (ни одного символа еще не получено)
таймер останавливается (строка 1 3993).
В любом случае после этого блока условных операторов вызывается функция
in_t rans f e r, чтобы напрямую передать уже имеющиеся во входной очереди
символы на чтение процессу (строка 1400 1 ) . Затем следует вызов handl e_
even t s , который, в свою очередь, может поместить новые данные во входную
очередl! и который снова вызывает функцию in_t rans f e r . Такое кажущееся
дублирование вызовов требует дополнительных пояснений. Хотя до сих пор об­
суждение велось в терминах клавиатурного ввода, функция do_read не зависит
от конкретных устройств и обслуживает, в том числе, удаленные терминалы,
подключаемые по последовательной линии. Возможна ситуация, когда предыду­
щий поток ввода заполняет буфер, и ввод приостанавливается. Первый вызов
in_trans f e r не запускает поток, но вызов hand l e_event s может это сделать.
То, что он пр:и: этом еще раз вызывает функцию in_t rans f er, - всего лишь не­
большой �довесок� . Нам важно, что удаленный терминал снова получает разре­
шение отправлять данные. Оба вызова способны привести к тому, что запрос
окажется выполненным, и файловой системе будет отправлено ответное сообще­
ние. В качестве флага-индикатора отправки ответного сообщения используется
378 Глава 3 . Ввод- вывод

переменная tp - > t ty_in l e f t . Если ее значение не равно нулю (строка 14004),


do_read самостоятельно генерирует и отправляет ответ (строки 140 1 3 - 1402 1 ).
В данном случае мы предполагаем, что системный вызов s e l e c t не использо­
вался, а, следовательно, вызова s e l e c t_re t ry в строке 14006 также нет.
Если в исходном запросе требовалось неблокирующее чтение, файловой системе
дается указание вернуть код ошибки EAGAI N сделавшему вызов процессу. Если
в запросе было затребовано обыкновенное блокирующее чтение, файловая сис­
тема получает код SUS PEND, который разблокирует ее, но дает указание оставить
процесс-инициатор вызова заблокированным. В данном случае в поле t р - > t ty_
inrepc ode записывается значение REVIVE. Когда позднее делается вызов re ad,
этот код помещается в ответное сообщение файловой системе с целью информи­
ровать ее о том, что запросивший чтение процесс был приостановлен и должен
быть активизирован.
Функция do_wr i t e (строка 14029) подобна do_read, но проще, так как при
обработке системного вызова wr i t e нужно учитывать меньшее количество раз­
нообразных параметров. Сначала делается ряд проверок, подобных проверкам
в do_read, из которых выясняется, что предыдущий запрос уже обработан и пе­
реданные параметры корректны. Затем параметры сообщения копируются в струк­
туру t ty. После этого вызывается функция hand l e_event s и проверяется
значение переменной t p - > t ty_ou t l e f t , чтобы узнать, завершена ли работа
(строки 1 4058- 14060). Если да, значит, ответное сообщение уже отправлено
функцией hand l e_event s , и делать больше нечего. Если нет, генерируется от­
вет, параметры которого зависят от того, был исходный вызов wr i t e блокирую­
щим или нет.
Далее следует довольно большая, но не сложная для понимания функция do_
i o c t l (строка 14079). Тело этой функции состоит из двух инструкций swi t ch.
Первая определяет размер параметра, на который ссылается указатель в сообще­
нии (строки 14094- 1 4 1 25). Если размер параметра не равен нулю, проверяется
его корректность. В этом месте нельзя контролировать содержимое, но можно
удостовериться, что структура имеет требуемый размер, начинается по указанно­
му адресу и умещается в том сегменте, где она должна находиться. Остаток
функции составляет еще одна инструкция sw i t ch, проверяющая тип запрошен­
ной операции i o c t l (строки 1 4 1 48- 14225 ).
К несчастью, поддержка требуемых стандартом POSIX операций подразумевает,
что для операций i o c t l необходимо придумывать имена, которые напоминают,
но не дублируют Р О S I Х-имена. Соответствие между именами Р О S I Х -запро­
сов и параметрами вызова i o c t l в MINIX 3 иллюстрирует табл. 3.9. Операция
TCGETS обслуживает пользовательский вызов t cget at t r и просто возвращает
копию структуры t p - > t ty_t erm i o s для терминального устройства. Следую­
щие четыре типа запросов разделяют общий код. Типы запросов TC S E T SW,
TC SETSF и TC SETS соответствуют вызовам РОSIХ-функции t c s e t at t r, и ос­
новное действие в этом случае общее: структура t e rmi o s копируется в струк­
туру t ty терминала. Для вызова TC S E T S копирование происходит немедлен­
но, а для TC S ETSW и TC SETSF выполняется после того, как завершен вывод.
3 . 8 . Терминалы 379

Копирование производится вызовом phy s_copy, следующим за вызовом setat t r


(строки 1 4 1 53- 1 4 156). Если вызов t c s e t a t t r был сделан с модификатором,
требующим отложить выполнение до момента, пока не будет завершен текущий
вывод, а проверка переменной tp - >t ty_out l e f t показывает, что вывод дейст­
вительно еще не завершен (строка 14 139), параметры запроса помещаются в струк­
туру t ty терминала для последующей обработки. Вызов t cdra in приостанав­
ливает программу до тех пор, пока не будет завершен вывод. Он транслируется
в вызов i o c t l типа TC DRAIN. Если вывод уже завершен, дальнейшей обработки
не требуется. Иначе информация также записывается в структуру t ty.
РОSIХ-функция t c f l u s h отменяет незавершенный ввод и (или) вывод в за­
висимости от переданного аргумента. Соответствующая операция i o ct l состо­
ит из вызова t ty_i c anc e l , который обслуживает все терминалы, и (или) вы­
зова зависящей от устройства функции, адрес которой хранится в переменной
t p - >t ty_o c anc e l (строки 1 4 1 59- 1 4 1 67). Вызов t c f l ow аналогичным образом
транслируется в вызов i o c t l . Чтобы приостановить или вновь разрешить вы­
вод, в переменную t p - > t ty_i nh i Ь i t e d записывается значение TRUE или
FAL S E , а затем устанавливается флаг t p - > t ty_event s . Чтобы приостановить
или вновь разрешить ввод, удаленному терминалу отправляется, соответственно,
код STOP (обычно Ctrl+S) или START (Ctrl+Q). Для этого используется специ­
фичная для устройства подпрограмма, адрес которой хранится в переменной
t p - >t ty_e cho (строки 1 4 1 8 1 - 1 4 186).
Большая часть остальных операций, обрабатываемых вызовом do_i oct l , выпол­
няются одной строчкой кода, то есть просто вызовом соответствующей функции.
В случае операций K I OC SМAP (загрузка раскладки клавиатуры) и T I OC SFON (за­
грузка шрифта) делается проверка того, что устройство действительно является
консолью, так как эти операции неприменимы к другим типам терминалов. Если
используются виртуальные терминалы, на всех консолях будет одна раскладка
клавиатуры и шрифт. Аппаратное обеспечение не позволяет легко изменить та­
кой режим работы. Операции, работающие с размером окна, переносят данные
структуры wins i z e между пользовательским процессом и драйвером терминала.
Обратите особое внимание на комментарий под кодом для операции TI OCSWINS Z.
Когда процесс меняет размер окна, в некоторых версиях UNIX от ядра ожидает­
ся, что оно отправит сигнал S I GWINCH всем процессам из той же группы процес­
сов. По стандарту POSIX сигнал не требуется и в MINIX 3 не используется. Тем
не менее любой, кто хочет задействовать эти структуры, должен добавить сюда
код, инициирующий этот сигнал.
Последние два варианта из do_ioct l поддерживают РОSIХ-функции t cgetpgrp
и t c s e tpgrp. С этими типами не связано никаких действий, и они всегда воз­
вращают ошибку. И здесь нет ничего неправильного. Эти функции необходимы
для управления заданиями, для возможности приостанавливать и перезапускать
процесс с клавиатуры. Управление заданиями POSIX не требует, и оно не под­
держивается в MINIX 3. Тем не менее по стандарту POSIX эти функции должны
иметься даже в отсутствие управления заданиями с целью гарантировать пере­
носимость программ.
380 Глав а 3 . Ввод- вывод

Основная задача функции do_open (строка 14234) проста - она увеличивает.


значение переменной t p - > t ty_op enc t для устройства, чтобы впоследствии
можно было судить, открыто оно или нет. Вместе с тем сначала нужно провести
несколько проверок. Согласно стандарту POSIX, первый процесс, открывающий
терминал, должен быть лидером сеанса, и когда этот процесс завершается, у дру­
гих процессов из той же группы отбирается доступ к терминалу. Демонам необ­
ходимо иметь возможность выводить сообщения об ошибках, и если вывод демо­
нов не перенаправлен в файл, он должен поступать на экран, который нельзя
закрыть.
Для этой цели в MINIX 3 существует устройство / dev / l og. Физически это то
же устройство, что и / dev / cons o l e , но оно адресуется отдельным вспомога­
тельным номером и обрабатывается иначе. Это устройство только для записи,
поэтому do_open возвращает ошибку EACCE S S , если сделана попытка открыть
его для чтения (строка 14246). Еще одна проверка, которую делает do_open, -
проверка флага O_NOCTTY. Если флаг не установлен, а устройство не является
устройством / dev / l og, терминал становится управляющим для данной груп­
пы процессов. Для этого номер процесса, сделавшего вызов, заносится в поле
t p - >t ty_pgrp записи таблицы t ty_t aЫ e. Затем инкрементируется перемен­
ная tp - > t ty_openct и отправляется ответное сообщение.
Терминальное устройство может быть открыто несколько раз, и потом функ­
ции do_c l o s e (строка 14260) нечего делать, кроме как уменьшить значение
t p - >t ty_openct на единицу. Следующая проверка (строка 14266) препятству­
ет закрытию устройства, если это устройство / dev / l og. Если обнаруживает­
ся, что устройство закрыто в последний раз, для отмены ввода делается вызов
t p - > t ty_i c anc e l . Также вызываются специфические для устройства функ­
ции t p - >t ty_o c anc e l и tp - >t ty_c l o s e. Различным полям структуры t ty
закрытого устройства присваиваются значения по умолчанию и отправляется
ответное сообщение.
Последним из обработчиков сообщений является функция do_c anc e l ( стро­
ка 1428 1 ). Она вызывается, когда для блокированного процесса, пытающегося
выполнить чтение иди запись, получен сигнал. Возможны три различных со­
стояния, подлежащие контролю.
1. Когда процесс был завершен, он мог выполнять чтение.
2. Когда процесс был завершен, он мог выполнять запись.
3. Процесс мог быть приостановлен вызовом t c dra in до тех пор, пока его вы-
вод не будет завершен.
Для каждого из этих случаев делается проверка и вызывается основная функция
t p - > i c anc e l или специфическая для устройства функция, адрес которой хра­
нится в поле t p - >ooanc e l . В последнем случае единственное, что требуется, -
сбросить флаг tp - >t ty_i oreq, тем самым обозначив, что операция i o c t l за­
вершена. В завершение устанавливается флаг t p - >t ty_event s и отправляется
ответное сообщение.
3 . 8 . Терминалы 38 1

Код поддержки драйвера терминала


Мы рассмотрели функции верхнего уровня, вызываемые в главном цикле t ty_
t a s k, а сейчас настала пора обратиться к коду, обеспечивающему их работу. Мы
начнем с функции hand l e_event s (строка 14358). Как упоминалось ранее, при
каждом проходе главного цикла драйвера терминала для каждого терминального
устройства проверяется флаг tp - >t ty_event s , и если терминалу требуется вни­
мание, вызывается функция handl e_event s . Подпрограммы do_read и do_
wr i t e также вызывают функцию handl e_event s . Эта подпрограмма должна
выполняться быстро. Она сбрасывает флаг tp - >t ty_event s и обращается к спе­
цифическим для устройства функциям записи или чтения, опираясь на указатели
tp - >t ty_devread и tp - >t ty_devwr i t e (строки 14382 - 14385).
Функции вызываются без проверки условий, так как нет возможности выяснить,
что вызвало установку флага, чтение или запись. При разработке было решено,
чтQ проверять для каждого устройства два флага - более затратная операция,
чем вызывать две функции в том случае, если устройство активно. Кроме того,
большую часть времени обеспечивается эхопечать вводимых символов, то есть
полученный от терминала символ выводится на экран, поэтому необходимы
оба вызова. Как упоминалось при обсуждении вызова t c s e t at t r функцией
do_i o c t l , P O S I X не запрещает откладывать выполнение управляющих опе­
раций до тех пор, пока не будет завершен текущий вывод, поэтому сразу после
вызова t ty_devwr i t e имеет смысл позаботиться об операциях i o c t l . Это де­
лается в строке 14388, где, если имеются текущие управляющие запросы, вызы­
вается функция dev_i o c t l .
Так как флаг tp - >t ty_event s устанавливается прерываниями и от быстрого
устройства символы могут поступать с большой скоростью, существует вероят­
ность, что за время выполнения функций чтения и записи, а также вызова dev_
i o c t l произойдет другое прерывание, и флаг снова окажется установленным.
Извлечению данных из буфера, куда они помещаются обработчиком прерыва­
ний, придается более высокий приоритет. Поэтому функция handl e_event s
повторяет вызов функций чтения и записи, если в конце цикла обнаруживается,
что флаг снова установлен (строка 14389). Когда входной поток останавливается
(это может быть и выходной поток, но для входа более характерны подобные
повторяющиеся запросы), чтобы передать символы из входной очереди в буфер
сделавшего вызов процесса, вызывается функция in_t rans f er. Эта функция
сама отправляет ответное сообщение, если переданные символы завершают за­
прос (в каноническом режиме такое может произойти в том случае, если получе­
но запрошенное количество символов, либо получен символ конца строки, либо
достигнут конец файла). Когда это происходит, переменная tp - >t ty_l e f t по­
сле возврата в handl e_event s будет равна нулю. Если при соответствующей
проверке оказывается, что количество переданных символов достигло необхо­
димого минимума, отправляется ответное сообщение. Проверка tp - >t ty_l e f t
предотвращает повторную генерацию сообщения.
Далее перейдем к функции in_t rans f e r (строка 1 44 1 6), которая отвечает за
передачу данных из входной очереди в адресном пространстве драйвера в буфер
382 Глава 3. Ввод- в ы в од

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


данных в этом случае невозможно. Входная очередь представляет собой кру­
говой буфер, символы в которой необходимо проверять на признак конца фай­
ла или, если действует канонический режим, проверять, что ввод продолжается
с новой строки. Кроме того, символы во входной очереди 1 6-разрядные, а буфер
принимающего процесса является массивом 8-разрядных символов. Поэтому ис­
пользуется промежуточный локальный буфер. Символы один за одним проверя­
ются и помещаются в буфер, и когда тот заполняется, вызывается функция sy s_
v i rcopy, которая переносит содержимое промежуточного буфера в приемный
буфер пользовательского процесса (строки 14432- 14459).
Для того чтобы определить, есть ли работа для функции in_t rans f e r, задейст­
вуются три поля структуры t ty: tp - > t ty_in l e f t , tp - > t ty_eot c t и tp - >
t ty_min. Первые две управляют главным циклом процедуры. Ранее упомина­
лось, что в t p - > t ty_i n l e f t изначально помещается количество символов,
запрошенных в вызове re ad. Обычно эта переменная уменьшается на единицу
с каждым новым полученным символом, но она может быть внезапно сброшена
в ноль, если достигается состояние, сигнализирующее о завершении ввода. Когда
переменная становится равной нулю, генерируется ответное сообщение процессу,
запросившему чтение. Поэтому она также используется как индикатор отправки
сообщения. В результате, когда в строке 14429 выясняется, что переменная tp - >
t ty_inl e f t уже равна нулю, это - вполне веская причина, чтобы прервать вы­
полнение функции in_t rans f e r без ответного сообщения.
Во второй части условия сравниваются переменные tp - > t ty_e o t c t и tp - >
t ty_min. В каноническом режиме обе переменные относятся к количеству пол­
ных введенных строк, а в неканоническом режиме они относятся к символам.
Переменная tp - > t ty_eo t c t увеличивается на единицу с каждым занесенным
во входную очередь �раз рывом строки� или байтом и уменьшается подпрограм­
мой in_t rans f e r, когда строки или байты удаляются из очереди. Таким обра­
зом, эта переменная подсчитывает количество строк или символов, полученных
драйвером терминала, но еще не переданных процессу. Поле tp - > t ty_min хра­
нит значение минимального числа строк (в каноническом режиме) или симво­
лов (в неканоническом), которое должно быть передано для удовлетворения за­
проса. В каноническом режиме это значение всегда равно 1, а в неканоническом
оно может принадлежать интервалу от О до МAX_INPUT (в MINIX 3 эта констан­
та равна 255). Тем самым вторая половина проверки (строка 14429) приводит
в каноническом режиме к тому, что подпрограмма завершается сразу же, если

еще не получена хотя бы одна целая строка. Передача производится после получе­
ния всей строки, потому содержимое очереди можно изменить, если, например,
пользователь ввел символ K I L L или ERASE до нажатия клавиши Enter. В некано­
ническом режиме немедленный возврат имеет место, если запрошенное количе­
ство символов еще недоступно.
Несколькими строками далее переменные tp - >t ty_i n l e f t и tp - >t ty_eot ct
управляют главным циклом процедуры. В каноническом режиме передача про­
изводится до тех пор, пока в очереди не останется больше полностью введенных
3 . 8. Терминал ы 383

строк. В неканоническом режиме в поле t p - > t ty_e o t c t подсчитывается коли­


чество ожидающих передачи символов. Переменная tp - >t ty_min необходима,
чтобы определить, нужно ли входить в цикл, но в определении условия завер­
шения цикла не участвует. После того как цикл начался, передаются либо все
полученные символы, либо запрошенное их количество в зависимости от тоm,
что меньше.
Во входной очереди символы представлены в виде 16-разрядных величин. Поль­
зовательскому процессу передается только сам код символа - младшие 8 бит.
Назначение старших битов кода иллюстрирует рис. 3.24. Здесь есть флаги, по­
казывающие, входит ли символ в ЕSС-последовательность (Ctrl+V) , означает ли
он конец файла или содержит один из кодов, сопоставленных концу строки.

V: I N_ESC после LN EXT (Ctrl + V)


D: D)
I N_EOF, конец файла (Ctrl +
N: I N_EOT, разрыв строки (NL и др.)
сссс: счетчик отображения символов
7: 7,
Бит может быть обнулен, если установлен ISTRI P
6-0: Биты 0--6,
кoд ASCl l
Рис. 3 . 24 . Поля кода символа, находящегося во входной очереди

Четыре бита используются для подсчета символов, чтобы знать, сколько места
осталось для эхопечати. Условная инструкция в строке 14435 проверяет, уста­
новлен ли признак конца файла (D на рисунке) . Эта проверка делается в начале
потому, что символ конца файла не должен ни передаваться программе, ни
использоваться при подсчете символов. При передаче каждого символа у него
маскируется 8 старших бит, и в локальный буфер записывается только АSСП­
значение (строка 1 4437).
Существуют несколько способов сигнализировать об окончании ввода, но от спе­
цифической для устройства функции ввода ожидается, что она распознает, когда
вводимый символ является переносом строки, концом файла (Ctrl+D) или одним
из подобных символов, и соответствующим образом пометит их. Функции i n_
t r ans f e r остается только проверить эту отметку, бит IN_EOT (N на рис. 3.24)
в строке 14454. Если символ опознан, значение tp - >t ty_eot c t уменьшается
на единицу. В неканоническом режиме подобным образом подсчитываются все
символы, и все они помечаются битом I N_EOT, поэтому tp - >t ty_eot c t содер­
жит число символов, не удаленных из очереди. Единственное различие в работе
главного цикла процедуры в двух разных режимах работы терминала определя­
ется строкой 14457. Там обнуляется переменная tp - >t ty_e o t c t , когда полу­
чен символ, помеченный как разрыв строки, но делается это лишь тогда, когда
действует канонический режим. Таким образом, когда управление вновь переда­
ется в начало цикла, тот корректно завершается после символа разрыва строки.
Но сказанное верно только для канонического режима - в неканоническом раз­
рывы строк игнорируются.
384 Глава 3 . Ввод- вывод

Когда цикл завершается, локальный буфер символов для передачи об:р1чно час­
тично полон (строки 1 446 1 - 14468). В том случае, если значение tp - > t ty_
inl e f t достигло нуля, генерируется и отправляется ответное сообщение. В кано­
ническом режиме это делается всегда, а в неканоническом число символов в бу­
фере сравнивается с минимальным передаваемым количеством, и если символов
недостаточно, ответное сообщение не отправляется. Если у вас достаточно хорошая
память, чтобы запомнить вызовы i n_t r ans f e r (в do_read и handl e_event s ) ,
вы, наверно, будете удивлены. Там код, следующий за вызовом, отправлял ответ­
ное сообщение, когда функция i n_t rans f er передавала больше символов, чем
указано в tp - > t ty_min. И здесь именно этот случай. Причина, по которой от­
ветное сообщение не отправляется непосредственно из in_t rans f e r, раскрыва­
ется чуть позже при обсуждении следующей функции, вызывающей функцию
in_t r ans f e r при других обстоятельствах.
Эта следующая функция носит имя in__proc e s s (строка 14486). Она вызывается
из аппаратно-зависимого кода для выполнения общйх действий, необходимых
всегда. Ее аргументами являются указатель на структуру t ty для исходного
устройства, указатель на массив 8-разрядных символов, подлежащих обработке,
и счетчик. Новое значение счетчика возвращается в вызвавшую программу.
Функция i n__pro c e s s весьма велика, но ее работа не сложна. Она добавляет
1 6-разрядные символы во входную очередь, откуда их в дальнейшем извлекает
функция in_t rans f er.
Функцией i n_t r ans f e r символы разделяются на несколько категорий.
1. Обычные символы, расширенные до 16 бит, которые добавляются в очередь.
2. Символы, управляющие дальнейшей обработкой, модифицируют флаги, но
сами в очередь не помещаются.
3. Символы для эхопечати применяются немедленно, без занесения в очередь.
4. Символы, имеющие специальное значение, заносятся в очередь с установлен-
ными в старшем байте специальными битами, например, такими как бит БОТ.
Сначала рассмотрим совершенно стандартную ситуацию, когда обычный символ,
такой как х (АSСП-код Ох78), вводится в середине короткой строки без воздей­
ствия ЕSС-кодов на терминале, •настроенном» по умолчанию. Полученный
от устройства ввода, этот символ занимает биты от О до 7 (см. рис. 3.24). В строке
14504 старший значащий бит сбрасывается в ноль, если установлен бит I S TRI P.
Но по умолчанию MINIX 3 не обнуляет старший бит, позволя;я вводить 8-раз­
рядные символы. В любом случае символ х это не затронет. Расширенная обра­
ботка ввода по умолчанию в M INIX 3 не разрешена, поэтому проверка бита
I EXTERN в переменной tp - > t ty_t ermi o s . c_l f lags (строка 14507) дает поло­
жительный результат, но для всех последующих проверок результат отрицателен
вследствие установленных нами условий: не действует ЕSС-код (строка 1 45 1 0),
сам символ не является ЕSС-кодом (строка 145 1 7 ) и это - не символ REPRINT
(строка 14524).
Проверки в следующих нескольких строках обнаруживают, что символ не явля­
ется специальным символом _PO S I X_VD I SABLE, ни CR, ни NL. Наконец, одна
3 . 8 . Терминалы 385

проверка успешна: действует канонический режим, и символ не является специ­


альным (строка 1 4324). Рассматриваемая нами буква х не является ни символом
ERASE, ни KI LL, ни EOF (Ctrl+D), ни NL, ни EOL, поэтому до строки 14576 с ним
ничего не происходит. Здесь выясняется, что бит I XON установлен (по умол­
чанию). Этот бит разрешает использование символов START (Ctrl +S) и STOP
(Ctrl+Q), но х к таковым, очевидно, не относится. В строке 14597 обнаруживает­
ся, что установлен бит I S IG, разрешающий обработку INTR и QU IT, но, опять
же, совпадения в данном случае не обнаруживается.
Фактически первое интересное событие для обычного символа происходит в стро­
ке 1 46 1 0, где выясняется, заполнена уже входная очередь или нет. В этом случае
символ игнорируется, и так как активен канонический режим, пользователь не
увидит эха символа на экране. Игнорирование символа обеспечивает инструк­
ция c ont i nue, которая вызывает переход в начало цикла. В данном случае рас­
сматриваем рядовые условия выполнения, поэтому предположим, что буфер еще
не полон. Не проходит и следующий тест, выделяющий специальный неканони­
ческий режим работы (строка 1 4 6 1 6). Поэтому управление передается на стро­
ку 14629, содержащую вызов функции e cho, которая показывает пользователю
введенный символ, так как по умолчанию флаг t p - > t ty_t errni o s . c_l f l ag
установлен.
Наконец, в строках 14632-14636 символ подготавливается к занесению во входную
очередь. В этот момент увеличивается значение t p - > t ty_i nc ount , а tp - > t ty_
eot c t не изменяется, так как это - обычный символ, не отмеченный битом ЕОТ.
Последняя строка в цикле вызывает функцию i n_t r ans f e r в том случае, если
только что помещенный в очередь символ привел к ее запоJ.IНению. В обычных
условиях, в которых протекает действие, функция i n_t ran s f er ничего бы не
сделала, поскольку значение tp - > t ty_e o t c t равно нулю, tp - > t ty_rnin - еди­
нице (предполагается, что очередь обслуживалась нормально и предшествую­
щий ввод был принят после завершения строки), и тест в начале функции in_
t r ans f er (строка 14429) привел бы к немедленному возврату.
Итак, рассмотрев вызов i n_t rans f er для обычного символа в стандартных ус­
ловиях, мы вновь вернемся к началу, но теперь взглянем, что происходит в менее
привычной ситуации. Первым будет на рассмотрении символ e s c ape, который
позволяет символам, в обычной ситуации имеющим специальное значение, пере­
даваться пользовательскому процессу. Действие этого символа включается уста­
новкой флага tp - > t ty_e s c ap ed. Когда ЕSС-режим активен, флаг сбрасывает­
ся, а к текущему символу добавляется бит IN_E SC, на рис. 3.24 обозначенный
как v. Это вызывает дополнительную обработку при эхопечати; чтобы сделать
такие символы видимыми, они отображаются с префиксом л . Кроме того, бит
IN_ES C предотвращает интерпретацию символа как специального.
Следующие несколько строк проверяют сам ЕSС-символ, LNEXT (по умолчанию
Ctrl+V) . При его обнаружении устанавливается флаг tp - > t ty_e s c aped и дваж­
ды вызывается функция r awe cho, чтобы вывести префикс л с примыкающим
символом забоя. Это напоминает пользователю, что действует ЕSС-режим, а после­
дующий ввод затирает префикс л . Символ LNEXT - пример символа, влияющего
386 Глава 3 . Ввод- вывод

на обработку последующего ввода (в данном случае затрагивается только один


последующий символ). Такой символ не заносится в очередь, и после двух вызо­
вов rawe cho цикл переходит в начало. Порядок указанных двух проверок ва­
жен, так как им обеспечивается возможность передать LNEXT пользовательскому
процессу. Для этого символ необходимо ввести дважды.
Следующий специальный символ, обрабатываемый i n_p r oc e s s , - REPRINT
(Ctrl+R). Обнаружение этого символа инициирует вызов rep r i nt (строка 14525),
что приводит к повторному отображению выведенного эха. В дальнейшем сам
символ REPRINT игнорируется и не оказывает влияния на входную очередь.
Изучать, как обрабатываются все остальные специальные символы, было бы уто­
мительно, а исходные коды функции i n_pro c e s s достаточно прямолинейны.
Поэтому мы упомянем лишь еще несколько деталей. Во-первых, использование
специальных битов из старшего байта 1 6-разрядного значения во входной очере­
ди позволяет легко разбивать символы на классы, имеющие сходный эффект.
Так, все символы ЕОТ (Ctrl+D), LF и альтернативный символ EOL (по умолчанию
не задан) отмечаются битом ЕОТ (на рис. 3.24 это бит D (строки 14566- 14573)),
что позволяет легко их распознавать.
Наконец, объясним отмеченное ранее странное поведение функции in_t rans f er.
Ответное сообщение не генерируется каждый раз при завершении функции, хо­
тя, казалось бы, после вызова i n_t r ans f e r всегда следует код, генерирующий
ответ. Вспомните, что вызов i n_t r ans f e r в функции i n_pr oc e s s , когда оче­
редь полна, не имеет эффекта в каноническом режиме (строка 1 4639). Но если
требуется неканоническая обработка, каждый символ помечается битом ЕОТ,
и, таким образом, tp - > t ty_e o t c t подсчитывает все символы (строка 14636).
В свою очередь, это приводит к переходу в главный цикл функции in_tr ans f e r
при ее вызове, когда вызов делается п о причине заполнения очереди в неканони­
ческом режиме. В таком случае не нужно после возврата из i n_t r ans f e r пере­
давать драйверу терминала никаких сообщений, ведь после этого наверняка бу­
дут считаны еще символы. Конечно, хотя в неканоническом режиме отдельный
вызов r e ad ограничен размером входной очереди (в MINIX 3 - 255 символов),
в неканоническом режиме функция r e ad должна быть способна передать коли­
чество символов по стандарту POSIX, задаваемое константой _PO S I X_ S S I Z E_
МАХ. В MINIX 3 это значение равно 32767.

Несколько следующих функций из файла t ty . с обеспечивают ввод символов.


Функция t ty_echo (строка 1 4647) особым образом обрабатывает некоторые
символы, но большинство из них просто отображаются ею в выводе того терми­
нала, который используется для ввода. Когда вывод от пользовательского про­
цесса направлен на устройство, может оказаться, что в этот момент на устройст­
во производится эхопечать. Когда пользователь попытается удалить последний
введенный символ, это усложняет дело. Чтобы решить проблему, когда произво­
дится обычный вывод, флаг tp - > t ty_r ep r i nt всегда устанавливается аппа­
ратно-зависимыми функциями вывода. Поэтому функция, которая вызывается
для обработки символа забоя, может сообщить, что к экрану применен смешанный
вывод. Поскольку в echo для вывода символов также привлекаются аппаратные
3 . 8 . Терминалы 387

процедуры вывода, текущее значение флага t p - > t ty_reprint сохраняется в ло­


кальной переменной rp и восстанавливается после вывода (строки 14668- 1470 1).
Исключение составляет случай, когда только что начался ввод новой строки.
Здесь переменной rp присваивается значение FALS E, тем самым обеспечивается,
что флаг tp - > t ty_rep r i nt будет сброшен по завершении функции echo.
Бы могли заметить, что функция e cho возвращает значение. Например, вы мог­
ли встретить такую запись в коде i n_p r oc e s s (строка 1 4629):
ch = t ty_e cho ( t p , ch) ;

Возвращаемое значение говорит о том, сколько знакомест занял на экране вы­


вод, и в случае символа ТАВ может достигать восьми. Эти символы подсчитыва­
ются в поле, которое на рис. 3.24 обозначено как с с с с . Обычные символы зани­
мают одно знакоместо, но специальные, за исключением ТАВ, CR, NL или DEL
(Ox7F), требуют два, они отображаются с префиксом л перед печатным АSСП­
символом. С другой стороны, NL или CR «занимают• ноль знакомест. Конечно
же, сам вывод эха должен осуществляться при помощи аппаратно-зависимых
подпрограмм, и когда устройству нужно передать символ, делается косвенный
вызов подпрограммы, адрес которой хранится в tp - > t ty_echo, как происхо­
дит, например, для обычных символов в строке 14696.
Следующая функция, rawe cho, вызывается тогда, когда необходимо обойти спе­
циальную обработку, присущую функции echo. Функция r awe cho проверяет,
установлен ли флаг ЕСНО, и если да, отправляет введенный символ аппаратно­
зависимой подпрограмме t p - > t ty_echo без всякой дополнительной обработ­
ки. Локальная переменная rp используется для того, чтобы сохранить значение
флага tp - > t ty_r ep r i n t .
Когда in_proce s s обнаруживает символ забоя, вызывается функция backover
(строка 1472 1 ) . Она манипулирует содержимым входной очереди, пытаясь по
возможности удалить из нее последний введенный символ, то есть если очередь
не пуста и последний символ не является переносом строки. Здесь проверяется
значение флага tp - >t ty_repr i n t , упоминавшегося при обсуждении функций
e cho и r awe cho . Когда этот флаг равен TRUE, происходит вызов repr i nt
(строка 1 4732), чтобы показать на экране чистую копию редактируемой строки.
Чтобы узнать, сколько знакомест необходимо очистить, проверяется значение
поля l en у последнего введенного символа (поле с с с с на рис. 3.24), и для каж­
дого знакоместа при помощи r awecho выводится последовательность «забой­
пробел-забой•, стирающая символ с экрана.
Следующая функция имеет имя r ep r i nt . В дополнение к тому, что ее вызывает
backover, она может вызываться, когда пользователь нажимает клавиши Ctrl+R
( REPRINT ) . Цикл в коде этой функции (строки 1 4764- 1 4769) сканирует вход­
ную очередь в обратном направлении до последнего «разрыва строки•. Если раз­
рыв строки является последним введенным символом, значит, для функции нет
поля деятельности, и она заканчивает работу. Если это не так, сначала функция
выводит на экран символ REPRINT, который отображается как пара л R, перехо­
дит на следующую строку и повторно показывает содержимое очереди, начиная
с последнего символа разрыва строки до конца.
388 Глава 3. Ввод- вывод

Вот теперь мы добрались до функции out__pr o c e s s (строка 1 4789). Как и i n_


pro c e s s , она вызывается аппаратно-зависимыми функциями вывода, но устрое­
на проще. Сама эта функция вызывается не подпрограммами консоли, а специфи­
ческими подпрограммами вывода, действующими при работе последовательного
интерфейса RS-232 и псевдотерминала. Эта функция обрабатывает кольцевой
байтовый буфер, но не удаляет из него символы. Единственное, что она меняет
в массиве, - вставляет перед символами NL символы CR, если установлены биты
O P O S T (разрешение обработки вывода) и ONLCR (преобразование NL в CRLF )
в поле t p - >t ty_t ermi o s . o f l ag. В M INIX 3 по умолчанию эти биты уста­
новлены. Работа функции заключается в поддержании значения переменной
tp - > t ty__po s i t i on в структуре t ty устройства. Усложняют жизнь символы
табуляции и забоя.
Далее следует подпрограмма dev_i o c t l (строка 1 4874). Она поддерживает do_
i o c t l , выполняя функции t c dra i n и t c f l u sh, когда do_i o c t l вызывается
с аргументом TC SADRAIN или TC SAFLUSH. Вызов do_i o c t l не может начать
обрабатываться немедленно, если вывод еще не завершен, поэтому информация
о запросе сохраняется в полях структуры t ty, зарезервированных для этой цели.
Когда запускается функция handl e_event s , она после вызова аппаратно-зави­
симой подпрограммы проверяет значение флага tp - > t ty_ioreq, и если имеется
отложенная операция, вызывает dev_i o c t l . Сама функция dev_i o c t l прове­
ряет tp - > t ty_out l e f t , чтобы узнать, завершен ли вывод. Если это так, выпол­
няются те же действия, которые функция do_i oc t l выполнила бы в варианте
без задержки. При работе t cdrain единственное, что нужно сделать, - это сбро­
сить поле tp - > t ty_i o r e q и отправить ответное сообщение файловой системе
с просьбой разбудить приостановленный процесс, сделавший вызов. Вариант
TC SAFLUSH вызова t c s e t addr прибегает к вызову t ty_i c anc e l , чтобы пре­
рвать ввод. В обоих вариантах t c s e at ddr структура t ermi o s , адрес которой
передается при вызове i o c t l , копируется в поле tp - > t ty_t e rmi o s . Затем вы­
зывается s e t at t r и посылается ответное сообщение, как и в случае с t c dr a i n,
с целью разблокировать процесс-инициатор вызова.
Следующая на очереди - процедура s e t a t t r (строка 1 4899). Как вы могли
видеть, она вызывается из do_i o c t l и dev_i o c t l, чтобы изменить атрибуты
терминального устройства, а также из do_c l o se, когда нужно сбросить атрибуты
в значения по умолчанию. Эта функция всегда вызывается после того, как за­
писываются новые данные в структуру t e rmi o s , поскольку лишь изменения
значений недостаточно. Если управляемое устройство переведено в неканониче­
ский режим, необходимо установить бит IN_EOT у всех символов во входной
очереди, как если бы они изначально были напечатаны в неканоническом ре­
жиме. Так как нельзя узнать, какой атрибут только что был изменен, невоз ­
можно и определить, следует ли устанавливать бит у символов в очереди. Поэто­
му проще все свести к одному случаю, нежели проверять содержимое очереди
(строки 1 4913- 14919).
Далее нас интересуют значения параметров MIN и T IME. В каноническом режиме
значение t p - > t ty_min всегда равно 1 и установлено в строке 1 4926. В некано-
3 . 8 . Терминал ы 389

ническом режиме различные комбинации этих параметров позволяют реализо­


вать четыре разных режима работы, показанных в табл. 3.7. В tp - > t ty_m i n
(строки 1493 1 - 14934) сначала записывается значение, переданное через tp - >
t ty_t e rmi s o . с е [ VМIN J , а после при равенстве его нулю и при нулевом значе­
нии в tp - > t ty_t ermi s o . с е [ VT IME ] оно изменяется.
Наконец, благодаря s e t a t t r вывод не останавливается, если отключено управ­
ление XON / XOFF; функция s e t at t r отправляет сигнал S I GHUP, если скорость
передачи установлена в ноль, и делает косвенный вызов подпрограммы, адрес
которой хранится в t p - > t ty_i o c t l , чтобы выполнить действия, доступные
только на уровне устройства.
Функция t ty_r ep ly (строка 14952) уже много раз упоминалась в предшест­
вующих обсуждениях. Ее алгоритм прост, она формирует сообщение и отправля­
ет его. Если по какой-то причине отправить сообщение не удалось, происходит
сбой системы. Оставшиеся функции столь же просты. Функция s i gchar (стро­
ка 14973) заставляет менеджера памяти отправить сигнал. Если установлен флаг
NOFLSH, очищается входная очередь - обнуляется счетчик полученных строк или
символов, а указатели на конец и начало очереди становятся равными. Это -
действие по умолчанию. Флаг может быть также установлен, когда ожидается
сигнал S IGHUP, чтобы продолжить ввод и вывод после получения сигнала. Функ­
ция t ty_i c anc e l (строка 1 5000) очищает входную очередь, как это делает
s i gchar, а в дополнение вызывает аппаратно-зависимую функцию tp - >t ty_
i c anc e l с целью очистить буфер самого устройства при наличии такого буфера.
Функция t ty_i n i t (строка 150 1 3) вызывается один раз на каждое устройство
при запуске t ty_t ask. Она устанавливает значения по умолчанию. Изначально
в поля tp - > t ty_i c anc e l , tp - > t ty_oc anc e l , tp - > t ty_i o c t l и t p - > t ty_
c l o s e записывается указатель на заглушку, t ty _devnop. Затем t ty _ini t вы­
зывает одну из специфичных для устройства функций инициализации в зависи­
мости от того, к какой категории относится терминал: консоль, последовательная
линия или псевдотерминал. Эта функция сохраняет в полях структуры ссылки
на реальные функции, специфичные для данного устройства. ( Вспомните, что
если в определенной категории не инициализировано ни одного устройства, со­
ответствующий макрос трубит немедленный отбой, в результате не компилиру­
ется ни одна из частей кода неиспользуемых устройств.) Затем вызов i n i t_scr
инициализирует драйвер консоли и обращается к процедуре инициализации
клавиатуры.
Следующие три функции поддерживают таймеры. Сторожевой таймер инициали­
зируется указателем на функцию, запускаемую при его истечении. Для большин­
ства таймеров, устанавливаемых драйвером терминала, такой функцией является
t ty_t imed_out . Она устанавливает флаги событий, форсирующие обработку
ввода и вывода. Функция exp i r e_t ime r s поддерживает очередь таймера драй­
вера терминала. Вспомните, что она вызывается из главного цикла функции
t ty_t a s k при получении сообщения SYN_ALARМ. Библиотечная подпрограмма
tmr s_expt imers выполняет обход связанного списка таймеров, вызывая сторо­
жевые функции для тех, которые истекли. Если по возвращении из библиотечной
390 Глава 3 . Ввод-вывод

функции очередь все еще активна, выполняется вызов ядра sy s_s e t a l arrn, за­
прашивающий очередное сообщение SYN_ALARМ. Наконец, функция s e t t irner
(строка 15089) устанавливает таймеры, определяющие срок возврата из вызова
read в неканоническом режиме. Она вызывается с двумя параметрами: t ty__ptr,
указателем на структуру t ty, и еnаЫ е, целым числом, обозначающим истину
или ложь. Библиотечные функции trnr s_s e t t irner и trnr s_c l r t irner исполь­
зуются для включения и отключения таймера в соответствии со значением аргу­
мента еnаЫ е. Если таймер включен, то в качестве сторожевой функции всегда
используется описанная ранее функция t ty_t irned_out .
Описание функции t ty_devnop (строка 15 125) длиннее, чем ее код, поскольку
последний попросту отсутствует. Это - пустая функция, косвенно вызываемая
в случае, если обслуживание устройству не требуется. Мы видели, что в t t у_
ini t многие указатели функций по умолчанию указывают на t ty_devnop до
тех пор, пока не выполнена процедура инициализации устройства.
Последняя функция в файле t ty . с заслуживает особого внимания - это функ­
ция s e l e c t . Она представляет собой системный вызов, используемый, когда
одному процессу необходимо асинхронно обслуживать несколько устройств
ввода-вывода. Классическим примером является программа связи, работающая
с локальной клавиатурой и удаленной системой, возможно, соединенными при
помощи модема. Вызов s e l e c t дает возможность открыть несколько файлов
устройств и наблюдать за их считыванием или записью без блокирования. В от­
сутствие s e l e c t для двусторонней связи необходимо использовать два отдель­
ных процесса, один из которых является главным и обеспечивает связь в одном
направлении, а другой является подчиненным и обеспечивает связь в другом на­
правлении. Вызов s e l e c t является примером удобной возможности, значитель­
но усложняющей систему. Одной из целей разработки MINIX 3 является про­
стота, достаточная для освоения операционной системы с разумными усилиями
за разумное время. По этой причине мы несколько ограничимся в повествовании
и не станем рассматривать здесь функцию do_s e l e c t (строка 1 5 1 35), а также
поддерживающие подпрограммы s e l e c t_t ry (строка 143 13) и s e l e c t_re t ry
(строка 14348).

3 . 8 . 5 . Реал изация драй вера клавиатуры


Теперь мы обратимся к аппаратно-зависимому коду, обеспечивающему работу
консоли. В MINIX 3 консоль состоит из клавиатуры I B M РС и отображаемого
на память экрана. Физические устройства консоли полностью различны, у стан­
дартных настольных систем экран поддерживается при помощи контроллера
(одного из дюжины типов), а работу клавиатуры обеспечивают схемы на ма­
теринской плате, взаимодействующие с однокристальным 8-разрядным компью­
тером клавиатуры. Для поддержания двух различных устройств требуются два
варианта программного обеспечения, которые в MINIX находятся в файлах
keyboard . с и conso l e . с .
С точки зрения операционной системы клавиатура и экран являются частями
одного устройства, / dev / cons o l e. Если у видеоконтроллера достаточно памяти,
3 . 8 . Терминалы 391

то в систему может быть включена поддержка виртуальной консоли, тогда, по­


мимо устройства / dev / c on s o l e, могут существовать дополнительные логиче­
ские устройства, / dev/ t ty c l , / dev/ t ty c 2 и т. д. В любой момент времени на
экране видна только одна из этих консолей, эта же консоль получает ввод с един­
ственной клавиатуры. Логически клавиатура подчинена консоли, хотя подтвер­
ждается это только двумя достаточно незначительными фактами. Во-первых,
в структуре консоли t ty, хранящейся в t ty_t aЬ l e, есть отдельные заполняемые
при запуске поля для ввода и вывода, например t ty_devread и t ty_devwr i te,
являющиеся указателями на функции в файлах keyboard . с и conso l e . с . Одна­
ко есть только одно поле t ty __pr i v, ссылающееся исключительно на структуры
данных консоли. Во-вторых, перед входом в главный цикл t ty_t a s k проводит
инициализацию всех логических устройств. Для / dev / c ons o l e процедура ини­
циализации расположена в файле c ons o l e . с, и код для инициализации клавиа­
туры вызывается из нее. В то же время иерархия может быть перевернута. Имея
дело с устройствами ввода-вывода, мы всегда сначала рассматривали ввод, а затем
вывод, и сейчас мы продолжим эту традицию, рассмотрев код в файле keyboard . с
в текущем разделе и отложив файл c ons o l e . с до следующего.
Файл k eyb oard . с , как и многие другие, начинается с нескольких директив
# i nc lude. Но одна из них необычна. Файл keymap s / us - s t d . sr c, включен­
ный в строке 152 18, не является заголовочным С-файлом, это - файл исходных
кодов, который при компиляции определяет раскладку клавиатуры по умол­
чанию, попадающую в keyboard . о в виде инициализированного массива. Сам
этот файл довольно велик, и в табл. 3. 1 1 мы привели лишь несколько типичных
записей из него. После директив # inc l ude следуют макросы, задающие разнооб­
разные константы. Первая группа привлекается для низкоуровневого взаимодей­
ствия с контроллером клавиатуры. Большая часть из них задают адреса портов
ввода-вывода или битовые комбинации, используемые при таком взаимодейст­
вии. Следующая группа макросов описывает символические имена для специ­
альных клавиш. Константа KB_IN_BYTE S (строка 1 5249), имеющая значение 32,
определяет размер циклического клавиатурного буфера. Следующие переменные
хранят различные параметры, необходимые для правильной интерпретации нажа­
тия клавиши. Поскольку буфер только один, необходимо гарантировать полную
обработку его содержимого перед сменой виртуальной консоли.
Следующая группа переменных обеспечивает хранение различных состояний,
что необходимо для корректной интерпретации нажатия клавиши. Они имеют
разное назначение. Например, флаг c ap s_down (строка 1 5266) переключается
с TRUE на FAL SE, и наоборот, при нажатии клавиши Caps Lock. Флаг shi f t (стро­
ка 1 5264) устанавливается в TRUE, когда нажимается клавиша Shift, и сбрасыва­
ется, когда она отпускается. Переменная e s c устанавливается, когда получен
код опроса e s c ape. Она всегда сбрасывается после получения одного символа.
Макрос map_key O (строка1 5297) возвращает АS СП-код, соответствующий дан­
ному коду опроса без учета модификаторов, то есть попадающий в первую ко­
лонку карты клавиш. 4 Старший брат� этого макроса, макрос map_key (строка
1 5303), выполняет преобразование кода опроса в АS СП-код с учетом всех моди­
фикаторов, действующих при вводе символа.
392 Глава 3 . Ввод- вывод

Когда нажимается или отпускается клавиша, вызывается обработчик прерыва­


ний клавиатуры, подпрограмма kЬd_int e rrupt (строка 1 5335). Чтобы узнать
от контроллера клавиатуры код опроса, она вызывает s c ode. Когда прерывание
инициировано отпусканием клавиши, у кода опроса устанавливается старший
значащий бит, и в этом случае нажатие игнорируется, если это - не одна из кла­
виш-модификаторов. Чтобы обслужить прерывание как можно быстрее, все не­
обработанные коды опроса помещаются в кольцевой буфер, и для текущей кон­
соли устанавливается флаг tp - > t ty_event s (строка 1 5350). Как и ранее, мы
будем считать, что вызовов s e l e c t не производилось, и выход из обработчика
kЬd_int errupt выполняется немедленно после этого. На рис. 3.25 представлен
пример содержимого кольцевого буфера для короткой строки, содержащей два
символа в верхнем регистре, каждый из которых предваряется кодом опроса, соот­
ветствующим нажатию клавиши Shift, а следом за кодом символа идет код отпус­
кания клавиши Shift.

42 35 1 63 1 70 1 8 1 46 38 1 66 38 1 66 24 1 52 57 1 85
L+ Н+ Н- L- Е+ Е- L+ L- L+ L- О+ О- SP+ SP-
54 17 145 1 82 24 1 52 1 9 1 47 38 1 66 32 1 60 28 1 56
R+ W+ W- R- О+ О- R+ R- L+ L- D+ D- CR+ CR-
Рис. 3.25. Содержимое входного буфера для строки текста, введенной с клавиатуры. Вторая
строка таблицы описывает нажатия клавиш. Символы L+, L-, R+ и R- означают соответственно
нажатие и отпускание правой и левой клавиши Shift. Код отпускания клавиши
на 1 28 больше кода нажатия
Функция kb_r ead извлекает из циклического буфера коды опроса и помещает
в свой локальный буфер АSСП-коды. Этот локальный буфер должен быть дос­
таточно емким, чтобы вместить ЕSС-последовательности, генерируемые в ответ
на нажатия некоторых клавиш на цифровой клавиатуре. Затем, чтобы поместить
символы во входную очередь, вызывается функция in_pro c e s s . В строке 1 5379
значение i c ount инкрементируется. Вызов make_break возвращает АSСП-код
в виде целого числа. В этой точке специальные клавиши, например клавиши циф­
ровой клавиатуры и функциональные клавиши, имеют коды большие, чем OxFF.
Коды в диапазоне от НОМЕ до INSRT (от Ох 1 0 1 до Ох 1 0 С, эти константы задаются
в файле inc lude / m i n i x / keymap . h) при помощи массива nump ad_map пре­
образуются в трехсимвольные ЕSС-последовательности, показанные в табл. 3. 13.
Эти последовательности затем передаются в функцию i n_p r o c e s s (строки
1 5392 - 1 5397). Большие по величине коды в функцию i n_pr o c e s s не пере­
даются, среди них ищутся коды, соответствующие комбинациям клавиш Alt+f-,
Alt+� и от Alt+F1 до Alt+ F 1 2. Если обнаруживается одна из таких комбинаций,
вызывается функция s e l e c t_c on s o l e , переключающая виртуальные консоли.
Сочетания от Ctrl+F1 до Ctrl+F1 2 также обрабатываются особо. Сочетание Ctrl+F1
показывает текущие назначения функциональных клавиш (более подробно об
этом мы скажем позже), а сочетание Ctrl+FЗ обеспечивает переключение между
программной и аппаратной прокруткой на экране консоли. Сочетания Ctrl+F7,
3 . 8 . Терминалы 393

Ctrl+F8 и Ctrl+ F9 генерируют те же сигналы, что Ctrl+\, Ctrl+C и Ctrl+ U соответ­


ственно, с тем исключением, что они не могут быть изменены командой s t ty.

ЕSС-последовательности, генерируемые для клавиш цифровой клавиатуры.


Табл и ца 3 . 1 3 .
Когда коды опроса преобразуются в АSСll-коды, специальным клавишам присваиваются
«ПСевдоАSСll-КОДЫ», большие OxFF
Клавиша Код опроса «ASCl l >• ЕSС-последовательность
Ноте 71 Ох1 01 ESC [ Н
Up 72 Ох1 03 ESC [ А
PgUp 73 Ох1 07 ESC [ V
74 Ох1 0А ESC [ S
Left 75 Ох1 05 ESC [ D
5 76 Ох1 09 ESC [ G
Right 77 Ох1 06 ESC [ С
+ 78 Ох1 0В ESC [ Т
End 79 Ох1 02 ESC [ У
Down 80 Ох1 04 ESC [ В
PgDn 81 Ох1 08 ESC [ U
lns 82 Ох1 0С ESC [ @
Функция make_break преобразует коды опроса в АSСП-коды и обновляет зна­
чение переменных, отслеживающих значения модификаторов. Но перед этим она
ищет �волшебную� комбинацию клавиш Ctrl+Alt+Del, которую все пользователи
РС знают как способ принудительной перезагрузки MS- DOS. Обратите внима­
ние на комментарий о том, что это действие было бы лучше выполнить на более
низком уровне. Тем не менее простота обработки прерываний в MINIX 3 в про­
странстве ядра делает обнаружение нажатия этого сочетания невозможным, когда
уведомление о прерывании послано, но код опроса клавиатуры еще не считан.
Желательным является штатное завершение работы системы, поэтому вместо
обращения к подпрограммам РС B I O S выполняется вызов ядра sys_k i l l , ини­
циирующий передачу сигнала S I GK I L L процессу i n i t - родителю всех ос­
тальных процессов (строка 15448). Ожидается, что i n i t обработает этот сигнал
и в нормальном порядке завершит работу системы, прежде чем вернуться в мо­
нитор начальной загрузки, из которого можно управлять либо полным переза­
пуском системы, либо перезагрузкой MINIX 3.
Конечно, было бы неправильно ожидать, что этот механизм будет работать все­
гда. Большинство пользователей понимают опасность внезапной перезагрузки
и не прибегают к клавишам Ctrl+Alt+Del до тех пор, пока не произойдет что-то
действительно серьезное, после чего управлять системой будет невозможно.
К этому моменту может сложиться ситуация, когда правильно отправить сигнал
другому процессу уже невозможно. Именно потому в make_break есть статиче­
ская переменная CAD_c ount . При большинстве сбоев система обработки преры­
ваний продолжает работать, значит, клавиатурный ввод продолжает поступать,
и таймерное задание остается работоспособным. MINIX 3 учитывает поведение
394 Глава 3 . Ввод-вывод

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


ют в сердцах нажимать все подряд. Если попытка отправить сигнал S I GABRT
процессу ini t не удается, и пользователь обращаетсяся к магической комбина­
ции Ctrl+Alt+Del во второй раз, делается прямой вызов функции wreboo t , кото­
рая принудительно возвращает управление монитору начальной загрузки.
Основная часть make_b r e ak устроена не сложно. В переменную make запи­
сывается признак, было ли прерывание вызвано нажатием или отпусканием кла­
виши, после чего в переменную ch помещается АSСП-код, возвращаемый функ­
цией map_key. Далее следует инструкция swi t ch, проверяющая значение ch
(строки 15460- 1 5499). Рассмотрим два случая: случай обычной клавиши и слу­
чай специальной клавиши. Если нажата обычная клавиша, ни одно из условий
в swi t ch не будет выполнено, в варианте по умолчанию также ничего не про­
изойдет, поскольку обычные символы принимаются только при нажатии клави­
ши. Если каким-то образом обычная клавиша была воспринята при отпускании,
ее код заменяется значением - 1 , которое игнорируется вызывающей функцией
kb_re ad. Специальная клавиша, например Ctrl, обнаруживается в соответствую­
щем месте инструкции swit ch, в данном случае - в строке 1 546 1 . Состояние
make фиксируется в надлежащей переменной ( c t r l ) , а в качестве кода символа
возвращается (и игнорируется) - 1 . Обработка колов ALT, CALOCK, NLOCK и SLOCK
сложнее, но во всех этих вариантах действия похожи: в переменную записывает­
ся либо новое состояние модификатора (если модификатор действует только то­
гда, когда удерживается клавиша), либо инвертированное старое состояние (для
клавиш наподобие Caps Lock).
Нужно рассмотреть еще один вариант, код ЕХТКЕУ и переменную e s c . Не пу­
тайте этот случай с клавишей Esc на клавиатуре, которой соответствует код оп­
роса Ох1 В. Код ЕХТКЕУ нельзя сгенерировать, нажав какую-либо клавишу или
их комбинацию. Это префикс расширенных клавиш для клавиатур РС, первый
байт двухбайтового кода опроса, означающего, что передаваемый далее код оп­
роса не является частью обычного набора клавиш РС. Во многих случаях про­
граммное обеспечение интерпретирует обычный и расширенный коды одинаково.
Например, это почти всегда так для обычной клавиши / и клавиши / на цифро­
вой клавиатуре. В других случаях может потребоваться различать их нажатия.
Так, во многих раскладках клавиатур для языков, отличных от английского, ле­
вая и правая клавиши Alt интерпретируются по-разному, позволяя вводить три
разных символа с помощью одной клавиши. Код опроса у обеих клавиш одина­
ков и равен 56, но при нажатии правой клавиши Alt код опроса предваряется ко­
дом ЕХТКЕУ. Когда получен код ЕХТКЕУ, устанавливается флаг e s c, и в этом
случае функция make_break выполняет возврат прямо из инструкции swit ch,
тем самым обходя операторы в конце функции, записывающие в переменную e s c
нулевое значение. В результате e s c действует только н а один символ, который
должен быть получен следующим. Если вы знакомы с особенностями обычного
программирования ввода с клавиатур РС, то это также будет вам понятно, хотя
и немного странно, так как BIOS РС не позволяет считывать код опроса для кла­
виши Alt и возвращает другое значение для расширенного кода, нежели MINIX 3.
3 . 8 . Терми налы 395

Функция s et_leds (строка 1 5508) управляет светодиодами на клавиатуре, ин­


дицирующими состояние клавиш N u m Lock, Caps Lock и Scroll Lock. Чтобы ука­
зать клавиатуре, что следующий записанный в порт байт управляет индикато­
рами, в порт записывается управляющий байт, LED_CODE. Состояние всех трех
светодиодов кодируется тремя битами этого байта. Разумеется, данные операции
выполняются вызовами ядра, обращающимися к системному заданию для за­
писи в выходные порты. Поддержка осуществляется с помощью двух функций.
Функция kb_wa i t (строка 1 5530) вызывается тогда, когда необходимо опреде­
лить момент готовности клавиатуры к получению управляющей последователь­
ности, а kb_ack (строка 1 5552) проверяет, что команда подтверждена. Обе эти
команды используют механизм активного ожидания, непрерывно считывая со­
стояние до тех пор, пока не получено нужное значение. Такая методика не реко­
мендуется для операций ввода-вывода, но переключение индикаторов не должно
происходить часто, поэтому временные издержки не так велики. Заметьте, что
и kb_wa i t , и kb_ac k могут завершиться неудачей, что видно из кода возврата
функций. Число повторных попыток ограничено счетчиком цикла. Правда, пере­
ключение индикаторов на клавиатуре - не самая важная задача, поэтому возвра­
щаемое значение не проверяется и s e t_l eds выполняется .�:вслепую�.
Так как клавиатура является составной частью консоли, ее подпрограмма инициа­
лизации, kb_init (строка 1 5572), вызывается из s c r_ini t в файле cons o l e . с ,
а не напрямую из t ty_ i n i t в t ty . c . Если включены виртуальные консоли
(то есть константа NR_CONS в inc lude / m i n i x / c o n f i g . h больше 1 ) , kb_init
вызывается для каждой логической консоли. Следующая функция, kb_ini t_
onc e (строка 1 5583), вызывается всего один раз, что и отражено в ее названии.
Она задает состояние индикаторов на клавиатуре и сканирует клавиатуру, чтобы
удостовериться, что не считано никаких остаточных нажатий клавиш. Затем
инициализируются два массива, fkey_obs и s fkey_ob s, предназначенные для
связывания функциональных клавиш с процессами, которые должны на них реа­
гировать. Когда все готово, выполняются два вызова ядра, sys_irqs etpo l i cy
и sys_irqenaЫ e, задающие запросы на прерывание от клавиатуры и автома­
тическое разрешение прерываний. Таким образом, t ty_t a s k будет получать
уведомление при каждом нажатии и отпускании клавиши.
Хотя рассмотрением работы функциональных клавиш мы займемся чуть позже,
сейчас вполне целесообразно изучить массивы fkey_obs и s fkey_obs . Каждый
из них содержит 1 2 элементов, соответствующих 1 2 функциональным клавишам,
которыми оснащены современные персональные компьютеры. Первый массив
предназначен для немодифицированных функциональных клавиш, а второй -
для функциональных клавиш, нажатых одновременно с Shift. Массивы включают
элементы типа obs_t , представляющего собой структуру, хранящую номер про­
цесса и целое число. Структура и сами массивы объявлены в файле keyboard . с
в строках 1 5279- 1528 1 . При инициализации структуры ее полю proc_nr при­
сваивается значение, определенное как NONE и указывающее на то, что она не
используется. Значение NONE лежит за пределами, допустимыми для номеров
процессов. Обратите внимание на то, что номер процесса - это не его иденти­
фикатор, а индекс в таблице процессов. Возможно, терминология несколько
396 Глава 3. Ввод- вывод

запутанна, однако для передачи уведомления используется именно номер, а не


идентификатор процесса, поскольку номера процессов индексируют таблицу pri v,
разрешающую процессам принимать уведомления. Целочисленная переменная
event s также имеет нулевое начальное значение и применяется для подсчета
количества событий.
Следующие три функции довольно просты. Подпрограмма kbd_l oadmap (стро­
ка 15610) почти тривиальна. Она вызывается из do_ i o c t l файла t ty . с с це­
лью скопировать карту клавиш из пользовательского адресного пространства.
При этом новая раскладка записывается поверх раскладки по умолчанию, сгене­
рированной благодаря тому, что исходный файл раскладки включен в начало
файла keyboard . с .
С первого выпуска операционная система MINIX обеспечивала генерацию дам­
пов различной системной информации и другие специальные действия по нажа­
тию функциональных клавиш на консоли. Другие операционные системы, как
правило, не делают этого, однако система MINIX всегда бьша средством обучения,
стимулируя пользователей к экспериментам и предоставляя им дополнительные
возможности отладки. Во многих случаях вывод информации по нажатию функ­
циональных клавиш поддерживается даже при крахе системы. В табл. 3 . 1 4 пред­
ставлены функциональные клавиши и выполняемые ими действия.

Таблица 3 . 1 4. Функциональные клавиши, обнаруживаемые функцией fuпc_key


Кпавиwа Действие
F1 Вывод таблицы процессов ядра
F2 Вывод информации о занятой процессом памяти
FЗ Вывод загрузочного образа
F4 Вывод привилегий процессов
F5 Вывод параметров монитора загрузки
F6 Вывод информации о прерываниях
F7 Вывод сообщений ядра
F1 0 Вывод параметров ядра
F1 1 Вывод таймерной информации
F1 2 Вывод информации об очередях
SF1 Вывод таблицы процессов менеджера процессов
SF2 Вывод сигналов
SFЗ Вывод таблицы процессов файловой системы
SF4 Отображение устройства/драйвера
SF5 Отображение клавиш печати
SF9 Вывод Еthеrпеt-статистики (только для RTL81 39)
CF1 Отображение клавиш вывода
СFЗ Переключение между программной и аппаратной прокруткой
CF7 Отправка сигнала SIGQUIT, тот же эффект дает нажатие клавиш Ctrl+\
CF8 Отправка сигнала SIGINT, тот же эффект дает нажатие клавиши Del
CF9 Отправка сигнала SIGКILL, тот же эффект дает нажатие клавиш Ctrl+U
3. 8 . Терминалы 397

Нажатие функциональных клавиш поодиночке или совместно с клавишей Shift


приводит к появлению событий, которые невозможно обработать с помощью
драйвера терминала. Эти события приводят к передаче уведомлений драйверам
и серверам. Поскольку загрузка, включение и выключение драйверов в MINIX 3
возможны в процессе работы операционной системы, статическая привязка к ним
клавиш во время компиляции вряд ли является удачным решением. Для поддерж­
ки изменений в реальном времени подпрограмма t ty_t a s k принимает сообще­
ния типа FKEY_CONTROL, а обработкой запросов занимается функция do_fkey_
c t l (строка 1 5 6 2 4). Существует три типа запросов: FKEY_MAP, FKEY_UNMAP
и FKEY_EVENT S . Первые два соответственно назначают и отменяют функцио­
нальную клавишу процессу, указанному в битовой карте сообщения. Третий
запрос возвращает битовую карту нажатых клавиш, связанную с вызывающим
процессом, и очищает поле event s для этих событий. Информационный сервер
( Information Server, IS) инициализирует параметры процессов в загрузочном обра­
зе, а также участвует в генерации откликов. Тем не менее драйверы также могут
быть связаны с функциональной клавишей и реагировать на нее. Как правило,
это имеет место для Ethernet-дpaйвepoв, сбрасывающих дампы со статистикой
пакетов, которые полезны при разрешении сетевых проблем.
Функция func_key (строка 157 1 5 ) вызывается: из kb_r ead для того, чтобы оп­
ределить, имеются ли нажатые клавиши для локальной обработки. Это действие
выполняется каждый раз и в первую очередь при получении кода опроса. Если
клавиша не является функциональной, то как минимум три сравнения выполня­
ются прежде, чем управление возвращается kb_r ead. Если же клавиша являет­
ся функциональной и связана с процессом, последнему посылается уведомление.
В случае когда процесс связан с единственной клавишей, само уведомление ука­
зывает ему, что делать. Если же процесс связал себя с несколькими функцио­
нальными клавишами, требуется диалог: процесс посылает запрос FKEY_EVENTS
драйверу терминала, который обрабатывает его с помощью подпрограммы do_
f k ey _c t l и информирует источник о том, какие клавиши являются активными.
Затем процесс может вызвать процедуру обработки для каждой нажатой клавиши.
Функция s c an_k eyboard (строка 1 5800) взаимодействует с аппаратным ин­
терфейсом клавиатуры, считывая и записывая байты из портов ввода-вывода.
Контроллер клавиатуры информируется о том, что символ был считан последо­
вательностью команд в строках 1 5809- 1 5 8 1 0. Эти команды считывают байт, за­
тем записывают его обратно с установленным старшим битом, а затем перезапи­
сывают его еще раз со сброшенным битом. В результате те же самые данные при
следующем чтении не встречаются. В этой функции не делается никаких прове­
рок, что не важно, так как функция s c an_keyboard вызывается только из обра­
ботчика прерываний.
Завершает файл k eyboard . c функция do_pani c_dump s (строка 15819). Вы­
зываемая в результате краха системы, она дает пользователю возможность про­
смотреть отладочную информацию при помощи функциональных клавиш. Цикл
в строках 1 5830 - 1 5854
- еще один пример активного ожидания. Клавиатура
непрерывно опрашивается до тех пор, пока не нажата клавиша Esc. Безусловно,
398 Глава 3 . Ввод- вывод

никто не может заявить, что после сбоя, когда ожидается команда на перезагруз­
ку, имеет смысл применять более эффективные методики. Внутри цикла редко
используемая неблокирующая операция приема nb_re c e i ve служит для прие­
ма сообщений и проверки ввода с клавиатуры одного из вариантов, предложен­
ных в сообщении:
H i t ESC to reboo t , DEL to shut down , F - keys f o r debug dumps

В следующем разделе мы рассмотрим код функций do_newkrne s s и do_


d i agno s t i c s .

3 . 8 . 6 . Реал изация драй вера экрана


Если имеется достаточно видеопамяти, экран IBM РС можно сконфигурировать
как несколько виртуальных терминалов. В этом разделе рассматривается аппа­
ратно-зависимый код консоли. Кроме того, описываются подпрограммы вывода
отладочной информации, взаимодействующие с клавиатурой и дисплеем на низ­
ком уровне. Эти средства предоставляют ограниченные возможности для взаи­
модействия с пользователем, но работают даже тогда, когда другие компоненты
системы не функционируют и могут дать полезную информацию даже после
практически тотального краха системы.
Специфичный для отображаемого на память экрана РС код расположен в файле
c onsol e . с. Здесь объявляется структура cons o l e (строки 1598 1 - 1 5998), кото­
рая по своему смыслу является расширением структуры t ty из файла t ty . с .
При инициализации в поле tp - > t ty_p r iv структуры t ty для консоли записы­
вается указатель на принадлежащую этой консоли структуру c ons o l e. Первое
поле структуры является указателем, содержащим обратную ссылку на структу­
ру t ty. Остальные компоненты структуры содержат вполне ожидаемую для ви­
деоустройства информацию: переменные, хранящие текущие координаты курсо­
ра, адрес начала области памяти, отведенной для дисплея, и ее размер, адрес, на
который ссылается регистр базы чипа контроллера, текущий адрес курсора.
Другие переменные требуются для работы с Е S С-последовательностями. Так
как символы принимаются в виде 8-разрядных байтов и перед выводом в видео­
память должны быть скомбинированы с байтом атрибутов, блок данных подго­
тавливается к передаче в массиве c_rarnqueue, объема которого достаточно для
хранения 80 1 6-разрядных пар <�:символ-атрибут». Каждой виртуальной консоли
требуется собственная структура c ons o l e, для хранения которой выделяется
область памяти в c ons_t aЫ e (строка 1 600 1 ) . Как и в случае t ty и других
структур, мы обычно будем ссылаться на поля c ons o l e через указатель, на­
пример: cons - >c_t ty.
Для каждой консоли в поле tp - > t ty_devwr i t e хранится адрес функции c ons_
wr i t e (строка 1 6036). Она вызывается только из одного места - обработчика
handl e_event s в файле t ty . с. Большая часть остальных функций в cons ol e . с
призвана обеспечивать работу функции c ons_wr i t e. Когда она вызывается
впервые после того, как процесс-клиент сделал вызов wr i t е , выводимые данные
располагаются в буфере в адресном пространстве этого процесса. Определить
3 . 8 . Терми налы 399

положение буфера можно при помощи полей tp - > t ty_outproc и tp - >out_


v i r структуры t ty. Поле tp - > t ty_out l e f t говорит о том, сколько символов
должно быть передано, а поле tp - > t ty_out c um изначально равно нулю, сооб­
щая о том, что ни одного символа еще не выведено. Это - обычная ситуация
для функции c ons_wr i t e, так как она выводит все данные, указанные в вызове.
Но если пользователь хочет замедлить процесс вывода, он может ввести с кла­
виатуры символ STOP (Ctrl+S), который приводит к тому, что устанавливается
флаг t p - > t ty_inh i Ь i t ed. Когда этот флаг установлен, cons_wr i t e немед­
ленно возвращает управление, даже если вызов wr i t e еще не выполнен пол­
ностью. Причем handl e_event s продолжит вызывать c ons_wr i t e и когда
флаг tp - >t ty_inh i Ь i t e d будет сброшен, а для этого необходимо ввести сим­
вол S TART (Ctrl+Q), прерванная передача данных возобновится.
Первым аргументом c ons_wr i t e является указатель на структуру t ty для дан­
ной консоли, поэтому первым действием инициализируется указатель c ons , со­
держащий адрес структуры cons o l e (строка 1 6049). Затем необходимо прове­
рить, действительно ли есть какая-либо работа, так как handl e_event s всегда
вызывает cons_wr i t e. Если нет, функция быстро завершается (строка 1 6056).
После этого она входит в свой главный цикл (строки 1 606 1 - 1 6089). Этот цикл
очень похож на цикл функции i n_t r an s f e r в файле t ty . с . При помощи вы­
зовов sys_v i r c opy ядра локальный буфер объемом 64 символа заполняется
данными из пользовательского буфера, обновляются указатель на начало буфера
и счетчик символов, после чего все символы из локального буфера переносятся
в массив c ons - >c_ramqueue, дополненные байтом атрибутов. Позже эти дан­
ные выводятся на экран с помощью функции f l u sh.
Как вы могли видеть на рис. 3.23, существуют несколько способов осущест­
вить эту передачу. Можно вызывать ou t_char для каждого символа, но лишь
в расчете на то, что при выводе символов не потребуется ни одна из специаль­
ных функций out_char, не выводится ЕSС-последовательность, ширина экрана
не превышена и массив c ons - > c_r amqu eue не заполнен. Если все функции
out_char не требуются, символ можно поместить в массив cons - >c_ramqueue
напрямую, вместе с байтом атрибутов (поле c ons - >c_a t t r ) , а все переменные
c ons - > c_rwords (индекс в очереди), c ons - > c_c o l umn (отслеживает текущий
столбец на экране) и tbu f (указатель на буфер) инкрементируются. Прямой пе­
ренос символов в c ons - > c_r amqu eu e соответствует штриховой линии с левой
стороны рис. 3.23. При необходимости вызывается функция ou t_char (стро­
ка 1 6082 ) . В этом вызове выполняются все подсчеты и по мере надобности
вызывается функция f l u sh, которая обеспечивает окончательную передачу дан­
ных в видеопамять.
Перекачка данных из пользовательского буфера в локальный производится до
тех пор, пока значение в поле tp - > t ty_out l e f t говорит о наличии символов
и не установлен флаг tp - > t ty_inh i Ь i t ed. Если передача останавливается, в ре­
зультате завершения вызова wr i t e или потому, что установлен флаг tp - >t ty_
inhiЬi t ed, то чтобы передать последние символы из очереди в память экрана,
опять вызывается функция f l u sh. Когда операция завершена (то есть проверка
400 Глава 3. Ввод- вы вод

показывает, что поле tp - > t ty_ou t l e f t содержит нулевое значение), при по­
мощи t ty_rep ly отправляется ответное сообщение (строки 1 6096- 1 6097).
В дополнение к вызовам c ons_wr i t e из handl e_event s , символы на консоль
могут выводить функции echo и rawecho в аппаратно-независимой части драй­
вера терминала. Если текущим устройством вывода является консоль, косвен­
ные вызовы через указатель tp - > t ty_echo перенаправляются функции cons_
echo (строка 1 6 1 05). Она делает свою работу, вызывая out_char и затем f l u sh.
Пользователь вводит данные символ за символом, и ему предпочтительно, чтобы
эхо отображалось сразу же, без видимой задержки, поэтому помещать символы
в очередь было бы неправильно.
Итак, теперь мы добрались до функции out_char (строка 1 6 1 19). Сначала она
проверяет, вводится ли ЕSС-последовательность, вызывая p ar s e_e s c ap e, и ес­
ли это так, немедленно завершается (строки 1 6 1 24 - 1 6 126). В противном случае
управление передается в конструкцию swi t ch, которая проверяет различные
особые случаи: нулевой символ, символ забоя, символ звонка и т. п. Несложно
проследить, как обрабатывается большая часть этих ситуаций. Самые сложные
варианты - символы перевода строки и табуляции, поскольку они сложным об­
разом меняют координаты курсора на экране и могут вызва:rь прокрутку. По­
следняя проверка выполняется на код E S C . Если он обнаруживается, устанавли­
вается флаг c ons - >c_e s c_s t at e (строка 1 6 1 8 1 ) и последующие вызовы out_
char перенаправляются в p ar s e_e s c ap e до тех пор, пока последовательность
не завершится. Вариант по умолчанию выполняется для печатных символов.
Если превышена ширина экрана, при необходимости делается прокрутка экрана
и вызывается f l u sh. Прежде чем поместить символ в выходную очередь, прове­
ряется, заполнена ли она, и если заполнена, вызывается f l u sh. Как мы видели
ранее при описании функции c ons_wr i t e, в случае занесения символа в оче­
редь необходимо учесть это, обновив значения нескольких переменных.
Далее следует функция s c r o l l_s c r e en (строка 16205). Она выполняет как про­
крутку вверх, то есть нормальную прокрутку, ожидаемую при заполнении нижней
строки экрана, так и прокрутку вниз, которая необходима при попытке устано­
вить курсор выше верхней границы экрана. Для каждого направления прокрутки
возможны три метода. Это проистекает из требования поддержки различных
типов видеокарт.
Мы рассмотрим случай прокрутки вверх. Сначала переменной chars присваивает­
ся значение, равное размеру экрана минус 1 . Когда прокрутка делается программ­
но, для ее выполнения достаточно одного вызова функции v i d_v i d_c opy, ко­
торая копирует chars символов на одну строку ниже в памяти. Эта функция
умеет переходить в начало области при достижении ее конца, и наоборот. Так,
если ей указано скопировать блок памяти, начало которого выходит за верхнюю
границу памяти, то не укладывающиеся в область видеопамяти данные берутся
из ее нижней части, то есть видеопамять рассматривается как циклический мас­
сив. Простота этого вызова не компенсирует низкую скорость операции. Несмот­
ря на то что подпрограмма v i d_v i d_c opy написана на языке ассемблера (ее
код хранится в файле dr iver s / t ty / v i dc opy . s ) , для ее выполнения необхо­
димо скопировать 3840 байт, что довольно много даже для ассемблерного кода.
3. 8 . Терми налы 40 1

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


жет включить ее, если аппаратная не работает или по каким-то причинам неже­
лательна. Одна из таких причин - желание использовать команду s c r e endump,
чтобы иметь возможность сохранить экранную память в файле или просматри­
вать дисплей главной консоли при работе с удаленного терминала. При аппарат­
ной прокрутке эта команда не дает ожидаемого результата, так как начало видео­
памяти наверняка не совпадает с началом видимой на экране области.
В первой части составного условия (строка 1 6226) проверяется значение пере­
менной wrap. Эта переменная содержит TRUE для старых экранов, поддержи­
вающих аппаратную прокрутку, и если проверка не выполняется, в ветви e l s e
производится простая аппаратная прокрутка (строка 1 6230). Соответственно,
значение указателя на начало экранной области, используемого видеоконтролле­
ром, cons - >c_o r i g, изменяется так, чтобы указывать на первый символ той
строки, которая окажется наверху экрана. Если значение wrap равно FAL SE,
проверка составного условия продолжается. В этом случае проверяется, помес­
тится ли перемещаемый блок памяти в области памяти, отведенной для консоли.
Если нет, то вызовом v i d_v i d_c opy содержимое копируется физически в на­
чало области видеопамяти. Если же адреса не перекрываются, делается простая
аппаратная прокрутка, всегда практикуемая в более старых видеоконтроллерах.
Для этого изменяется значение cons - >c_org, и новое значение указателя на
начало области заносится в нужный управляющий регистр контроллера. Соот­
ветствующий вызов делается позднее, как и вызов, очищающий нижнюю строку
экрана для достижения эффекта <�:прокрутки�.
Код прокрутки вниз очень похож на тот, что прокручивает экран вверх. В конце
нижняя (или верхняя) строка экрана, на которую указывает переменная new_
l i ne, очищается вызовом mem_v i d_c opy, обновляются значения некоторых пе­
ременных и делается проверка того, что координаты курсора имеют приемлемые
значения. При необходимости, если, например, ЕSС-последовательность перемес­
тила курсор на столбец с отрицательным номером, координаты корректируются.
В завершение вычисляется, где должен быть курсор, и это значение сравнивает­
ся с cons - > c_cur. Если значения не совпадают, а обрабатываемая память при­
надлежит текущей виртуальной консоли, то чтобы записать корректные значе­
ния в регистр курсора контроллера, делается вызов подпрограммы s e t_6 8 4 5 .
Н а рис. 3.26 показано, как можно представить анализ ЕSС-последовательностей
при помощи конечного автомата. Этот автомат реализуется подпрограммой
pars e_e s c ape (строка 1 6293), вызываемой в начале кода out_char, если поле
cons - >c_e s c_s t a t e не равно нулю. Сам символ ESC обнаруживается в out_
char, и переменная c o n s - >c_e s c_s t a t e переводится в состояние 1 . Когда
получен следующий символ, функция par s e_e s c ape подготавливается к обра­
ботке дальнейшей информации: значение ' \ О ' заносится в поле c ons - >c_e s c_
int ro, указатель на начало массива параметров, c ons - >c_e s c_paramv [ О J , за­
носится в co ns - > c_e s c_par amp, а сам массив параметров заполняется нуля­
ми. Затем проверяется первый символ, следующий за E S C . Допустимыми зна­
чениями являются [ и м. В первом случае символ [ копируется в переменную
402 Глава 3. Ввод- вывод

cons - >c_e s c_intro, и автомат переходит в состояние 2. Во втором случае вы­


зывается функция do_e s c ap e , которая выполняет действие, и автомат возвра­
щается в состояние О. Если же за E SC следует недопустимый символ, он игнори­
руется, а дальше все обрабатывается, как обычно.

c_esc_state =1
Число или ";"
Не " Г'
Вызов
do_escape

Вызов
do_escape Накопление
числовых
аргументов
Рис. 3 . 26 . Конечный автомат для обработки ЕSС-последовательностей

Когда автомат получает последовательность E S C [ , следующий полученный


символ обрабатывается в состоянии 2. В этой точке возможны три варианта.
Если на входе числовой символ, его значение добавляется к увеличенному в де­
сять раз значению параметра, на который в текущий момент указывает c ons - >
c_e s c_paramp (сначала этот указатель ссылается на c ons - >c_e s c_p aramv [ О ] ,
и все параметры равны нулю). Здесь состояние автомата не меняется. Это по­
зволяет передавать параметры в виде последовательности чисел, накапливая их
итог, хотя максимальное значение, в текущий момент распознаваемое MINIX,
равно 80. Оно может быть использовано в последовательности, перемещающей
курсор в указанную позицию на экране (строки 1 6335 - 1 6337). Если получено не
число, а точка с запятой, тогда указатель на текущий параметр перемещается
к следующему параметру, чтобы начать считывание в нечисловых значений ( стро­
ки 1 6339 - 1 634 1 ). Благодаря такому подходу, если изменить константу МАХ_
ES C_PARМS в сторону массива большего объема, код менять не придется. Нако­
нец, в третьем случае, когда получен символ, не являющийся ни числом, ни точ­
кой с запятой, вызывается do_e s c ape.
Функция do_e s c ap e (строка 1 6352) весьма объемна, несмотря на относительно
скромную поддержку ЕSС-последовательностей в MINIX 3. Но при любом объе­
ме код должен быть достаточно простым. После того как делается вызов f l ush,
нужно убедиться, что содержимое экрана полностью обновлено. Инструкция i f
выполняет простую проверку, является ли следующий за E SC символ преамбу­
лой ЕSС-последовательности или нет. Если нет, допустимо только одно дейст­
вие - перемещение курсора на строку вверх, этому соответствует ЕSС-последо­
вательность ESC м. Обратите внимание, что проверка значения осуществляется
в инструкции swi t ch в расчете на появление новых вариантов, то есть новых
последовательностей, не соответствующих формату ESC [ . Поэтому обрабатывает­
ся вариант без преамбулы типичным для многих последовательностей образом:
3 . 8 . Терми налы 403

по значению переменной c ons - > c_row определяется, необходима ли прокрут­


ка. Если курсор находится в нулевой строке, делается вызов s c r o l l_s c r e en
с параметром SCROLL_DOWN. Если нет, курсор просто сдвигается на одну строку
вверх, для чего переменная c ons - > c_row уменьшается на 1 и вызывается f lush.
Если обнаружена преамбула ЕSС-последовательности, срабатывает другая ветвь
инструкции i f (строка 1 6377 ). Сначала проверяется, не символ [ ли это, то есть
единственная возможная преамбула, обрабатываемая в текущий момент в MINIX 3.
Если символ корректен, в переменную va l u e записывается значение первого
полученного параметра, или ноль, если параметров нет (строка 1 6380). Если по­
следовательность некорректна, ничего не происходит, за исключением того, что
swi t ch с большим телом (строки 1 638 1 - 1 6586) пропускается, а состояние авто­
мата сбрасывается в О перед вызовом do_e s c ape. В более интересном случае,
когда последовательность правильна, выполняется инструкция swi t ch. Все воз­
можные варианты мы рассматривать не будем. Вместо этого обсудим только
наиболее характерные типы действий, управляемых ЕSС-последовательностями.
Первые пять последовательностей без числовых аргументов на клавиатурах IBM
РС генерируются клавишами со стрелками и клавишей Home. Две последователь­
ности, ESC [ А и ES C [ В, сходны с E SC М, с той разницей, что числовой параметр
позволяет перемещаться более чем на одну строку, а при достижении границ эк­
рана содержимое не прокручивается. Функция f l ush в этом случае обнаружи­
вает попытки передвинуть курсор за границы экрана и ограничивает его переме­
щение. Две другие последовательности, ESC [ с и ESC [ D, перемещающие курсор
вправо и влево, аналогичным образом ограничены функцией f l u sh. Когда они
генерируются клавишами управления курсором, числовой аргумент не передает­
ся, поэтому происходит перемещение на одну строку или один столбец.
Далее, последовательность E S C [ Н может иметь два числовых параметра, на­
пример ESC [ 2 О ; 6 ОН. Эти параметры задают положение курсора в абсолютных
координатах, а не относительно предыдущего места расположения, и для пра­
вильной интерпретации преобразуются из координат, отсчитываемых с 1, в ко­
ординаты с началом в О. Клавиша Home на клавиатуре генерирует последова­
тельность без параметров (с параметрами по умолчанию), которая перемещает
курсор в положение ( 1 ; 1 ) .
Две следующие последовательности, E S C [ s J и E SC [ sк, очищают либо часть
строки, либо часть всего экрана, в зависимости от переданного параметра. В обоих
случаях подсчитывается количество символов. Например, для последовательно­
сти ESC [ lJ в c ount заносится количество символов от начала экрана до теку­
щего положения курсора, и это количество и параметр положения, d s t , который
может быть равен началу экрана, c ons - > c_org, используются как аргументы
для вызова rnern_v i d_c opy. Аргументы процедуры таковы, что она заполняет
указанную область экрана текущим цветом фона.
Четыре следующие последовательности вставляют новые строки и удаляют стро­
ки и пробелы в текущем местоположении курсора, и их работа не нуждается в де­
тальном рассмотрении. Последний вариант, последовательность ESC [ nrn (обратите
внимание, что п это числовой аргумент, а rn литера, часть последовательности),
- -
404 Глава 3. Ввод- вывод

оказывает влияние на параметр c ons - > c_at t r. Это - байт атрибутов, который
при записи символов в видеопамять чередуется с кодами символов.
Функция s e t_6 8 4 5 (строка 1 6594) вызывается тогда, когда необходимо об­
новить информацию видеоконтроллера. У контроллера 6845 есть внутренние
1 6-разрядные регистры, которые программируются по 8 бит за раз. Поэтому для
записи одного регистра требуются четыре операции с портами ввода-вывода.
В каждой из них создается массив (вектор) пар (порт, значение) и осуществляется
вызов ядра sy s_voutb, обращающийся к системному заданию для выполнения
ввода-вывода. Некоторые регистры микросхемы видеоконтроллера 6845 пере­
числены в табл. 3 . 1 5 .

Таблица З . 1 5 . Некоторые из регистров микросхемы 6845


Регистры Назначение
1 0-1 1 Размер курсора
1 2-13 Начальный адрес видимой части экрана
1 4-15 Положение курсора
Следующая функция, get_6 8 4 5 (строка 16613), возвращает значения доступных
для считывания регистров видеоконтроллера. Для этого она пользуется вызова­
ми ядра. В текущем коде MINIX 3 эта функция не вызывается, однако может
быть полезна для расширений в будущем (например, для добавления графиче­
ской поддержки).
Функция Ьеер (строка 1 6629) вызывается при нажатии клавиш Ctrl+G. Она по­
дает на внутренний динамик прямоугольный сигнал, опираясь на встроенные
аппаратные возможности РС. Звук появляется путем некоторых магических ма­
нипуляций с портами ввода-вывода, которые интересны только программистам
на ассемблере. Более интересно то, что сигнал выключается при помощи уведом­
ления. Будучи процессом с системными привилегиями (такими как запись в таб­
лице priv ) , драйвер терминала может установить таймер с помощью библиотеч­
ной функции trnr s_s e t t irne r s . Это делается в строке 1 6655, а следующая
функция, s t op_beep, вызывается после истечения таймера. Этот таймер �;:та­
вится в собственную очередь драйвера терминала. Вызов ядра sys_s e t a l arrn,
следующий далее, обращается к системному заданию для установки таймера в яд­
ре. По его истечении главный цикл драйвера терминала, t ty_t a s k, обнаружива­
ет сообщение SYN_ALARМ и вызывает функцию exp i re_t irne r s . Последняя об­
рабатывает все таймеры, принадлежащие драйверу терминала, один из которых
установлен функцией Ьеер.
Адрес следующей подпрограммы, s t op_beep (строка 1 6666), помещен функ­
цией Ьеер в поле trnr_ func таймера. Она выключает сигнал после истечения
заданного интервала времени и сбрасывает флаг beep ing. Это предотвращает
обработку лишних обращений к функции генерации сигнала.
Подпрограмма s c r_init вызывается из t ty_in i t столько раз, сколько указано
в NR_CONS . При каждом вызове аргументом подпрограммы является указатель
на структуру, один из элементов массива t ty_t aЫ e. В строках 1 6693 и 1 6694
3 . 8 . Терминалы 405

вычисляется значение l ine, будущий индекс в массиве c ons_t aЫ e, это значе­


ние проверяется на корректность, и если все правильно, оно используется для
инициализации указателя c o n s , ссылающегося на текущую запись в массиве
c ons_t aЫ e. К этому моменту поле c ons - >c_t ty может быть инициализиро­
вано указателем на главную структуру t ty для устройства, а в tp - > t ty_p r i v,
в свою очередь, может быть записан указатель на структуру c ons o l e_t устрой­
ства. Затем для инициалиэации клавиатуры вызывается подпрограмма kb_ini t
и устанавливаются указатели на специфичные для данного устройства подпро­
граммы. После этого tp - > t ty_devwr i t e ссылается на c ons_wr i t e, а tp - >
t ty_echo содержит указатель на c ons_echo. Далее из BIOS извлекается адрес
ввода-вывода регистра базы видеоконтроллера и в соответствии с типом ви­
деоконтроллера устанавливается флаг wrap, определяющий способ прокрутки
(строки 1 6708- 1 673 1 ) . Затем в глобальной таблице дескрипторов запоминается
дескриптор сегмента области видеопамяти (строка 1 6735).
В дальнейшем происходит инициализация виртуальных консолей. При инициа­
лизации каждой консоли с разным значением tp вызывается s c r_ini t, и таким
образом, для каждой консоли в s c r_ini t используются собственные значения
l ine и c ons (строки 1 6750 - 1 6753), и каждая консоль �арендует• собственную
область видеопамяти. Затем каждый экран очищается, и, наконец, консоль с ну­
левым номером становится активной.
Ряд подпрограмм отображает вывод от имени драйвера терминала, ядра или дру­
гого системного компонента. Первая, kpu t c (строка 1 6775), просто вызывает
функцию putk, которая побайтно выводит текст. Она применяется здесь потому,
что библиотечная подпрограмма, предоставляющая возможность использования
pr in t f системным компонентам, связывается с одноименной подпрограммой
символьной печати, однако другие функции драйвера терминала рассчитаны на
подпрограмму с именем pu t k.
Функция do_new_krne s s (строка 1 6784) применяется для печати сообщений из
ядра. Здесь термин �сообщения• не самый удачный; он не имеет отношения к со­
общениям, обеспечивающим взаимодействие между процессами. Данная функ­
ция отображает на консоли текст, содержащий информацию, предупреждения
и сведения об ошибках.
Ядру необходим особый механизм отображения данных. Этот механизм должен
быть достаточно самостоятельным, чтобы работать во время загрузки, когда ком­
поненты операционной системы еще не функционируют, и во время сбоев, когда
основные компоненты могут быть недоступными. Ядро помещает текст в цик­
лический символьный буфер, входящий в структуру, содержащую указатели на
следующий записываемый байт и размер еще не обработанного текста. При по­
явлении нового текста ядро посылает драйверу терминала сообщение SYS_S IG,
и если главный цикл в процедуре t ty_t a s k функционирует, вызывается функ­
ция do_new_krne s s . При внештатной ситуации (например, в случае краха сис­
темы) сообщение S YS_S I G обнаруживается циклом процедуры do_pani c_
durnp s с помощью операции неблокирующего чтения, которую мы видели в фай­
ле keyboard . с, и функция do_new_krne s s вызывается оттуда. В обоих случаях
406 Глава 3. Ввод- вывод

вызов ядра sys_g e t kme s s ag e s получает копию структуры ядра, байты ото­
бражаются по одному с помощью функции putk, а последний вызов putk с ну­
левым символом приводит к сбросу вывода. Хранение позиции в буфере для
различных сообщений осуществляется с помощью локальной статической пере­
менной.
Функция do_di agno s t i c s (строка 1 6823) имеет назначение, схожее с do_
new_kme s s , однако обеспечивает вывод сообщений не для ядра, а для систем­
ных процессов. Сообщение типа D I AGNO S T I C S может быть получено главным
циклом функции t ty_t a s k либо циклом в функции do_p an i c_dump s . В обоих
случаях производится вызов do_d i agno s t i c s . Сообщение содержит указатель
на буфер вызывающего процесса и его размер. Локальная буферизация не ис­
пользуется; вместо этого текст побайтно доставляется путем многократных вы­
зовов sy s_v i r c opy ядра. Это защищает драйвер терминала; в случае если про­
изойдет сбой и процесс начнет генерировать большие объемы данных, проблема
переполнения буфера не сможет возникнуть. Вывод символов осуществляется
по одному функцией putk и завершается нулевым байтом.
Функция putk (строка 1 6850) печатает символы от имени кода драйвера терми­
нала и используется описанными функциями для вывода текста от лица ядра
или других системных компонентов. Она просто вызывает функцию ou t_char
для каждого полученного ненулевого символа и функцию f l ush для нулевого
символа, которым заканчивается строка.
Остальные процедуры в файле c ons o l e . с просты и невелики, поэтому мы не
потратим на них много времени. Функция t o gg l e_s c r o l l (строка 1 6869 )
делает именно то, что означает ее название, она изменяет значение флага типа
прокрутки: аппаратная или программная. Помимо этого, она выводит текст в те­
кущей позиции курсора, сообщая, какой режим выбран. Функция c ons_s t op
(строка 1 6869) инициализирует консоль, переводя ее в состояние, которое ожи­
дает монитор начальной загрузки. Это делается перед выходом из системы или
перезагрузкой. Функция c ons_orgO (строка 1 6893) используется только тогда,
когда нажатием клавиши FЗ изменяется режим прокрутки или идет подготовка
к выходу из системы. Функция s e l ec t_cons o l e (строка 1 69 17 ) выбирает (ак­
тивизирует) виртуальную консоль. При вызове ей передается индекс новой
консоли, и она дважды вызывает функцию s e t_6 8 4 5 , чтобы показать на экране
данные из соответствующей части видеопамяти.
Две последние подпрограммы в значительной степени зависят от особенностей
программного обеспечения. Функция c on_ l o a d f ont (строка 1 693 1 ) загружает
в видеоконтроллер шрифт, обеспечивая выполнение операции T I OC S FON вызова
i oc t l . Эта функция серией вызовов ga_p rogram (строка 1 697 1 ) делает так, что
становится видимой память шрифтов контроллера, которая в обычной ситуации
не адресуема. Затем, чтобы скопировать шрифт в ставшую доступной область
памяти, вызывается функция phy s_c opy, а после этого другая последователь­
ность команд возвращает устройство в нормальный режим работы.
Последняя функция, c ons_i o c t l (строка 1 6987), совершает лишь одно дейст­
вие: задает размер экрана. Она вызывается функцией s c r_ini t, используя зна-
Резюме 407

чения, полученные из BIOS. Если бы возникла необходимость вызова i oc t l для


изменения размеров экрана, в MINIX 3 пришлось бы добавить код для работы
с новыми параметрами.

Рез ю ме
Вводом-выводом часто пренебрегают, хотя он заслуживает более серьезного от-­
ношения. Значительная доля кода любой операционной системы связана с вво­
дом-выводом. Тем не менее драйверы устройств ввода-вывода часто становятся
причиной проблем. Как правило, драйверы создают программисты, работающие
в компаниях-производителях устройств. Традиционные операционные системы
предоставляют драйверам доступ к критически важным ресурсам, таким как пре­
рывания, порты ввода-вывода и память других процессов. В MINIX 3 драйверы
являются независимыми процессами с ограниченными привилегиями. По этой
причине ошибка в одном драйвере не приводит к краху всей системы.
Мы начали с рассмотрения аппаратного обеспечения ввода-вывода и связи уст­
ройств ввода-вывода с контроллерами. Именно такую связь создает программ­
ное обеспечение. Затем мы рассмотрели четыре уровня программного обеспече­
ния ввода-вывода: обработчики прерываний, драйверы устройств, независимое
от устройств программное обеспечение и библиотеки плюс спулеры, работаю­
щие в пользовательском пространстве.
Далее мы изучили проблему взаимной блокировки и инструментарий для борь­
бы с ней. Взаимная блокировка возникает, когда имеется группа процессов,
получивших монопольный доступ к некоторым ресурсам, и каждому процессу
в группе необходим помимо этого другой ресурс, принадлежащий другому процес­
су. В таком случае все процессы блокируются и не могут выполняться. Взаимную
блокировку можно исключить, если система построена в расчете на упреждение
подобной ситуации. Например, можно разрешить каждому процессу удерживать
не более одного ресурса в каждый момент времени. Другой способ избежать бло­
кировки - проверять каждый запрос, определяя, ведет ли он к опасной ситуации
(в которой возможно возникновение блокировки) и отменять или откладывать
опасные запросы.
В MINIX 3 драйверы устройств реализованы в виде процессов, встроенных в яд­
ро. Мы рассмотрели драйверы виртуального диска, жесткого диска и терминала.
У каждого из этих драйверов есть главный цикл, в котором принимаются и об­
рабатываются запросы, в конечном итоге формируется и отправляется ответное
сообщение о результатах. Исходный код главных циклов и общих функций драй­
веров помещен в единую библиотеку драйверов, хотя каждый драйвер компили­
руется и связывается с собственными копиями библиотечных процедур. Адресные
пространства драйверов индивидуальны. Множество различных терминалов, ис­
пользующих системную консоль, последовательные линии и сетевые соединения,
поддерживаются одним и тем же процессом драйвера терминала.
408 Глава 3. Ввод-вывод

Драйверы устройств по-разному взаимодействуют с системой прерываний. У ст­


ройства, которые могут выполнить свою работу быстро, например виртуальный
диск или отображаемый на память экран, вообще не прибегают к прерываниям.
У жесткого диска большая часть работы выполняется в самом коде драйвера, а об­
работчики прерываний возвращают информацию о состоянии. Прерывания всегда
ожидаемы, для чего можно прибегать к вызову r e c e i ve. Прерывание от кла­
виатуры может произойти в любой момент. Сообщения, сгенерированные всеми
прерываниями для драйвера терминала, принимаются и обрабатываются в глав­
ном цикле драйвера. При появлении прерывания от клавиатуры первый шаг
обработки ввода должен быть как можно более быстрым, чтобы подготовиться
к приему последующих прерываний.
Драйверы MINIX 3 обладают ограниченными привилегиями: они не могут обра­
батывать прерывания и самостоятельно пользоваться портами ввода-вывода. Пре­
рывания обрабатывает системное задание; оно посылает сообщение драйверу,
чтобы уведомить его о том, что прерывание произошло. Аналогичным образом
системное задание выступает в роли посредника при доступе к портам ввода-вы­
вода. Сами драйверы не могут читать из них и писать в них напрямую.

В оп р осы и задани я
1 . Устройство для чтения DVD со скоростью 1х способно предоставлять 1,32 Мбайт
данных в секунду. Какова максимальная скорость устройства, которое мож­
но подключить к компьютеру с помощью интерфейса U S B 2.0 без потери
данных?
2. Многие диски содержат коды исправления ошибок (ЕСС) в конце каждого
сектора. Какие действия следует предпринять, если сам код ошибочен? Какой
программный или аппаратный компонент должен это делать?
3. Что такое ввод-вывод, отображаемый на память? Для чего он используется?
4. Объясните, что такое прямой доступ к памяти (DMA) и для чего он исполь­
зуется.
5. Хотя DMA не использует процессор, максимальная скорость передачи дан­
ных остается ограниченной. Предположим, вы считываете блок с диска. На­
зовите три фактора, ограничивающие скорость его передачи.
6. Музыка с качеством, соответствующим компакт-диску, должна иметь частоту
дискретизации сигнала 44 1 00 Гц. Предположим, что таймер генерирует пре­
рывания с такой частотой, а обработка прерывю:tия занимает 1 мс на про­
цессоре со скоростью 1 ГГц. Какова наименьшая частота таймера, обеспе­
чивающая сохранение всех данных? Положим, что число команд обработки
прерывания постоянно, следовательно, удвоение частоты таймера удваивает
время обработки прерывания.
7. Альтернативой прерываниям является опрос. Существуют ли ситуации, в ко­
торых опрос предпочтительней?
Вопросы и задания 409

8. Дисковые контроллеры снабжены внутренними буферами, объем которых


растет с каждой новой моделью. Почему?
9. У каждого драйвера устройства имеются два различных интерфейса с опе­
рационной системой. Один интерфейс представляет собой набор функций
драйвера, вызываемых операционной системой, а второй интерфейс - набор
функций операционной системы, вызываемой драйвером. Назовите вызов,
с высокой вероятностью имеющийся в каждом из этих интерфейсов.
1 О. Почему разработчики операционных систем стараются по возможности обес­
печивать ввод-вывод, независимый от устройств?
1 1 . На каком из четырех уровней программного обеспечения ввода-вывода вы­
полняются следующие действия:
1 ) вычисление номеров дорожки, сектора и головки для чтения диска;
2) поддержание кэша последних блоков;
3) запись команд в регистры устройства;
4) проверка разрешения доступа пользователя к устройству;
5) преобразование двоичного целого числа в ASCII -символы для вывода
на печать.
1 2 . Почему файлы, посылаемые на принтер, обычно перед печатью накапливают­
ся на диске?
13. Приведите пример взаимной блокировки, которая может иметь место в ре­
альном мире.
14. Рассмотрим рис. 3.7. Предположим, что на шаге п процесс С вместо ресурса R
запрашивает ресурс S. Приведет ли это к взаимной блокировке? А если он за­
просит оба ресурса, то есть и S и R?
15. Внимательно посмотрите на рис. 3.9, б. Если процесс D запросит еще одну
единицу, приведет это к безопасному состоянию или к небезопасному? Что
будет, если запрос поступит от процесса С, а не D?
16. Все траектории на рис. 3 . 1 0 горизонтальны или вертикальны. Можете ли вы
представить себе условия, при которых имелись бы также наклонные траек­
тории?
17. Предположим, процесс А на рис. 3. 1 1 запросил последний ленточный накопи­
тель. Приведет ли это к взаимной блокировке?
18. У компьютера есть шесть накопителей на магнитной ленте и п процессов, со­
ревнующихся за право их использовать. Каждому процессу может потребо­
ваться два накопителя. При каких значениях п в системе не будет взаимных
блокировок?
19. Может ли система находиться в состоянии, не являющимся ни состоянием
взаимной блокировки, ни безопасным состоянием? Если да, приведите при­
мер. Если нет, докажите, что все состояния либо являются тупиками, либо
они безопасны.
41 О Глава 3. Ввод- вывод

20. Распределенная система, использующая почтовые ящики, имеет два прими­


тива для взаимодействия между процессами: S END (послать) и RECE IVE (по­
лучить). Второй примитив указывает процесс, от которого следует получить
сообщение, и блокируется, если сообщения от процесса недоступны, даже не­
смотря на то, что могут ожидаться сообщения от других процессов. Здесь нет
общих ресурсов, но процессам необходимо часто связываться друг с другом
относительно других вопросов. Возможна ли взаимная блокировка? Аргу­
ментируйте ответ.
2 1 . Сотни одинаковых процессов в электронных системах межбанковского пере­
вода денежных средств работают следующим образом. Каждый процесс читает
входную строку, определяющую количество денег для перевода, кредитовый
и дебетовый счета. Затем процесс блокирует оба счета и выполняет транзак­
цию, а после завершения перевода снимает блокировку. При параллельно ра­
ботающем большом количестве процессов существует опасность, что имея
заблокированным счет х, процесс будет неспособен заблокировать счет у, по­
скольку счет у уже окажется заблокированным процессом, в данный момент
ожидающим счет х. Разработайте схему взаимодействия, позволяющую избе­
жать взаимных блокировок. Не освобождайте запись счета до тех пор, пока
вы не закончите транзакцию. (Иначе говоря, не позволяются решения, в ко­
торых один счет блокируется и затем немедленно освобождается, если другие
счета заблокированы.)
22. Алгоритм банкира работает в системе, где есть т классов ресурсов и п процес­
сов. При стремящихся к бесконечности т и п количество операций, которые
нужно выполнить для проверки безопасности состояния, пропорционально
тапЬ. Что представляют собой величины а и Ь?
23. Рассмотрим алгоритм банкира на рис. 3. 1 1 . Предположим, что процессы А и D
изменяют свои запросы на дополнительные ресурсы ( 1 , 2, 1 , О) и ( 1 , 2, 1 , О)
соответственно. Могут ли эти запросы быть удовлетворены с сохранением
безопасного состояния системы?
24. Золушка и Принц расторгают брак. Чтобы разделить свое имущество, они со­
гласились на следующий алгоритм. Каждое утро любой из них может послать
письмо адвокату другого, в котором запрашивает один предмет имущества.
Поскольку день уходит на доставку писем, они пришли к соглашению, что ес­
ли оба обнаруживают, что запросили один и тот же предмет в один и тот же
день, на следующий день они посылают письмо с отменой запроса. Среди
прочего имущества у них есть собака Буфер, конура Буфера, их канарейка
Твитер и клетка Твитера. Животные любят свои жилища, поэтому было при­
нято соглашение, что любой вариант раздела имущества, отделяющий живот­
ное от его дома, является недействительным, после которого весь раздел
имущества требуется начать заново. И Золушка, и Принц отчаянно хотят запо­
лучить Буфера. Поскольку они могут уехать (отдельно друг от друга) в отпуск,
каждый супруг запрограммировал персональный компьютер ддя обработки
переговоров. Когда они возвращаются из отпусков, компьютеры все еще ве­
дут переговоры. Почему? Возможна ли взаимная блокировка? Возможно ли
зависание? Аргументируйте ответ.
Вопросы и задания 41 1

25. Рассмотрим диск, содержащий 1 0 ООО цилиндров, 1 000 секторов/дорожек


размером 5 1 2 байт с 8 дорожками на цилиндре. Период вращения составляет
1 О мс, а время поиска дорожки- 1 мс. Какова максимально доступная часто­
та цикла передачи? Длительность цикла передачи?
26. Локальная сеть используется следующим образом. Пользователь делает сис­
темный вызов, чтобы записать пакеты данных через сеть. Затем операцион­
ная система копирует данные в буфер ядра. После этого данные копируются
в схему сетевого адаптера. После того как все байты попадают в контроллер,
они посылаются по сети со скоростью 10 Мбит/с. Получающий данные сетевой
контроллер сохраняет каждый бит, спустя 1 мкс после его отправки. Когда
последний бит получен, центральный процессор компьютера-получателя пре­
рывается, и ядро копирует прибывший пакет в свой буфер, чтобы исследо­
вать его. Поняв, какому пользователю предназначается пакет, ядро копирует
данные в пространство этого пользователя. Если предположить, что каждое
прерывание и его обработка занимают 1 мс, размер пакетов равен 1024 байт
(не считая заголовков), а копирование одного байта составляет 1 мкс, чему
равна максимальная скорость, с которой один процесс может передавать дан­
ные другому процессу? Предположите, что отправитель блокируется, пока по­
лучатель не закончит работу и не отправит обратно подтверждение. Для про­
стоты допустим, что временем получения подтверждения можно пренебречь.
27. Сообщение, формат которого показан в табл. 3.3, используется для того, чтобы
отправлять запросы драйверам блочных устройств. Какие поля можно было
бы опустить в сообщении для символьных устройств и есть ли такие поля?
28. Драйвер диска получает запросы на чтение/запись к цилиндрам 10, 22, 20, 2,
40, 6 и 38. Перемещение блока головок с одного цилиндра на соседний зани­
мает 6 мс. Сколько потребуется времени на перемещение головок при исполь­
зовании алгоритма:
1 ) обслуживания в порядке поступления запросов;
2) обслуживания в первую очередь ближайшего цилиндра;
3) элеваторного алгоритма (сначала блок головок двигается вверх);
Во всех случаях начальное положение блока головок на цилиндре 20.
29. Продавец персональных компьютеров, посещая университет на юго-западе
Амстердама для продажи партии компьютеров, заявляет, что его компания
приложила существенные усилия по развитию их версии UNIX. В качестве
примера он отмечает, что в их драйвере диска применяется элеваторный алго­
ритм, а обслуживание очереди запросов к одному цилиндру происходит в по­
рядке размещения секторов. На студента Гарри Хакера его речь производит
настолько сильное впечатление, что он покупает один компьютер. Гарри при­
носит компьютер домой и пишет программу, читающую случайные 10 ООО бло­
ков диска. К его изумлению, замеренная им производительность идентична
той, которой можно было ожидать при использовании алгоритма обслужива­
ния запросов в порядке поступления. Означает ли это, что продавец лгал?
41 2 Глава 3 . Ввод-вывод

30. В UNIX каждый процесс состоит из двух частей, работающих в адресном поль­
зовательском пространстве и в пространстве ядра. Является ли часть в, про­
странстве ядра подпрограммой или сопрограммой?
3 1 . На некотором компьютере обработчик прерываний от таймера выполняет
свои действия за 2 мс (включая накладные расходы по переключению про­
цессов). Прерывания от таймера поступают с частотой 60 Гц. Какая часть вре­
мени работы центрального процессора расходуется на таймер?
32. В тексте описано два варианта применения сторожевых таймеров: ожидание
запуска двигателя дисковода и выполнение возврата каретки на печатных тер­
миналах. Приведите еще один пример.
33. Почему терминалы, использующие интерфейс RS-232, управляются прерыва­
ниями, а терминалы с отображением на память - нет?
34. Рассмотрим работу терминала. Драйвер посылает один символ, после чего
блокируется. За передачей символа в линию следует прерывание, затем драй­
вер разблокируется и посылает следующий символ и т. д. Какую часть време­
ни центрального процессора занимает управление модемом, если обработка
прерывания, вывод одного символа и каждая блокировка требуют 4 мс? Бу­
дет ли этот метод работать с линиями 1 1 0 бод? А что насчет линий 4800 бод?
35. Вообразите терминал, содержащий 1 200 х 800 пикселов. Для прокрутки окна
центральный процессор (или контроллер) должен переместить все строки
текста вверх, копируя их биты из одной части видеопамяти в другую. Допус­
тим, в окне 66 строк по 80 символов в строке (всего 5280 символов), а каждый
символ имеет 8 пикселов в ширину и 16 пикселов в высоту. Сколько времени
займет прокрутка всего окна, если для копирования одного байта требуется
500 нс? Если все строки имеют по 80 символов в длину, чему будет равна эк­
вивалентная скорость терминала в бодах? Помещение одного символа на
экран занимает 50 мкс. Подсчитайте скорость, если терминал цветной и имеет
4 бит/пиксел (в этом случае помещение символа на экран занимает 200 мкс).
36. Для чего в операционных системах нужны ЕSС-последовательности, напри­
мер Ctrl+V в MINIX?
37. Получив с:и:мвол S I G INT (Ctrl+C), драйвер экрана MINIX очищает всю оче­
редь на вЬiвод для этого экрана. Почему?
38. У многих терминалов, использующих интерфейс RS-232, есть ЕSС-последо­
вательности для удаления текущей строки и перемещения всех нижних строк
на одну строку вверх. Как, по-вашему, реализована эта операция внутри тер­
минала?
39. На оригинальном компьютере I B M РС с цветным дисплеем запись в видеопа­
мять в любое время, кроме того интервала, когда электронный луч совершал
вертикальный обратный ход, вызывала появление уродливых пятен по всему
экрану. На экран выводятся 25 строк по 80 символов, каждый из которых по­
мещается в квадрат 8 х 8 пикселов. Каждый ряд из 640 пикселов рисуется за
один горизонтальный проход луча, что занимает 63,6 мкс, включая горизон­
тальное обратное движение луча. Экран перерисовывается 60 раз в секунду.
Вопросы и задания 41 3

При каждом выводе экрана требуется период времени на вертикальный об­


ратный ход луча. Какую часть времени видеопамять оказывается доступной
для записи?
40. Напишите графический драйвер для цветного дисплея IBM или любого друго­
го подходящего растрового дисплея. Драйвер должен воспринимать команды,
устанавливающие цвет отдельных пикселов, перемещающие прямоугольные
блоки по экрану и любые другие, какие вы сочтете интересными. Пользова­
тельские программы должны взаимодействовать с драйвером, открывая файл
/ dev/ graphi c s и записывая туда команды.
4 1 . Модифицируйте драйвер дисковода гибких дисков в MINIX, реализовав кэ­
ширование дорожек.
42. Напишите драйвер дисковода гибких дисков, который работает как символь­
ное, а не блочное устройство, чтобы обойти системный кэш блоков. Таким об­
разом, пользователи смогут считывать большие блоки данных, которые при
помощи D МА копируются непосредственно в адресное пространство пользо­
вателя, что значительно повышает производительность. Такой драйвер прежде
всего был бы интересен программам, считывающим необработанное содержи­
мое диска, не используя файловую систему. В эту категорию программ попа­
дают программы, проверяющие структуру файловой системы.
43. Реализуйте системный UNI Х-вызов PROF I L , который не поддерживается
в MINIX.
44. Измените драйвер терминала так, чтобы в дополнение к специальной клави­
ше, удаляющей последний введенный символ, была клавиша, удаляющая по­
следнее слово.
45. В систему MINIX 3 был добавлен новый диск со сменным носителем. Этому
уtтройству необходимо раскручивать диск после каждой смены носителя,
и время раскрутки довольно велико. Ожидается, что в процессе работы сис­
темы носитель будет часто меняться. Подпрограмма wa i t f o r из файла at_
wini с перестала удовлетворять пользователей. Разработайте новый вари­
.

ант процедуры wa i t f or, которая после одной секунды активного ожидания


нужного значения работает так: драйвер диска ждет одну секунду, затем про­
веряет значение порта, до тех пор пока не будет обнаружено нужное значение
или не истечет заданный параметром TIMEOUT интервал времени.
Глава 4
У п р авле н и е п а м я т ью

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


Хотя в наши дни память среднего домашнего компьютера в 2000 раз превышает
ресурсы IBM 7094 - машины, бывшей в начале 60-х годов самой мощной в ми­
ре, - программы все равно превосходят в объеме компьютерную память. Пе­
рефразируя закон Паркинсона, можно сказать, что программное обеспечение
имеет свойство непрерывно разрастаться, заполняя всю доступную на данный
момент память.
В этой главе мы узнаем, как операционная система управляет памятью. В идеале
каждый программист хотел бы иметь неограниченную по объему и быстродейст­
вию память, при этом также являющуюся энергонезависимой, то есть сохраняю­
щую свое содержимое при выключении питания. Раз уж мы взялись за эту тему,
то почему бы заодно не помечтать о дешевой памяти? К сожалению, существую­
щие технологии пока не способны удовлетворить все подобные пожелания.
Вследствие этого память в компьютерах имеет иерархическую структуру. Не­
большая часть ее представляет собой очень быструю, дорогую, энергозависимую
(то есть теряющую информацию при выключении питания) кэш-память. Кроме
того, компьютеры обладают десятками мегабайтов средней по быстродействию
и цене энергозависимой оперативной памяти ( Randorn Access Mernory, RAM),
а также десятками или сотнями гигабайтов медленной, дешевой и энергонезави­
симой постоянной памяти (Read Only Mernory, ROM) на жестком диске. Одной
из задач операционной системы является координация всех этих видов памяти.
Часть операционной системы, отвечающая за управление памятью, называется
менеджером па.мяти. Он следит за тем, какая часть памяти используется в дан­
ный момент, а какая - свободна; при необходимости выделяет память процессам
и по их завершении освобождает ресурсы; управляет обменом данными между
оперативной памятью и диском, если та слишком мала для того, чтобы вместить
все процессы. В большинстве операционных систем менеджер памяти является
частью ядра; MINIX 3 тем не менее в этом отношении является исключением.
В этой главе мы изучим несколько различных схем управления памятью, от са­
мой простой до весьма сложной и запутанной. Сначала мы рассмотрим наиболее
элементарную систему управления памятью, а затем постепенно будем перехо­
дить к все более и более совершенным системам.
4 . 1 . Б азовые механизмы упр а влен ия памятью 41 5

В главе 1 мы обращали внимание на то, что в компьютерном мире история имеет


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

4 . 1 . Б аз о вые механи з м ы
уп равлен и я пам я ть ю
Системы управления памятью можно разделить на два класса: те, в которых про­
цессы при выполнении перемещаются между оперативной памятью и диском,
и те, в которых этого не происходит. Второй вариант проще, поэтому начнем с не­
го, а обменом с диском, требующим либо подкачки, либо замещения страниц, мы
займемся потом. Читая главу 4, следует помнить, что варианты с подкачкой и за­
мещением страниц в значительной степени являются искусственными, вызван­
ными отсутствием достаточного объема оперативной памяти для одновременноm
хранения всех программ. Если когда-нибудь оперативная память настолько уве­
личится в объеме, что ее будет достаточно для любых целей, аргументы в пользу
той или иной схемы управления перестанут быть актуальными.
Однако не стоит забывать, что объем программ растет так же стремительно, как
и объем памяти, поэтому не исключено, что необходимость в эффективном
управлении памятью будет требоваться всегда. В 1980-е mды во многих универ­
ситетах использовались компьютеры V АХ, оснащенные системами разделения
времени и памятью объемом 4 Мбайт. С одним таким компьютером работали
десятки пользователей, получая более или менее удовлетворительное качество
обслуживания. Теперь для однопользовательского компьютера с операционной
системой Windows ХР компания Microsoft рекомендует не менее 1 28 Мбайт па­
мяти. Повсеместное распространение мультимедиа еще более повышает требова­
ния к памяти, так что эффективное управление памятью будет востребовано еще
как минимум лет 10.

4. 1 . 1 . Однозадач ная система без подкачки


и замещения стран и ц
В самой простой однозадачной системе управления памятью из всех возможных
в каждый конкретный момент времени работает только одна программа, при
этом память разделяется между программами и операционной системой. Есть
три варианта такой схемы. Как показано на рис. 4 . 1 , а, операционная система
может находиться в нижней части памяти - в оперативной памяти (RAM), или
в ОЗУ (оперативное запоминающее устройство).
41 6 Гла в а 4. У правл ение памят ью

OxFFF Драйверы
Операционная устройств
система в ПЗУ в ПЗУ
Программа
пользователя Программа
пользователя
Программа
пользователя
Операционная Операционная
система в ПЗУ система в ПЗУ
о о о
а б в

Рис. 4. 1 . Три простейшие модели организации памяти при наличии операционной системы
и одного пользовательского процесса. Существуют и другие варианты
Кроме того, операционная система может располагаться в самой верхней части
памяти (рис. 4 . 1 , б) - в постоянной памяти (ROM), или в ПЗУ (постоянное за­
поминающее устройство). В третьей модели драйверы устройств могут разме­
щаться в ПЗУ, а остальная часть системы - ниже в ОЗУ (рис. 4 . 1, в). Первая мо­
дель раньше применялась на мэйнфреймах и мини-компьютерах, но в настоящее
время практически не употребляется. Вторая модель сейчас используется в не­
которых палмтопах и встраиваемых системах, а третья модель была характерна
для ранних персональных компьютеров (например, работающих под управлением
MS-DOS), при этом часть системы, которая располагалась в ПЗУ, носила назва­
ние BIOS (Basic lnput Output System - базовая система ввода-вывода).
Когда система организована таким образом, в каждый конкретный момент вре­
мени может работать только один процесс. Как только пользователь набирает
команду, операционная система копирует запрашиваемую программу с диска
в память и выполняет ее, а после окончания процесса выводит на экран пригла­
шение и ждет новой команды. Получив инструкции, она загружает другую про­
грамму в память, записывая ее поверх предыдущей.

4 . 1 2 М ногозадачная система
. .

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

Когда задание поступает в память, его можно расположить во входной очереди


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

Несколько
входных очередей
�----� аоо к
Раздел 4 Раздел 4
f-------1700 к

Раздел 3 Раздел 3

400 К
Раздел 2 Раздел 2
200 к
Раздел 1 Раздел 1
Операционная 1 00 к Операционная
система о система
а б
Многозадачность с фиксированными разделами памяти: - фиксированные
Рис. 4 . 2 . а
разделы с отдельными входными очередями для каждого раздела; б - фиксированные
разделы с одной общей входной очередью
Недостаток размещения входящих заданий по отдельным очередям становится
очевидным, когда к большому разделу очередь отсутствует, в то время как к ма­
ленькому выстраивается довольно много заданий (в примере на рис. 4.2, а - это
разделы 1 и 3). Небольшим заданиям приходится ждать своей очереди, чтобы
попасть в память, и это при том, что память в основном свободна. Альтернатив­
ная схема заключается в организации одной общей очереди для всех разделов
(рис. 4.2, б): как только раздел освобождается, задание, ближайшее к началу оче­
реди и подходящее для выполнения в этом разделе, можно загрузить и присту­
пить к его выполнению. Поскольку нежелательно тратить большие разделы на
маленькие задания, существует другая стратегия. Она состоит в том, что каждый
раз после освобождения раздела происходит поиск в очереди наибольшего из
приемлемых по размерам для этого раздела заданий, и именно это задание выби­
рается для выполнения. Заметим, что последний алгоритм означает дискрими­
нацию мелких заданий, как недостойных того, чтобы под них отводился целый
раздел, в то время как обычно небольшим программам (часто интерактивным)
крайне важно предоставлять привилегированное обслуживание.
Один из выходов из положения - создание хотя бы одного маленького раздела
памяти, который позволит выполнять маленькие задания без долгого ожидания
освобождения больших разделов.
41 8 Глава 4 . У п равл е н и е памят ь ю

Еще одни вариант - ввести правило, по которому задание, предназначенное для


обработки, можно держать в кандидатах на обслуживание не более k раз. Каждый
раз, когда его выполнение откладывается, к счетчику добавляется единица. Ко­
гда значение счетчика становится равным k, игнорировать задание более нельзя.
Схема, в которой утром оператор задает фиксированные разделы и после этого
они не изменяются, в течение многих лет практиковалась в системах OS/360 на
больших мэйнфреймах компании IBM и носила название OS/MFT, или просто
МFГ (Multiprogramming with а Fixed number of Tasks - мультипрограммирова­
ние с фиксированным количеством заданий). Она легка для понимания и проста
в реализации: входящее задание стоит в очереди до тех пор, пока не станет дос­
тупным соответствующий раздел, затем оно загружается в этот раздел памяти
и там работает до завершения процесса. Однако сейчас очень мало (если они во­
обще сохранились) операционных систем, поддерживающих такую модель (это
относится даже к пакетным системам мэйнфреймов).

4 . 1 . З . Переадресация и защита
Многозадачность вносит две существенные проблемы, требующие решения,
это переадресация для перемещения программы в памяти и защита. Из рис. 4.2 яс­
но, что разные задания выполняются по разным адресам. Когда программа компо­
нуется (то есть в едином адресном пространстве объединяются основной модуль,
написанные пользователем процедуры и библиотечные подпрограммы), компо­
новщик должен знать, с какого адреса будет начинаться программа в памяти.
Например, предположим, что первая команда представляет собой вызов проце­
дуры с абсолютным адресом 1 00 внутри двоичного файла, создаваемого компо­
новщиком. Если эта программа загрузится в раздел 1 (по адресу 100 К), команда
обратится к абсолютному адресу 1 00, принадлежащему операционной системе.
А нужно вызвать процедуру по адресу 1 00 К + 1 00. Если же программа загрузит­
ся в раздел 2, команду нужно переадресовать по адресу 200 К + 100 и т. д. Эта
проблема известна как проблема переадресации.
Одним из возможных решений является модификация команд во время загруз­
ки программы в память. В прогр амме, загружаемой в раздел 1 , к каждому адресу
прибавляется значение 100 К, в программе, которая попадает в раздел 2, к адре­
сам добавляется значение 200 К и т. д. Чтобы выполнить подобную переадреса­
цию во время загрузки, компоновщик должен включить в двоичную программу
список или битовую карту с информацией о том, какие слова в программе яв­
ляются адресами (и их нужно перераспределить), а какие - кодами машинных
команд, постоянными или другими частями программы, которые не нужно изме­
нять. Так работает операционная система OS/MFT.
Переадресация во время загрузки не решает проблемы защиты. Вредоносные
программы всегда могут организовать какую-нибудь новую команду перехода
и с ее помощью проникнуть в память. Поскольку в такой системе использует­
ся абсолютная адресация памяти, а не смещение относительно того или иного
регистра, не существует способа, который позволил бы запретить программе
обращаться к любому слову в памяти для его чтения или записи. В многопользо-
4.2. Подкачка 41 9

вательских системах крайне нежелательно разрешать процессам доступ к облас­


ти памяти, принадлежащей другим пользователям.
Для защиты компьютера IBM 360 разработчики приняли следующее решение:
они разделили память на блоки по 2 Кбайт и назначили каждому блоку 4-раз­
рядный код защиты. Этот 4-разрядный ключ содержал слово состояния про­
граммы (Program Status Word, PSW). Аппаратура IВМ 360 перехватывала все
попытки работающих процессов обратиться к любой части памяти, код защиты
которой отличался от содержимого слова состояния программы. Поскольку
только операционная система была вправе изменять коды защиты и ключи,
предотвращалось вмешательство пользовательских процессов в дела друг друга
и в работу операционной системы.
Альтернативное решение сразу обеих проблем (защиты и переадресации) заклю­
чается в оснащении машины двумя специальными аппаратными регистрами, на­
зываемыми базовым и ограничительным. При планировании процесса в базо­
вый регистр загружается адрес начала раздела памяти, а в ограничивающий
регистр - длина раздела. К каждому автоматически формируемому адресу перед
его передачей в память прибавляется содержимое базового регистра. Таким об­
разом, если базовый регистр содержит величину 100 К, команда CALL 1 0 0 будет
превращена в команду CALL l O O K + 1 0 0 без изменения самой команды. Кроме
того, адреса проверяются по отношению к ограничительному регистру для га­
рантии, что они не используются для адресации памяти вне текущего раздела.
Базовый и ограничительный регистры защищаются аппаратно, чтобы не допус­
тить их изменений пользовательскими программами.
Неудобство, присущее этой схеме, - необходимость выполнять операции сложе­
ния и сравнения при каждом обращении к памяти. Операция сравнения может
быть выполнена быстро, но сложение относительно нее - медленная операция
(за исключением тех случаев, когда применяется специальная микросхема сло­
жения), что обусловлено временем распространения сигнала.
Такая система адресации использовалась в CDC 6600 - первом в мире супер­
компьютере. В центральном процессоре Intel 8088 для первых машин IBM РС
применялась ее упрощенная версия: были базовые регистры, но отсутствовали ог­
раничительные. Сейчас эту схему можно встретить лишь в немногих компьютерах.

4 . 2 . П одка ч ка
В случае пакетных систем память с фиксированными разделами действует про­
сто и эффективно. Каждое задание после ожидания в очереди загружается в раз­
дел памяти и остается там до своего завершения. До тех пор пока в памяти мо­
жет храниться достаточное количество заданий для обеспечения постоянной
занятости центрального процессора, нет причин что-либо усложнять.
Но совершенно другая ситуация имеет место в системах разделения времени и пер­
сональных компьютерах, ориентированных на работу с графикой. Оперативной
памяти иногда оказывается недостаточно для того, чтобы вместить все текущие
420 Гл ава 4 . У п р авление памят ью

активные процессы, и тогда избыток процессов приходится хранить на диске,


а для обработки динамически переносить в память.
Существует два основных подхода к управлению памятью, зависящие (отчасти)
от доступного аппаратного обеспечения. Самая простая стратегия, называемая
подкач:кой (swapping), заключается в том, что каждый процесс полностью копи­
руется в память, работает некоторое время и затем полностью же возвращается
на диск. Другая стратегия, носящая название виртуальной памяти, позволяет про­
граммам работать даже тогда, когда они только частично находятся в оперативной
памяти. Здесь мы изучим подкачку, а вторую стратегию рассмотрим в пункте 4.3.
Работа системы с подкачкой иллюстрирует рис. 4.3. На начальной стадии в па­
мяти находится только процесс А (рис. 4.3, а). Затем создаются или загружаются
с диска процессы В и С (рис. 4.3, б и в). На рис. 4.3, г процесс А выгружается на
диск. Затем появляется процесс D, а процесс В завершается (рис. 4.3, д и е). Нако­
нец, процесс А снова возвращается в память (рис. 4.3, ж). Так как теперь про­
цесс А расположен в другом месте, его адреса должны быть перенастроены либо
программно во время загрузки в память, либо (более заманчивый вариант) аппа­
ратно во время выполнения программы.
Основная разница между фиксированными разделами на рис. 4.2 и непостоян­
ными разделами на рис. 4.3 заключается в том, что во втором случае количество,
размещение и размер разделов изменяются динамически по ходу поступления
и завершения процессов, тогда как в первом варианте все эти параметры фикси­
рованы. Гибкость схемы, в которой нет ограничений, связанных с определенным
количеством разделов, и в которой каждый из разделов может быть очень боль­
шим или совсем маленьким, оптимизирует использование памяти, но и услож­
няет операции выделения и освобождения памяти, а также отслеживание проис­
ходящих изменений.
Когда в результате подкачки в памяти появляется множество неиспользованных
фрагментов, их можно объединить в один большой блок, передвинув все процес­
сы в сторону младших адресов настолько, насколько это возможно. Такая опера­
ция называется уплотнением, или сжатием, памяти. Обычно ее не выполняют
по причине экономии времени работы процессора. Например, на машине с опе­
ративной памятью объемом 1 Гбайт, которая может копировать 2 Гбайт в секун­
ду ( 1 байт за 0,5 нс), уплотнение всей памяти займет около 0,5 с. Конечно, время
не очень велико, но станет заметной помехой для пользователя, просматриваю­
щего потоковое видео.
Еще один момент, на который стоит обратить внимание: сколько памяти должно
быть предоставлено процессу, когда он создается или копируется с диска? Если
процесс имеет фиксированный размер, размещение происходит просто: операци­
онная система предоставляет точно необходимое количество памяти, ни больше,
ни меньше, чем нужно.
Однако если область данных процесса может расти, например, в результате ди­
намического распределения памяти из кучи (heap ), что позволяют многие языки
программирования, проблема предоставления памяти возникает каждый раз,
когда процесс пытается увеличиться. Если область неиспользованной памяти рас­
положена рядом с процессом, ее можно отдать процессу, позволив ему вырасти
4 . 2 . Подкачка 421

на величину этой области. Если же процесс соседствует с другим процессом, для


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

Время _____.

с с

в в в

А А А

Операционная Операционная Операционная Операционная


система система система система
а б в г

Время _____.

с с

в
А

D D D

Операционная Операционная Операционная


система система система
д е ж

Рис. 4 . 3 . Перераспределение памяти по мере того, как процессы поступают в память


и покидают ее. Заштрихованы неиспользуемые области памяти
Если предположить, что большинство процессов растет во время работы, вероятно,
сразу стоит предоставлять им несколько больше памяти, чем требуется, а всякий
раз, когда процесс копируется на диск или перемещается в памяти, придется обра­
батывать служебные данные, связанные с перемещением или подкачкой процессов,
больше не умещающихся в предоставленной им памяти. Но когда процесс выгружа­
ется на диск, вместе с ним должно сохраняться содержимое только действительно
422 Глава 4. Управл е ни е памятью

используемого адресного пространства, так как очень расточительно перемещать


также и придерживаемую им �про запас� память. На рис. 4.4, а показана конфи­
гурация памяти с предоставлением пространства для роста двух процессов.

}
В-стек
t
} Место для роста
_ _ _ _ _ _ _ _ _ _

_ _ _ _t _ _ _ _ м"""' дп• '°""' - - - - � - - - -

}
В-данные
в
используется В-программа
д••= """''"'

_ _ _ _t _ _ _ _
}
Meoro для роопо
А-стек
_ _ _ _ _

- - - - -
t

_ _ _ _ _

- - - - -
} Место для роста

}--",
А-данные
А
используется А-программа

Операционная Операционная
система система

а б
Рис . 4.4. Распределение памяти в случае расш ирен ия процессов: а - предоставление
пространства для роста области данн ых; б - предоставление пространства
для роста стека и области дан н ых

Если процесс имеет два наращиваемых сегмента, например сегмент данных,


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

4 . 2 . 1 . Уп равление памятью
с помощью битовых карт
Если память выделяется динамически, этим процессом должна управлять операци­
онная система. Существует два подхода к учету использования памяти: битовые
карты и списки свободных областей. В этом и следующем разделах мы по очереди
рассмотрим оба метода.
При работе с битовой картой память разделяется на блоки размером от несколь­
ких слов до нескольких килобайтов. В битовой карте каждому свободному блоку
4. 2 . Подкачка 423

соответствует один нулевой бит, а каждому занятому блоку - бит, установлен­


ный в 1 (или наоборот). На рис. 4.5, а показана область памяти, а на рис. 4.5, б -
соответствующая ей битовая карта.

1 1 1 1 1 000 р о 5 н 5 з р 8 6 р 14 4
11111111
1 1 001 1 1 1
р 20 6 н 29 з х
1 1 1 1 1 000
Процесс
б в
Свободный
фрагмент
Рис . 4 . 5 . Учет испол ьзования па м яти:а обл асть па мяти с пятью процесса м и и тремя
-

свободны м и фрагментам и ; б соответствующая битовая карта ; та же информация


- в -

в виде списка
Размер минимального выделяемого блока - весьма важный параметр системы.
Чем он меньше, тем больше битовая карта. В случае если минимальный вьще­
ляемый блок памяти имеет размер 4 байта, то есть 32 бит, для него нужно 1 бит
в карте. Тогда область размером в 32п потребует п бит карты, таким образом, би­
товая карта займет всего лишь 1/33 часть памяти. Если же отдать предпочтение
большим блокам, битовая карта станет меньше, но при этом может оказаться не­
используемой существенная часть последнего блока каждого процесса (если раз­
мер процесса не кратен размеру минимального блока).
Битовая карта предоставляет простой механизм отслеживания слов в памяти
фиксированного объема, поскольку ее размер зависит только от объема памяти
и размера минимального блока. Для этой схемы характерна проблема - при ре­
шении переместить процесс из k блоков в память менеджер памяти должен найти
в битовой карте последовательность из k смежных нулевых битов. Поиск после­
довательности заданной длины в битовой карте является медленной операцией
(так как искомая последовательность битов может пересекать границы слов в бито­
вом массиве). Это - главный аргумент противников битовых карт.

4 . 2 . 2 . Уп равление памятью с помощью


связан н ых списков
Другой подход к отслеживанию состояния памяти предоставляют связанные спи­
ски занятых и свободных фрагментов памяти, где фрагментом является либо про­
цесс, либо участок между двумя процессами. Память, показанная на рис. 4.5, а,
представлена в виде однонаправленного списка сегментов на рис. 4.5, в. Каждая
424 Глава 4 . Упр авле н ие памятью

запись в списке указывает, является ли область памяти свободной Н (от hole -


дыра) или занятой процессом Р (process), а также содержит адрес, с которого на­
чинается эта область, ее длину и указатель на следующую запись.
В нашем примере список упорядочен по адресам. Такая сортировка имеет следую­
щее преимущество: когда процесс завершается или выгружается на диск, измене­
ние списка представляет собой тривиальную операцию. Процесс обычно имеет
двух соседей (кроме случаев, когда он находится на самом •верху• или н а самом
•низу• памяти). Соседями могут быть процессы или свободные фрагменты, что
приводит к четырем комбинациям, показанным на рис. 4.6. На рис. 4.6, а коррек­
тировка списка требует замены Р на Н. На рис. 4.6, б и 4.6, в две записи объе­
диняются в одну, а список становится на запись короче. На рис. 4.6, г объединя­
ются три записи, а из списка удаляются два элемента. Так как ячейка таблицы
процессов для завершившегося процесса обычно непосредственно указывает на
запись в списке для этого процесса, возможно, удобнее иметь список с двумя
связями, чем с одной (последний показан на рис. 4.5, в). Такая двунаправлен­
ная структура упрощает поиск предыдущей записи и оценку возможности кон-
катенации.

До завершения Х После завершения Х


а
1 А х
1 в 1 становится А
� в
1
б
1 А х
� становится А

в
� х
1 в 1 становится � в 1
г
� х
� становится

Х
Рис. 4 . 6 . Четыре варианта завершения про цесса

Если процессы и свободные участки хранятся в списке, отсортированном по


адресам, существуют несколько алгоритмов предоставления памяти процессу,
создаваемому заново (или существующему процессу после подкачки с диска).
Допустим, менеджер памяти знает, сколько памяти нужно предоставить. Про­
стейший алгоритм представляет собой поиск первого соответствия. Менеджер
памяти просматривает список областей до тех пор, пока не находит достаточно
большой свободный участок. Затем этот участок делится на два: одна часть отда­
ется процессу, а другая остается неиспользуемой. Так происходит всегда, кроме
статистически нереального случая точного соответствия свободного участка и по­
желаний процесса. Это быстрый алгоритм, поскольку поиск минимизирован на­
столько, насколько это возможно.
Алгоритм следующего соответствия мало отличается от предыдущего. Он рабо­
тает так же, как и первый алгоритм, но всякий раз, когда находится нужный сво­
бодный фрагмент, запоминается его адрес. И когда алгоритм в следующий раз
вызывается для поиска, он стартует с того самого места, где остановился в про­
шлый раз, вместо того чтобы снова и снова начинать поиск от головы списка, как
4. 2. Подкачка 425

это делает алгоритм первого соответствия. Моделирование работы этого алго­


ритма показало, что его производительность несколько хуже, чем первого [7).
Другой хорошо известный алгоритм называется алгоритмом нашучшего соот­
ветствия. Здесь выполняется поиск по всему списку и выбирается наименьший
по размеру подходящий свободный фрагмент. Вместо того чтобы делить боль­
шую незанятую область, которая может понадобиться позже, этот алгоритм пре­
доставляет возможность найти участок, наиболее приближенный по размерам
к реально необходимому.
За примерами работы алгоритмов первого соответствия и наилучшего соответст­
вия снова обратимся к рис. 4.5. Если необходим блок размером 2, правило первого
соответствия предоставит область по адресу 5, а схема наилучшего соответствия
разместит процесс в свободном фрагменте по адресу 18.
Алгоритм наилучшего соответствия медленнее алгоритма первого соответствия,
так как каждый раз требуется поиск во всем списке. Однако, что немного удиви­
тельно, его результаты еще хуже, чем у алгоритмов первого соответствия и сле­
дующего первого соответствия, поскольку он стремится заполнить память очень
маленькими, практически бесполезными свободными областями, то есть фраг­
ментирует память. Для варианта первого соответствия в среднем характерны
большие свободные фрагменты.
Раз алгоритмы �соответствия� не всегда спасают, можно попытаться решить
проблему разделения памяти на практически точно совпадающие с процессом
области и маленькие свободные фрагменты, то есть использовать алгоритм наи ­
худшего соответствия. Он всегда выбирает самый большой свободный фраг­
мент, размер которого после дробления еще остается достаточным для даль­
нейшего применения. Однако моделирование показало, что это также не очень
подходящая идея.
Все четыре алгоритма можно ускорить, если поддерживать отдельные списки
для процессов и свободных областей. Тогда поиск будет производиться только
среди незанятых фрагментов. Неизбежная цена, которую придется заплатить за
повышение скорости размещения процесса в памяти, заключается в дополни­
тельной сложности и замедлении работы при освобождении областей памяти,
так как ставший свободным фрагмент необходимо удалить из списка процессов
и вставить в список незанятых участков.
Если для процессов и свободных фрагментов поддерживаются отдельные спи­
ски, то последний можно отсортировать по размеру, тогда алгоритм наилучшего
соответствия будет работать быстрее. Когда он выполняет поиск в списке сво­
бодных фрагментов от самого маленького к самому большому, то, как только на­
ходит приемлемую незанятую область, алгоритм уже знает, что она - наимень­
шая из тех, в которых может поместиться задание, то есть наилучшая. В отличие
от схемы с одним списком, дальнейший поиск не требуется. Таким образом, если
список свободных фрагментов отсортирован по размерам, схемы первого соот­
ветствия и наилучшего соответствия одинаково быстры, а алгоритм следующего
соответствия не имеет смысла.
426 Глава 4. Управление памятью

При поддержке отдельных списков для процессов и свободных фрагментов воз­


можна небольшая оптимизация. Вместо создания отдельного набора структур дан­
ных для списка свободных участков (рис. 4.5, в) можно использовать сами сво­
бодные участки. Первое слово каждого незанятого фрагмента может содержать
его размер, а второе - указывать на следующую запись. Узлы списка на рис. 4.5, в,
для которых требовались три слова и один бит (Р/Н), больше не нужны.
Еще один алгоритм распределения называется алгоритмом быстроzо соответ­
ствия; при использовании этого алгоритма поддерживаются отдельные списки
для некоторых из наиболее часто запрашиваемых размеров. Например, можно
представить таблицу с п записями, в которой первая запись указывает на начало
списка свободных фрагментов размером 4 Кбайт, вторая запись является указа­
телем на список незанятых областей размером 8 Кбайт, третья - 12 Кбайт и т. д.
Свободный фрагмент размером, скажем, 21 байт мог бы располагаться в списке
областей по 20 Кбайт или в специальном списке участков дополнительных раз­
меров. Поиск фрагмента требуемого размера происходит чрезвычайно быстро.
Но этот алгоритм имеет тот же недостаток, что и прочие схемы, которые сорти­
руют свободные области по размеру, а именно: если процесс завершается или
выгружается на диск, поиск его соседей с целью узнать, возможно ли их объеди­
нение, является затратной операцией. А если не производить слияния областей,
память очень скоро окажется разбитой на множество маленьких свободных фраг­
ментов, в которые не поместится ни один процесс.

4 . 3 . В иртуал ь на я пам я т ь
Уже достаточно давно люди столкнулись с проблемой размещения программ,
оказавшихся слишком большими и поэтому не помещавшихся в доступной фи­
зической памяти. Обычно принималось решение о разделении программы на
части, называемые оверлеями (overlays). Нулевой оверлей обычно запускался
первым. По завершении своего выполнения он вызывал следующий оверлей.
Некоторые оверлейные системы были очень сложными, позволяя одновременно
находиться в памяти нескольким оверлеям. Оверлеи хранились на диске и по
мере необходимости динамически перемещались между памятью и диском сред­
ствами операционной системы.
Хотя фактическая работа по загрузке оверлеев с диска и выгрузке на диск вы­
полнялась системой, делить программы на части должен был программист. Раз­
биение больших программ на маленькие модули поглощало много времени и бы­
ло не слишком интересным занятием. Однако такая ситуация длилась недолго,
так как вскоре удалось поручить всю работу компьютеру.
Разработанный подход стал известен как виртуальная памятъ [47]. Основная
идея этого подхода состоит в том, что хотя общий размер программы, данных
и стека может превышать объем доступной физической памяти, операционная
система хранит части программы, использующиеся в настоящий момент, в опера­
тивной памяти, остальные - на диске. Например, программа размером 5 1 2 Мбайт
4. 3 . В иртуальная память 427

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

4 . 3 . 1 . За мещение стран иц
Большинство систем виртуальной памяти опираются на прием, называемый
замещение страниц (paging). На любом компьютере существует множество адре­
сов в памяти, к которым может обратиться программа. Когда программа исполь­
зует следующую инструкцию, она делает это для того, чтобы скопировать содер­
жимое памяти по адресу 1 000 в регистр REG (или наоборот, в зависимости от
компьютера):
MOVE REG , 1 0 0 0

Адреса могут формироваться с использованием индексации, базовых регистров,


сегментных регистров и другими путями.
Эти программно формируемые адреса, называемые виртуальными, образуют
виртуальное адресное пространство. На компьютерах без виртуальной памяти
виртуальные адреса подаются непосредственно на шину памяти и при чтении
или записи читается или записывается слово в физической памяти с тем же
самым адресом. При применении виртуальной памяти виртуальные адреса не
передаются напрямую шиной памяти. Вместо этого они направляются в блок
управления памятью ( Меmогу Management Unit, MMU), который отображает
виртуальные адреса на физические адреса (рис. 4.7).

Процессор передает виртуальный


адрес диспетчеру памяти
Процессор
Память Контроллер
диска

Диспетчер памяти посылает


физический адрес в память
Рис. 4 . 7 . Расположение и функции блока управления па м ятью (MMU)
428 Глава 4. Управление памятью

Очень простой пример того, как выполняется такого рода отображение, при­
веден на рис. 4.8. Мы рассматриваем компьютер, который может формировать
1 6-разрядные адреса, от О до 64 К. Это - виртуальные адреса. У данного компью­
тера есть только 32 Кбайт физической памяти, поэтому, хотя программы разме­
ром 64 Кбайт писать можно, их нельзя целиком загрузить в память и запустить
на выполнение. Полная копия образа памяти программы размером до 64 Кбайт
должна присутствовать на диске, но в таком виде, чтобы ее можно было по мере
надобности переносить в память по частям.

Виртуальное
адресное пространство
60 - 64 к 1------1
56- 60 к 1-----1
52 - 56 к 1------1
48 - 52 к !------!
44- 48 к !----� '\
40 - 44 к 1------1
36-40 к !----� '\ Адрес
32 - 36 к 1-----1
физической памяти
28 - 32 к ,_______,
28-32 к
24-28 к 1------1
24-28 к
20 -24 к 1------1
20-24 к
16- 20 к 1------1
1 6-20 к
12-16 К 1-----1 1------1
12- 16 к
8- 12 к 6 8- 12 к
4- 8 К 4-8 к
0-4 К 2 �---' � К -4 К
Страничный блок
Рис. 4.8. Связь между виртуальными и физическими адресами,
поддерживаемая с помощью таблицы страниц
Пространство виртуальных адресов разделено на единичные блоки, называе­
мые страницами. Соответствующие блоки в физической памяти называются
страничными блоками (page frame). Размер страниц и их блоков всегда одинаков.
В этом примере они равны 4 Кбайт, но в реальных системах использовались раз­
меры от 5 1 2 байт до 64 Кбайт. Имея 64 Кбайт виртуального адресного про­
странства и 32 Кбайт физической памяти, мы получаем 1 6 виртуальных страниц
и 8 страничных блоков. Передача данных между ОЗУ и диском всегда происхо­
дит постранично.
Пусть программа пытается получить доступ к адресу О, например, с помощью
следующей команды:
MOVE REG , O

Тогда виртуальный адрес О передается в блок управления памятью (MMU). Блок


управления памятью видит, что этот виртуальный адрес попадает на страницу О
4 .3. Виртуал ь ная память 429

(от О до 4095), а та отображается на страничный блок 2 (адреса от 8 192 до 12287).


MMU преобразует виртуальный адрес О в физический адрес 8 192 и выставляет
последний на шину. Память ничего не знает о блоке управления памятью, а видит
просто запрос на чтение или запись слова по адресу 8 192 и выполняет запрос.
Таким образом, блок управления памятью эффективно отображает все виртуаль­
ные адреса между О и 4095 на физические адреса от 8 192 до 1 2287.
Еще пример:
MOVE REG , 8 1 9 2

Поскольку виртуальный адрес 8 192 находится на виртуальной странице 2 , а эта


страница отображается на физический страничный блок 6 (физические адреса
от 24576 до 2867 1 ) , эта инструкция точно так же преобразуется в команду
MOVE REG , 2 4 5 7 6

В качестве третьего примера рассмотрим виртуальный адрес 20500, который ад­


ресует байт 20 от начала виртуальной страницы 5 (виртуальные адреса от 20480
до 24575) и отображается на физический адрес 12288 + 20 12308.
=

Сама по себе возможность отображения 16 виртуальных страниц на любой из


восьми страничных блоков с помощью соответствующей карты отображения
в блоке управления памятью не решает проблемы, заключающейся в том, что
объем виртуального адресного пространства больше физической памяти. Так как
у нас есть только 8 физических страничных блоков, лишь 8 виртуальных страниц
на рис. 4.8 воспроизводятся в физической памяти. Другие страницы, обозначенные
на рисунке крестиками, не отображаются. В аппаратном обеспечении страницы,
физически присутствующие в памяти, отслеживаются с помощью бита присут­
ствия/отсутствия.
Что произойдет, если программа попытается воспользоваться неотображаемой
страницей? Например:
MOVE REG , 3 2 7 8 0

Эта инструкции обращается к байту 1 2 на виртуальной странице 8 (отклады­


ваемой с адреса 32768). Блок управления памятью замечает, что страница не
отображается (обозначена крестиком на рисунке), и инициирует прерывание
центрального процессора, передающее управление операционной системе. Такое
прерывание называется ошибкой отсутствия страницы (page fault). Операцион­
ная система выбирает редко используемый страничный блок и записывает его
содержимое на диск. Затем она считывает с диска страницу, вызвавшую преры­
вание, в только что освободившийся блок, изменяет карту отображения и запус­
кает прерванную команду заново.
Например, если операционная система решает удалить из оперативной памяти
страничный блок 1 , она загружает виртуальную страницу 8 по физическому
адресу 4 К и производит два изменения в карте блока управления памятью.
Во-первых, содержимое виртуальной страницы 1 отмечается как неотображае­
мое, чтобы перехватывать в будущем любые попытки обращения к виртуальным
адресам между 4 К и 8 К. Затем заменяется крест в записи для виртуальной
страницы 8 номером 1, следовательно, когда прерванная команда будет выпол­
няться заново, она отобразит виртуальный адрес 32 780 на физический адрес 4 1 08.
430 Глава 4. У правлен ие памятью

Теперь рассмотрим блок управления памятью изнутри, чтобы увидеть, как он


работает, и понять, почему мы выбрали размер страницы, являющийся степенью
числа 2. На рис. 4.9 представлен пример отображения виртуального адреса 8 1 96
(00 10000000000 100 в двоичном виде) согласно карте на рис. 4.8. Входной 16-раз­
рядный виртуальный адрес разделяется на 4-разрядный номер страницы и 12-раз­
рядное смещение. При выделении 4 бит под номер страницы в нашей системе
может существовать 16 страниц, а 12 бит смещения позволяют адресовать все
4096 байт внутри страницы.

Физическай адрес
1 1 1 1 l ol ololololololololol 1 iolol на выходе (24580)
• j�

1 5 ООО о
14 ООО о
1 3 ООО о
12 ООО о
11 111 1
10 ООО о
9 101 1
Таблица --. 8 ООО о 12 битов смещения
страницы 7 ООО о копируются прямо
6 ООО о одных данных
ИЗ ВХ
в выходные
5 011 1
4 100 1
3 ООО 1
2 110 1 -.i 110 1
1 001 1 ,,..- Бит
010 1 присутствия/
1
о
отсутствия
Виртуальная страница 2
используется как индекс
в таблице страниц
Виртуальный адрес
lolol 1 lolololololololololol 1 l o l ol на входе (81 96)
t
Рис. 4.9. Внутренняя операция бло ка управления па м ятью в систе ме из 1 6 страниц по 4 Кбайт

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


щей получить номер страничного блока, соответствующего виртуальной стра­
нице. Если бит присутствия/отсутствия равен О, управление переходит к опе­
рационной системе. Если этот бит равен 1, номер страничного блока, найденный
в таблице страниц, записывается в 3 старших бита выходного регистра, а 12 бит
смещения копируются без изменения из входного виртуального адреса. Все
вместе они составляют 15-разрядный физический адрес. Затем содержимое вы­
ходного регистра выставляется на шину памяти как адрес физической памяти.
4 . 3 . В иртуаль ная память 43 1

4 . 3 . 2 . Табл ицы страниц


В теории отображение виртуальных адресов на физические происходит так, как
мы только что описали. Виртуальный адрес делится на номер виртуальной стра­
ницы (старшие биты) и смещение (младшие биты). Номер виртуальной страни­
цы используется как индекс в таблице страниц для поиска записи этой страни­
цы. По записи в таблице страниц находится номер физического блока страницы.
Данный номер присоединяется к старшим разрядам числа смещения, замещая
собой номер виртуальной страницы и тем самым формируя физический адрес,
который может быть послан в память.
Назначение таблицы страниц заключается в отображении виртуальных страниц
на страничные блоки. Говоря математически, таблица страниц - это функция,
имеющая в качестве аргумента номер виртуальной страницы и вырабатывающая
в результате номер физического блока. На основе полученного результата поле
виртуальной страницы в виртуальном адресе может быть заменено полем стра­
ничного блока, таким образом, формируется физический адрес.
При столь простом описании нам придется столкнуться с двумя важными аспек­
тами применения таблицы страниц.
1. Таблица страниц может оказаться слишком большой.
2. Отображение должно выполняться быстро.
Первое утверждение следует из того факта, что современные компьютеры ис­
пользуют, по крайней мере, 32-разрядные виртуальные адреса. При размере стра­
ницы, скажем, 4 Кбайт, 32-разрядное адресное пространство будет состоять из
1 млн страниц, а 64-разрядное адресное пространство потребует намного больше
страниц, чем то количество, с которым можно иметь дело. Один миллион страниц
в виртуальном адресном пространстве требует 1 млн записей в таблице стра­
ниц. И это при том, что каждый процесс нуждается в собственной таблице страниц
(так как у него есть свое собственное виртуальное адресное пространство).
Второе положение вытекает из того, что преобразование виртуальных адресов
в физические должно выполняться при каждом обращении к ячейке памяти.
Типичная команда процессора часто включает в себя, помимо кода операции,
операнд(ы) памяти. В результате необходимо сделать 1, 2 или иногда больше об­
ращений к таблице страниц в рамках отработки одной команды. Если выполне­
ние команды занимает, скажем, 1 нс, то поиск в таблице страниц должен за­
вершиться до истечения 250 пс, чтобы преобразование виртуальных адресов не
стало главным узким местом системы.
Потребность в массовом, но при этом быстром страничном отображении накла­
дывает существенные ограничения на устройство компьютеров. Хотя проблема
наиболее серьезна для машин вершины модельного ряда, она также проявляется
и для машин его нижней части, когда стоимость и соотношение цена/производи­
тельность имеют критические значения. В этом и следующих разделах мы рас­
смотрим структуру таблицы страниц в деталях и покажем несколько аппаратных
решений, которые использовались в реальных компьютерах.
432 Глава 4. Управление памятью

Простейшее (по крайней мере, концептуально) конструкторское решение заклю­


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

М ногоуровневые таблицы страниц


Чтобы обойти проблему необходимости постоянного хранения в памяти ог­
ромных таблиц страниц, на многих компьютерах применяются многоуровневые
таблицы страниц. Простой пример представлен на рис. 4 . 1 0 а, где изображен
32-разрядный виртуальный адрес, разделенный на 10-разрядное поле РТ1, 10-раз­
рядное поле РТ2 и 1 2-разрядное поле смещения. Так как под смещение отведено
12 бит, страницы имеют размер 4 Кбайт и их всего 2 2 0 •
Секрет метода многоуровневой организации заключается в том, чтобы не дер­
жать постоянно в памяти все таблицы страниц. В частности, те части, которые не
нужны в данный момент, не должны находиться в памяти. Предположим, напри­
мер, что процессу нужно 12 Мбайт: младшие 4 Мбайт памяти для текста про­
граммы, следующие 4 Мбайт для данных и старшие 4 Мбайт для стека. Между
верхней границей данных и нижней границей стека образуется гигантский сво­
бодный фрагмент, который не используется.
На рис. 4 . 1 0, б мы видим, как в данном примере работает двухуровневая таб­
лица страниц. Слева находится таблица страниц верхнего уровня с 1 024 запи­
сями, соответствующими 1 О-разрядному полю РТ1. Когда виртуальный адрес
предстает перед блоком управления памятью, тот сначала выделяет поле РТ1
и использует его значение как индекс таблицы верхнего уровня. Каждая из этих
1024 записей представляет 4 Мбайт, поскольку целое 4-гигабайтное (то есть
32-разрядное) виртуальное адресное пространство было нарезано на куски по
1024 байта.
4. 3 . В и ртуальная память 433

Таблица страниц
второго уровня

Таблица страниц
для старших
4 Мбайт памяти

Биты
10 10 1 2
1 РТ1 j Рт2 I Смещение 1
а

1 023 }1----�1
6 1-------�
5
1------<
4
1------1
3 1-------� страницам
2
1------<
К

1 1-------�
о�-----�

б
Рис. 4. 1 0 . Структура двухуровневой таблицы страниц: а 32-разрядный адрес с полями
-

двух таблиц страниц; б - таблицы страниц


Запись, место которой определяется по индексу в таблице страниц верхнего
уровня, позволяет получить адрес, или номер, страничного блока таблицы стра­
ниц второго уровня. Запись О в таблице страниц первого уровня указывает на
таблицу страниц для текста программы, запись 1 - на таблицу страниц для дан­
ных, запись 1 023 - на таблицу страниц для стека. Другие (заштрихованные) за­
писи не задействованы. Поле РТ2 используется как индекс в выбранной таблице
второго уровня для поиска номера страничного блока самой страницы.
В качестве примера рассмотрим 32-разрядный адрес Ох00403004 ( 4 206 596 в деся­
тичном виде), который соответствует байту 1 2292 в данных. У этого виртуального
адреса РТ1 = 1 , РТ2 = 2 и смещение = 4. Менеджер памяти сначала по полю РТ1,
434 Глава 4. Уп равле ние памятью

то есть по индексу в таблице страниц верхнего уровня, получает запись 1, кото­


рая соответствует адресам от 4 до 8 М. Затем по индексу из только что найден­
ной таблицы второго уровня из поля РТ2 извлекается запись 3, которая соответ­
ствует адресам от 1 2 288 до 1 6 383 внутри своего участка размером 4 М (то есть
абсолютным адресам от 4 206 592 до 4 2 1 0 687 ) . Эта запись содержит номер
физического блока страницы, содержащей виртуальный адрес Ох00403004. Если
данная страница не находится в памяти, бит присутствия/отсутствия в записи
таблицы страниц будет равен нулю, что приведет к ошибке отсутствия страни­
цы. Если страница в памяти, номер страничного блока, взятый из таблицы стра­
ниц второго уровня, присоединяется к смещению (4), образуя физический адрес.
Этот адрес выставляется на шину и передается памяти.
Следует отметить одну интересную деталь на рис. 4.1 О. Хотя адресное пространст­
во содержит больше миллиона страниц, фактически нужны только четыре табли­
цы: таблица верхнего уровня и таблицы нижнего уровня для памяти от О до 4 М,
от 4 до 8 М и свыше 8 М. Битам присутствия/отсутствия для 102 1 записи табли­
цы страниц верхнего уровня присвоено значение О, что вызовет ошибку отсутст­
вия страницы при любом обращении к ним. Если это произойдет, операционная
система заметит, что процесс пытается обратиться к области памяти, не предпо­
лагающей ссылок на нее, и предпримет соответствующее действие, например, по­
шлет ему сигнал или уничтожит его. В описанном примере мы выбрали круглые
значения для различных величин и размер поля РТ1, равный размеру поля РТ2,
но в реальной практике, конечно, возможны другие цифры.
Система двухуровневой иерархии (см. рис. 4.10) может быть расширена для трех,
четырех и более уровней. Дополнительные уровни дают большую гибкость, од­
нако сомнительно, что следует усложнять систему, слишком увеличивая число
уровней.

Структура записи таблицы страниц


Теперь от структуры таблицы страниц в целом перейдем к описанию отдельной
записи таблицы. Формат конкретной записи в значительной мере зависит от ма­
шины, но виды представленной информации примерно одни и те же. На рис. 4. 1 1
мы привели образец записи таблицы страниц. Ее длина варьируется от компью­
тера к компьютеру, но 32 бита - это наиболее распространенный размер. Самым
важным полем является поле номера страничноzо блока. При отображении стра­
ниц основной задачей является определение данной величины. За этим полем
следует бит присутствия/отсутствия. Если этот бит равен 1 , запись имеет силу
и может использоваться. Если он равен О, виртуальная страница, которой соответ­
ствует эта запись, в данный момент отсутствует в памяти. Обращение к записи
в таблице страниц, где биту присутствия/отсутствия присвоено нулевое значение,
приводит к ошибке отсутствия страницы.
Биты защиты говорят о том, какие виды доступа разрешены к этой странице.
В простейшей форме это поле содержит один бит, равный 1 для чтения/записи
и О только для чтения. Более сложные схемы имеют три бита, по одному для
допуска каждой из операций чтения, записи и выполнения страницы.
4 . 3 . Виртуальная па м ять 435

Изменение Присутствие/отсутствие

11 Номер страничного бnока

Обращение Защита

Рис. 4. 1 1 . Типичная запись таблицы страниц

Биты изменения и обращения отслеживают использование страницы. Когда стра­


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

4 . 3 . З . Буферы быстрого преобразования адресов


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

копирующую содержимое одного регистра в другой. Без замещения страниц эта


команда приводит только к одному обращению к памяти для выборки самой ко­
манды. В случае же замещения страниц потребуются дополнительные ссылки
для доступа к таблице страниц. Так как скорость выполнения команд в основном
ограничена скоростью, с которой центральный процессор выбирает команды
и данные из памяти, необходимость двух обращений к таблице страниц на каж­
дую ссылку к памяти снижает производительность на 2/3. При таких условиях
никто не стал бы внедрять этот метод.
Разработчики компьютеров многие годы размышляли об означенной проблеме
и в результате придумали решение. Оно основано на наблюдении, что боль­
шинство программ выполняет огромное количество обращений к небольшому
количеству страниц, а не наоборот. То есть в таблице страниц лишь малая толи­
ка записей читается интенсивно, остальная часть может быть вообще не востре­
бована. Это - пример концепции локальности обращений, к которой мы еще вер­
немся в этой главе.
В результате принятого решения компьютер снабжается небольшим аппаратным
устройством, служащим для отображения виртуальных адресов на физические
без обращения к таблице страниц. Параметры этого устройства, называемого бу­
фером быстрого преобразования адресов (Translation Lookaside Buffer, TLB), или
ассоциативной памятью, представлены в табл. 4. 1 . Оно обычно находится внут­
ри блока управления памятью и обрабатывает несколько записей. В нашем при­
мере их восемь, но фактически записей редко бывает больше 64. Каждая запись
содержит информацию об одной странице, а именно: номер виртуальной страницы,
бит изменения, код защиты (разрешения на чтение/запись/выполнение) и но­
мер физического страничного блока, в котором расположена страница. Эти поля
однозначно соответствуют полям в таблице страниц. Еще один бит служит при­
знаком действительности записи (используется она в данный момент или нет).

Таблица 4. 1 . Параметры буфера быстрого преобразования адресов


Действительность Виртуальная страница Изменение Защита Страничный блок
1 1 40 1 RW 31
20 о RX 38
1 30 RW 29
1 29 1 RW 62
19 о RX 50
21 о RX 45
860 RW 14
861 RW 75
ТLВ-буфер, представленный в таблице, мог бы сформировать циклический про­
цесс, располагающийся на виртуальных страницах 19-2 1 . Соответственно, эти
записи в табл. 4 . 1 имеют коды защиты для чтения и выполнения. Основные
данные, используемые в текущий момент (скажем, обрабатываемый массив),
4 . 3. В иртуал ьная память 437

находятся на страницах 1 29 и 130. Страница 140 содержит индексы, требуемые


для вычислений массива. И наконец, на страницах 860 и 861 находится стек.
Теперь посмотрим, как функционирует буфер быстрого преобразования адресов.
Когда виртуальный адрес представляется блоком управления памятью для ото­
бражения, аппаратура путем сравнения адреса одновременно со всеми записями
(то есть параллельно) сначала убеждается, что номер его виртуальной страницы
присутствует в TLB. Если найдено совпадение и обращение не нарушает биты
защиты, страничный блок берется прямо из TLB, без перехода к таблице стра­
ниц. Если номер виртуальной страницы присутствует в ТLВ-буфере, но инст­
рукция требует записи на страницу, доступную только для чтения, формируется
ошибка защиты точно так же, как это происходило бы при использовании самой
таблицы страниц.
Интересная ситуация получается, если номер виртуальной страницы не находит­
ся в буфере быстрого преобразования адресов. Блок управления памятью обна­
руживает этот факт и выполняет обычный поиск в таблице страниц. Затем он
удаляет одну из записей из буфера и заменяет ее только что найденной записью
из таблицы страниц. Таким образом, если страница вскоре будет затребована снова,
поиск окажется успешным. Когда запись удаляется из ТLВ-буфера, бит изменения
копируется в запись таблицы страниц в памяти. Другие значения уже находятся
там. Когда буфер загружается из таблицы страниц, все поля берутся из памяти.

П ро г ра мм ное управление Т L В- буферо м


До сих пор мы предполагали, что каждая машина со страничной виртуальной
памятью имеет таблицы страниц, распознаваемые аппаратным обеспечением
и буфером быстрого преобразования адресов. При таком устройстве за управле­
ние ТLВ-буфером и обработку ошибок отсутствия страниц в нем полностью отве­
чает аппаратура блока управления памятью (MMU). Передача управления опера­
ционной системе происходит только тогда, когда страница отсутствует в памяти.
В прошлом это допущение было справедливо. Однако многие современные RISС­
компьютеры, включая SPARC, MIPS, Alpha, НР РА и PowerPC, почти полно­
стью управляют страницами программно. На этих машинах записи ТLВ-буфера
явно загружаются операционной системой. Когда поиск в ассоциативной памяти
заканчивается неудачей (промах), блок управления памятью, вместо того чтобы
переключаться на таблицу страниц для поиска и выбора необходимой страницы,
формирует ошибку ТLВ-буфера и передает ее решение операционной системе.
Система должна найти страницу, удалить запись из буфера, ввести новую запись
и перезапустить прерванную инструкцию. И конечно, все это должно быть сде­
лано при помощи небольшого числа команд, поскольку промахи в ТLВ-буфере
случаются намного чаще, чем ошибки отсутствия страниц.
Удивительно то, что если буфер достаточно велик (размером, скажем, 64 запи­
си), чтобы минимизировать число промахов, программное управление буфером,
оказывается, является достаточно эффективным. Главная выгода здесь заклю­
чается в намного более простом устройстве блока управления памятью, что ос­
вобождает достаточный объем пространства в микросхеме процессора для кэша
438 Глава 4. Уп равле н ие памят ью

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


управление буфером быстрого преобразования адресов обсуждается в [ 1 22].
Для повышения производительности н а компьютерах, программно управляющих
ТLВ-буфером, разработаны различные стратегии. Один из подходов состоит
в попытке уменьшить как частоту промахов в буфере, так и издержки промаха,
когда он все-таки случается [5]. Чтобы снизить вероятность промаха, иногда опе­
рационная система может �интуитивно» вычислить, какие страницы, возможно,
будут использоваться следующими, и предварительно загрузить записи для них
в буфер. Например, когда клиентский процесс посылает сообщение серверному
процессу на той же самой машине, очень вероятно, что сервер вскоре запустится.
Зная это, система, пока обрабатывается прерывание, чтобы осуществить вызов
s end, может также проверить, где находятся страницы серверного кода, данных
и стека, и преобразовать их адреса из виртуальных в физические до того, как они
смогут стать причиной ошибки ТLВ-буфера.
Обычный путь аппаратной или программной обработки промахов в буфере -
это переход в таблицу страниц и выполнение операции индексации с целью
определить положение страницы, к которой происходит обращение. При про­
граммной реализации возникает проблема, суть которой в том, что страницы,
содержащиеся в таблице страниц" могут отсутствовать в ТLВ-буфере, что вызо­
вет дополнительные ошибки ТLВ-буфера во время обработки. Количество таких
ошибок можно уменьшить, поддерживая большой (например, размером 4 Кбайт)
программный кэш ТLВ-записей с фиксированным расположением в памяти. Если
сначала проверять программный кэш, операционная система способна в значи­
тельной степени снизить количество промахов в ТLВ-буфере.

4 . 3 . 4 . И н верти рованные табли цы страни ц


Традиционные таблицы страниц, которые мы описывали до сих пор, требуют по
одной записи на каждую виртуальную страницу, так как они индексируются по
номеру этой страницы. Если адресное пространство состоит из 2 32 байт с разме­
ром страницы 4096 байт, тогда в таблице страниц должно быть больше миллиона
записей. То есть такая таблица страниц должна занимать минимум 4 Мбайт.
В достаточно больших системах это, вероятно, осуществимо.
Однако поскольку 64-разрядные компьютеры встречаются все чаще, ситуация ра­
дикально меняется. Если адресное пространство увеличивается до 264 байт с раз­
мером страницы 4 Кбайт, нам требуется таблица страниц с числом записей 252 •
Если каждая запись равна 8 байт, таблица займет больше 30 Тбайт. Выделение
30 Тбайт только для таблицы страниц не реально уже сейчас и не будет реаль­
ным когда-либо в будущем. Следовательно, для 64-разрядного страничного вир­
туального пространства необходимо другое решение.
Одним из таких решений является инвертированная таблица страниц. В этой
модели таблица содержит по одной записи на страничный блок в реальной памяти,
а не на страницу в виртуальном адресном пространстве. Например, при 64-раз­
рядных виртуальных адресах, размере страниц 4 Кбайт и оперативной памяти
4. 3 . Виртуальн ая память 439

объемом 256 Мбайт инвертированная таблица страниц потребует всего лишь


65 536 записей. Каждая запись позволяет отслеживать, что (процесс, виртуаль­
ная страница) расположено в данном страничном блоке.
Хотя инвертированные таблицы страниц экономят много места, по крайней мере,
когда виртуальное адресное пространство намного превышает физическую память,
они имеют серьезный недостаток: преобразование виртуального адреса в физи­
ческий значительно усложняется. Когда процесс п обращается к виртуальной
странице р, аппаратное обеспечение не может больше найти физическую страни­
цу, отталкиваясь от номера р как от индекса в таблице страниц. Вместо этого оно
должно производить поиск записи (п, р) во всей инвертированной таблице стра­
ниц. Более того, этот поиск должен выполняться при каждом обращении к памя­
ти, а не только при ошибке отсутствия страницы. Операция поиска в таблице
размером 64 К записей при каждом обращении к памяти вовсе не повысит быст­
родействие вашей машины.
Выйти из этого затруднительного положения позволяет буфер быстрого преоб­
разования адресов (TLB). Если в ТLВ-буфер удастся включить все часто ис­
пользуемые страницы, трансляция адреса будет происходить так же быстро, как
и в случае обычных таблиц страниц. Но при промахе в ТLВ-буфере поиск в ин­
вертированной таблице страниц должен выполняться программно. Один из
возможных способов усовершенствовать поиск - поддерживать хеширование
виртуальных адресов. Все виртуальные страницы, находящиеся в данный момент
в памяти и имеющие одинаковое значение хеш-функции, сцепляются друг с дру­
гом (рис. 4 . 1 2 ) . Если хеш-таблица состоит из такого же количества ячеек, сколь­
ко есть в машине физических страниц, средняя цепочка будет длиной только
в одну запись, что значительно повысит скорость отображения адресов. Как толь­
ко обнаруживается номер страничного блока, новая пара (виртуальная, физиче­
ская) помещается в ТLВ-буфер, а вызвавшая ошибку команда перезапускается.

Традиционная таблица
страниц с записью
для каждой
из 252 страниц
2 -1
52
1
.,;; !"' .; '"\

256 Мбайт физической


памяти содержат
2 16 блоков по 4 Кбайт Хеш-таблица
2 16 -1 1 2 16 -�

o�i о�
Индексирование
по виртуальной
Индексирование
.t�\
П,? хеш-коду Виртуальная
,!'
странице виртуальнои страницы страница
Страничный блок
Рис . 4. 1 2 . Сравнение обычной и инвертированной таблиц страниц
440 Глава 4. Уп равление памятью

Инвертированные таблицы страниц в настоящее время используются на некото­


рых рабочих станциях компаний IВМ, Sun и Hewlett-Packard и будут встречаться
все чаще, так как 64-разрядные машины получают все большее распространение.
О других методах управления виртуальной памятью большого размера можно
узнать в [62, 1 1 3, 1 14]. Некоторые проблемы реализации виртуальной памяти,
связанные с аппаратным обеспечением, рассмотрены в [65].

4 . 4 . Ал горитм ы за м е ще н ия стр а н и ц
Когда происходит ошибка отсутствия страницы, операционная система должна
выбрать страницу для удаления из памяти, чтобы освободить место для затребо­
ванной страницы. Если удаляемая страница была изменена за время своего при­
сутствия в памяти, ее необходимо переписать на диск, обновив хранящуюся там
копию. Однако если страница не была модифицирована (например, страница
с текстом программы), копия на диске и так оказывается свежей, поэтому пере­
писывать ее не надо. В этом случае затребованная страница просто считывается
на место выгружаемой.
Хотя, в принципе, при каждой ошибке отсутствия страницы для удаления мож­
но выбирать случайную страницу, производительность системы заметно повы­
шается, когда предпочтение отдается редко используемой странице. Если выгру­
жается страница, обращения к которой происходят часто, велика вероятность,
что вскоре опять потребуется вернуть ее в память, что даст в результате допол­
нительные издержки. Теме разработки алгоритмов замещения страниц было по­
священо много работ, как теоретических, так и экспериментальных. Здесь м:ь1
опишем некоторые из наиболее важных алгоритмов.
Следует заметить, что проблема замещения страниц характерна и для других
компонентов компьютера. Например, большинство современных компьютеров
оснащено одним или несколькими кэшами памяти, хранящими недавно исполь­
зованные блоки памяти размером 32 или 64 байт. Когда кэш заполнен, возникает
задача выбора блока для удаления. Она аналогична удалению страницы, хотя
и должна решаться быстрее (если удаление страницы можно выполнить за не­
сколько миллисекунд, то при удалении блока из кэш-памяти требуется уложить­
ся в несколько наносекунд). Причина состоит в том, что при кэш-промахах полу­
чение данных осуществляется из основной памяти с нулевыми задержками на
перемещение головок и вращение диска.
Второй пример - веб-браузер, хранящий на диске копии страниц, к которым ра­
нее имелся доступ. Обычно максимальный размер кэша фиксирован, поэтому
при интенсивном использовании браузера он с большой вероятностью заполнит­
ся. Когда к веб-странице обращаются по ссылке, сначала проверяется ее наличие
в кэше, а затем копия в кэше сравнивается с оригиналом на сервере. Если копия
актуальна, она предлагается пользователю, в противном случае страница загру­
жается с веб-сервера. При отсутствии копии страница загружается сразу. В случае
если кэш содержит устаревшую версию страницы, она заменяется новой, только
4.4. Алгоритмы заме щения страниц 44 1

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

4 .4 . 1 . Оптимальный алгоритм замещения страниц


Наилучший из алгоритмов замещения страниц легко описать, но невозможно
реализовать. Он действует так. В тот момент, когда происходит ошибка отсутст­
вия страницы, в памяти находится некоторый набор страниц. К одной из этих
страниц будет обращаться следующая команда процессора (к странице, содер­
жащей требуемую команду) . Вполне вероятно, что на другие страницы ссылок
в течение следующих 10, 1 00 или даже 1 000 команд не будет. Каждая страница
может быть помечена количеством команд, которые будут выполнены перед пер­
вым к ней обращением.
Оптимальный алгоритм замещения страниц просто сообщает, что должна быть
выгружена страница с наибольшей меткой. Если одна страница не будет исполь­
зоваться в течение 8 млн команд, а другая - в течение 6 млн команд, ошибка от­
сутствия страницы отодвинет в будущее удаление первой на возможно макси­
мальный срок. Компьютеры, подобно людям, пытаются отложить неприятные
события настолько, насколько это возможно.
С этим алгоритмом связана только одна проблема - он невыполним. В момент
ошибки отсутствия страницы операционная система не имеет возможности узнать,
когда произойдут следующие обращения к каждой из страниц. (Мы рассматривали
аналогичную ситуацию раньше, когда обсуждали алгоритм планирования «самое
короткое задание - первое• : откуда системе знать, какое задание самое корот­
кое?) Тем не менее выnолняя программу на модели и следя за всеми обращениями
к страницам, оптимальную замену можно осуществить при втором запуске, опира­
ясь на информацию о ссылках на страницы, собранную во время первого запуска.
В этом случае можно сравнивать производительность реализуемых алгоритмов
с наилучшим. Если от операционной системы нужно добиться производительно­
сти, скажем, всего на 1 % ниже, чем в случае оптимального алгоритма, усилия,
потраченные на поиск лучшего алгоритма, повысят продуктивность схемы мак­
симум на 1 %.
Чтобы избежать возможных недоразумений, следует прояснить, что полученный
протокол обращений к страницам относится только к одной хорошо спланирован­
ной программе и, кроме того, к определенным входным данным. Таким образом,
алгоритм замещения страниц, выведенный из него, может быть хорош только для
этой программы с именно этими входными данными. Хотя такой метод полезен
для оценки алгоритмов замещения страниц, он не используется в реальных сис­
темах. Далее мы изучим алгоритмы, которые применимы в реальных ситуациях.
442 Глава 4. Управле н ие п амятью

4 . 4 . 2 . Ал горитм N R U
Чтобы дать операционной системе возможность собирать полезные статистиче­
ские данные о том, какие страницы используются, а какие - нет, большинство
компьютеров с виртуальной памятью поддерживают два статусных бита, связан­
ных с каждой страницей. Бит R (от Referenced - обращение) устанавливается
всякий раз, когда происходит обращение к странице (чтение или запись). Бит М
(от Modified - изменение) устанавливается, когда страница записывается (то есть
изменяется). Биты содержатся в каждом элементе таблицы страниц (см. рис. 4. 1 1 ).
Важно реализовать обновление этих битов при каждом обращении к памяти,
поэтому необходимо, чтобы они задавались аппаратно. Если однажды бит был
установлен, то он остается равным 1 до тех пор, пока операционная система про­
граммно не вернет его в состояние О.
Если аппаратное обеспечение не поддерживает эти биты, их можно смоделиро­
вать следующим образом. Когда процесс запускается, все его записи в таблице
страниц помечаются как отсутствующие в памяти. Как только происходит обра ­
щение к странице, происходит ошибка отсутствия страницы. Затем операционная
система устанавливает бит R (в своих внутренних таблицах) ; изменяет запись
в таблице страниц так, чтобы она указывала на корректную страницу с режимом
<Полько чтение� , и перезапускает команду. Если страница позднее записывается,
происходит другая ошибка отсутствия страницы, позволяющая операционной
системе установить бит М и изменить режим на 4Чтение/запись�.
Биты R и М могут использоваться для построения простого алгоритма замещения
страниц. Когда процесс запускается, оба страничных бита для всех его страниц
операционной системой сброшены в О. Периодически (например, при каждом
прерывании от таймера) бит R очищается с целью отличить страницы, к кото­
рым давно не было обращений, от тех, к которым обращения были.
При возникновении ошибки отсутствия страницы операционная система прове­
ряет все страницы и делит их на четыре категории на основании текущих значе­
ний битов R и М:
+ класс О - обращения и изменения не было;
+ класс 1 - обращения не было, страница изменена;
+ класс 2 - обращение было, страница не изменена;
+ класс 3 - были и обращение, и изменение.
Хотя класс 1 на первый взгляд кажется невозможным, описываемая им ситуация
случается, когда у страницы из класса 3 бит R сбрасывается во время прерыва­
ния от таймера. Прерывания от таймера не затирают бит М, поскольку информа­
ция, которую он несет, необходима, чтобы понять, нужно переписывать страницу
на диске или нет. Поэтому если бит R сбрасывается, а М остается установлен­
ным, страница попадает в класс 1 .
Алгоритм NR U (Not Recently Used не использовавшаяся в последнее время
-

страница) удаляет страницу после случайного поиска в непустом классе с наи-


4 . 4 . Алго ритмы замеще н ия стра ни ц 443

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


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

4 . 4 . З . Ал горитм FI FO
Другим требующим небольших издержек алгоритмом является FIFO (First- In,
First- Out первым пришел, первым ушел). Чтобы проиллюстрировать его рабо­
-

ту, рассмотрим универсам, на полках которого можно выставить ровно k различ­


ных продуктов. Пусть в продажу поступил новый удобный пищевой продукт:
растворимый, <1:глубоко замороженный » , экологически чистый йогурт, кото­
рый можно мгновенно приготовить в микроволновой печи. Покупатели тут же
обратили внимание на этот продукт, и наш ограниченный в размерах супер­
маркет, для того чтобы продавать новинку, должен избавиться от одного из зале­
жалых товаров.
Один из вариантов решения проблемы состоит в том, чтобы найти продукт,
который супермаркет продает дольше всего (то есть что-нибудь, что завезли на
реализацию не менее сотни лет назад ) , и освободить от него магазин на том
основании, что им никто больше не интересуется. На самом деле сохраняется но­
менклатура всех продаваемых в данный момент в супермаркете товаров, упоря­
доченная по времени их появления. Каждый новый продукт помещается в конец
перечня, а из начала списка удаляется самый старый товар.
Та же самая идея срабатывает в алгоритме замещения страниц. Операционная
система поддерживает список всех страниц, находящихся в данный момент в па­
мяти; первая страница в списке является старейшей, а страницы в хвосте спи­
ска попали в него совсем недавно. Когда происходит ошибка отсутствия стра­
ницы, выгружается из памяти страница в голове списка, а новая страница
добавляется в его конец. Применительно к тому же магазину: если согласно ал­
горитму FIFO воск для усов действительно стоит удалить, вряд ли то же самое
можно сказать в отношении муки, соли или масла. Та же проблема встает и в от­
ношении компьютеров. По этой причине алгоритм F I F O редко используется
в своей исходной форме.

4 . 4 . 4 . Ал горитм второго ш анса


В простейшем варианте алгоритма FIFO, который позволяет избежать проблемы
вытеснения из памяти часто используемых страниц, у самой старейшей страницы
изучается бит R. Если он равен О, значит, страница не только находится в п амяти
долго, она вдобавок еще и не используется, поэтому немедленно заменяется но­
вой. Если же бит R равен 1, ему присваивается значение О, страница переносится
444 Глава 4. У п равлен ие памятью

в конец списка, а время ее загрузки обновляется, то есть считается, что страница


только что попала в память. Затем процедура продолжается.
Работу этого алгоритма, называемого алгоритмом второю шанса (second chance),
иллюстрирует рис. 4 . 1 3 , а. Здесь изображены страницы от А до Н, хранящиеся
в связанном списке и отсортированные по времени их поступления в память.
Числа над страницами обозначают время их загрузки в память.

Страница,
загруженная Последняя
первой загруженная
страница
а

А трактуется
как заново
загруженная
страница
б
Рис. 4. 1 3 . Иллюстрация работы алгоритма второго шанса:а - страницы, отсортированные
в порядке FIFO; б список страниц, если ошибка отсутствия страницы произошла
-

в момент 20, а страница А имеет бит R, равный.О


Предположим, что в момент времени 20 происходит ошибка отсутствия страни­
цы. Самой старшей страницей является страница А, она была загружена в па­
мять в момент О, когда начал работу процесс. Если бит R страницы А равен О, она
выгружается из памяти либо с записью на диск (если страница 4:Грязная» ), либо
без записи (если она 4:Чистая» ). Если же бит R равен 1, страница А передвигается
в конец списка, а ее 4:Загрузочное время» принимает текущее значение (20). При
этом бит R сбрасывается. Поиск подходящей страницы продолжается; следую­
щей проверяется страница В.
Алгоритм второго шанса ищет в списке самую старую страницу, к которой не
было обращений в предыдущем временном интервале. Если же происходили
ссылки на все страницы, то алгоритм второго шанса превращается в обычный
алгоритм FIFO. Представьте, что у всех страниц на рис. 4. 13, а бит R равен 1 .
Одну з а другой передвигает операционная система страницы в конец списка,
очищая бит R каждый раз, когда она перемещает страницу в хвост. Наконец, она
вернется к странице А, но теперь уже ее биту R присвоено значение О. В этот
момент страница А выгружается из памяти. Таким образом, алгоритм всегда
успешно завершает свою работу.

4 . 4 . 5 . Ал горитм часов
Хотя алгоритм второго шанса является корректным, он слишком неэффективен,
так как постоянно тасует страницы по списку. Поэтому лучше хранить все стра­
ничные блоки в кольцевом списке в форме часов (рис. 4. 14, стрелка указывает на
старейшую страницу).
4 . 4 . Алгоритмы заме щения страни ц 445

Когда происходит страничное


прерывание, проверяется страница,
на которую указывает стрепка.
Предпринимаемые действия
зависят бита R:
от

R = О: страница выгружается

R = 1 : бит R сбрасывается, стрепка


движется вперед

Рис. 4. 1 4 . Алгоритм часов


Когда происходит ошибка отсутствия страницы, проверяется та страница, на ко­
торую направлена стрелка. Если ее бит R равен О, страница выгружается, на ее
место в часовой круг встает новая страница, а стрелка сдвигается вперед на одну
позицию. Если бит R равен 1, он сбрасывается, стрелка перемещается к следующей
странице. Этот процесс повторяется до тех пор, пока не обнаруживается та стра­
ница, у которой бит R = О. Не удивительно, что алгоритм называется алгорит­
мом часов. Он отличается от алгоритма второго шанса только своей реализацией.

4 . 4 . 6 . Ал горитм LRU
В основе этой неплохой аппроксимации оптимального алгоритма лежит на­
блюдение, что страницы, к которым наблюдалось многократное обращение в не­
скольких последних командах, вероятно, также будут часто востребованы в сле­
дующих. И наоборот, можно полагать, что страницы, к которым ранее не было
обращений, не потребуются в течение долгого времени. Эта идея привела к сле­
дующему реализуемому алгоритму: когда происходит ошибка отсутствия стра­
ницы, выгружается из памяти страница, которая не использовалась дольше все­
го. Такая стратегия замещения страниц называется LR U ( Least Recently Used -
дольше всего не использовавшаяся страница).
Хотя алгоритм LRU теоретически реализуем, он не дешев. Для полной реализации
алгоритма LRU необходимо поддерживать список всех содержащихся в памяти
страниц, такой, где последняя использовавшаяся страница находится в начале
списка, а та, к которой дольше всего не было обращений, - в конце. Сложность
заключается в том, что список должен обновляться при каждом обращении к па­
мяти. Поиск страницы, ее удаление, а затем вставка в начало списка - это опера­
ции, поглощающие очень много времени, даже если они выполняются аппаратно
(предположим, что необходимое оборудование можно сконструировать).
Существуют и другие способы реализации алгоритма LRU с помощью специ­
ального оборудования. Для первого метода требуется оснащение компьютера
64-разрядным аппаратным счетчиком С, который автоматически инкрементируется
446 Глава 4. Уп ра вление памятью

после каждой команды. Кроме того, каждая запись в таблице страниц должна
иметь поле, достаточно большое для хранения значения счетчика. После каждого
обращения к памяти текущая величина счетчика С запоминается в записи табли­
цы, соответствующей той странице, к которой произошла ссылка. А если возни­
кает ошибка отсутствия страницы, операционная система проверяет все значе­
ния счетчиков в таблице страниц и ищет наименьшее. Эта страница и является
дольше всего не использовавшейся.
Рассмотрим второй вариант аппаратной реализации алгоритма LRU. На машине
с п страничными блоками оборудование LRU может поддерживать матрицу разме­
ром п х п бит, изначально равных нул ю . Всякий раз при доступе к страничному
блоку k аппаратура сначала присваивает всем битам строки k единицу, а затем
приравнивает нулю все биты столбца k. В любой момент времени строка, двоич­
ное значение которой наименьшее, является дольше всего не использовавшейся.
Работа этого алгоритма продемонстрирована на рис. 4. 15, где рассматриваются
четыре страничных блока и следу ющий порядок обращения к страницам:
0 1 232 1 0323
После ссылки на страницу О мы получаем ситуацию, показанную на рис. 4. 1 5, а;
после обращения к странице 1 на рис. 4. 1 5, б и т. д.
-

Страница Страница Страница Страница Страница


о 1 2 з о 1 2 з о 1 2 з о 1 2 з о 1 2 з
о о 1 1 1 о о 1 1 о о о 1 о о о о о о о о

о о о о 1 о 1 1 1 о о 1 1 о о о 1 о о о

2 о о о о о о о о 1 1 о 1 1 1 о о 1 1 о 1
з о о о о о о о о о о о о 1 1 1 о 1 1 о о

а б в г д

о о о о о 1 1 1 о 1 1 о о 1 о о о 1 о о

1 о 1 1 о о 1 1 о о 1 о о о о о о о о о

1 о о 1 о о о 1 о о о о 1 1 о 1 1 1 о о

1 о о о о о о о 1 1 1 о 1 1 о о 1 1 1 о

е ж з и к

Рис . 4. 1 5 . Алгоритм LRU с привлечением матрицы. Обращения к страница м происходят


в последовательности: О, 1 , 2, З, 2, 1 , О, З, 2, З

4 . 4 . 7 . П рограм м ное модели рование


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

редко используют эти алгоритмы. Вместо них применяется решение, программ­


но реализуемое. Одна из разновидностей алгоритма LRU называется NRJ (Not
Frequently Used - редко использовавшаяся страница). Для него необходим про­
граммный счетчик, связанный с каждой страницей в памяти, изначально равный
нулю. Во время каждого прерывания от таймера операционная система исследует
все страницы в памяти. Бит R каждой страницы (он равен О или 1 ) прибавляется
к счетчику. В сущности, счетчики пытаются отследить, как часто происходят об­
ращения к каждой странице. При ошибке отсутствия страницы для замещения
выбирается страница с наименьшим значением счетчика.
Основная проблема, возникающая при работе алгоритма NFU, заключается в том,
что он никогда ничего не забывает. Например, в многопроходном компиляторе
страницы, которые часто обрабатывались во время первого прохода, могут иметь
высокое значение счетчика при дальнейших проходах. Фактически, если случа­
ется так, что первый проход выполняется дольше всех, страницы, содержащие
программный код для следующих проходов, могут всегда иметь более низкое зна­
чение счетчика, чем страницы первого прохода. Следовательно, операционная
система удалит полезные страницы вместо тех, которые больше не нужны.
К счастью, небольшая доработка алгоритма NFU наделяет его способностью моде­
лировать алгоритм LRU достаточно хорошо. Изменение сводится к двум моди­
фикациям. Во-первых, каждый счетчик перед прибавлением бита R сдвигается
вправо на один разряд. Во-вторых, бит R вдвигается в крайний слева, а не в край­
ний справа разряд счетчика.
На рис. 4 . 1 6 продемонстрировано, как работает видоизмененный алгоритм, из­
вестный под названием алгоритма старения (aging). Предположим, после перво­
го такта часов биты R для страниц от О до 5 имеют значения 1, О, 1, О, 1, 1 соот­
ветственно (у страницы О бит R равен 1, у страницы 1 - О, у страницы 2 - 1
и т. д.). Другими словами, между тактом О и тактом 1 произошло обращение к стра­
ницам О, 2, 4 и 5, их биты R приняли значение 1 , остальные сохранили значе­
ние О. После того как шесть соответствующих счетчиков сдвинулись на разряд
и бит R занял крайнюю слева позицию, счетчики получили значения, показан­
ные на рис. 4. 1 6, а. Остальные четыре колонки рисунка изображают шесть счет­
чиков после следующих четырех тактов часов.
Когда происходит ошибка отсутствия страницы, удаляется та страница, счетчик
которой имеет наименьшую величину. Ясно, что счетчик страницы, к которой не
было обращений, скажем, за четыре такта, будет начинаться с четырех нулей
и, таким образом, иметь более низкое значение, чем счетчик страницы, на кото­
рую не ссылались в течение только трех тактов часов.
Эта схема отличается от алгоритма LRU в двух отношениях. Рассмотрим страни­
цы 3 и 5 на рис. 4 . 1 6, д. Ни к одной из них не было обращений за последние
два такта, к обеим бьmо обращение за предшествующий такт. Следуя алгоритму
LRU, при удалении страницы из памяти мы должны выбрать одну из двух. Пробле­
ма в том, что мы не знаем, к какой из них позже имелось обращение в интервале
между тактами 1 и 2. Записывая только один бит за промежуток времени, мы те­
ряем возможность отличить более ранние от более поздних обращений в этом
448 Глава 4 . Уп равление памя т ью

интервале времени. Все, что мы можем сделать, - это выгрузить страницу 3, так
как к странице 5 также обращались двумя тактами раньше, а к странице 3 - нет.

Биты R для Биты R для Биты R для Биты R для Биты R для
страниц 0-5, страниц 0-5, страниц 0-5, страниц 0-5, страниц 0-5,
такт О такт 1 такт 2 такт З такт 4
11 1°11 1°11 11 1 11 11 1°1°1 1 1°1 l1 !1 lol 1 lol 1 I 11 1°1°1°1 1 1°1 1°11 11 1°1°1°1
Страница
о 10000000 11000000 11100000 11110000 01111000
00000000 10000000 11000000 01100000 10110000
2 10000000 01000000 00100000 00100000 10001000
3 00000000 00000000 10000000 01000000 00100000
4 10000000 11000000 01100000 10110000 01011000
5 10000000 01000000 10100000 01010000 00101000
а б в г д
Рис . 4 . 1 6 . Алгоритм старения программно моделирует алгоритм LRU. Показаны шесть
страниц после пяти тактов часов
Второе отличие между алгоритмом LR U и алгоритмом старения заключается
в том, что в последнем счетчик имеет конечное число разрядов, например 8.
Предположим, что каждая из двух страниц получила нулевое значение счетчика.
В данной ситуации мы лишь случайным образом можем выбрать одну из них.
На самом деле не исключено, что к одной странице в последний раз обращались
9 тактов назад, а к другой - 1 000 тактов назад. И мы не имеем возможности уви­
деть это. На практике, однако, обычно достаточно 8 бит при такте системных ча­
сов около 20 мс. Если к странице не обращались в течение 1 60 мс, очень вероят­
но, что она не нужна.

4. 5 . Разработка систем за ме щени я страниц


В предыдущих разделах мы объяснили, как работает механизм замещения стра­
ниц, представили несколько основных алгоритмов замещения страниц и показа­
ли, к&к моделировать такие системы. Но просто знания механизма недостаточно.
Чтобы разработать хорошую систему, нужно вникнуть во все детали намного
глубже. Разница такая же, как между человеком, который знает, как ходят ладья,
конь, слон и другие шахматные фигуры, и хорошим шахматистом. Здесь мы рас­
смотрим все то, что должны принимать во внимание разработчики операцион­
ных систем, чтобы добиться от системы замещения страниц достойной произво­
дительности.
4. 5. Разработка систем заме ще н ия стран иц 449

4 . 5 . 1 . Модел ь рабочего набора


В простейшей схеме замещения страниц в момент запуска процессов нужные им
страницы отсутствуют в памяти. Как только центральный процессор пытается
выбрать первую команду, он получает ошибку отсутствия страницы, побуждаю­
щую операционную систему перенести в память страницу, содержащую первую
команду. Обычно следом быстро происходят ошибки отсутствия страниц для
глобальных переменных и стека. Через некоторое время в памяти скапливается
большинство необходимых процессу страниц, и он приступает к работе с относи­
тельно незначительным количеством ошибок отсутствия страниц. Этот метод
называется замещением страниц по запросу ( dernand paging), так как страницы
загружаются в память по требованию, а не заранее.
Конечно, достаточно легко написать тестовую программу, систематически чи­
тающую все страницы в огромном адресном пространстве, что сопровождается
таким количеством ошибок отсутствия страниц, что для их обработки не хватает
памяти. К счастью, большинство процессов не работают таким образом. Они ха­
рактеризуются локалъностъю обращений, означающей, что во время выполнения
любой своей фазы процесс обращается только к сравнительно небольшой части
собственных страниц. Многопроходный компилятор, например, на каждом про­
ходе обращается только к части страниц.
Множество страниц, которое процесс использует в данный момент, называется
рабочим набором [3 1 , 32]. Если рабочий набор целиком находится в памяти, про­
цесс выполняется, не вызывая большого количества ошибок, пока он не перей­
дет к другой фазе выполнения (к следующему проходу в случае компилятора).
Если доступная память слишком мала, чтобы содержать полный рабочий набор,
процесс инициирует множество ошибок отсутствия страниц и замедляется, так
как выполнение среднестатистической команды занимает несколько наносекунд,
а чтение страницы с диска обычно требует 10 мс. При скорости одна или две ко­
манды на 10 мс для завершения программы понадобятся века. Говорят, что про­
грамма пробуксовывает (thrashing), когда она вызывает ошибку отсутствия стра­
ницы каждые несколько команд [32 ] .
В системах разделения времени процессы часто сбрасываются н а диск (то есть
все их страницы удаляются из памяти) с целью позволить другим процессам по­
лучить доступ к центральному процессору. Возникает вопрос, что делать, когда
процесс снова загружается в память. С формальной точки зрения делать ничего
не нужно. Процесс будет вызывать одну за другой ошибки отсутствия страниц
до тех пор, пока в память не загрузится весь его рабочий набор. Проблема в том,
что наличие 20, 100 или даже 1 000 ошибок отсутствия страниц при каждой за­
грузке процесса значительно замедляет систему, кроме того, впустую тратится
много времени центрального процессора, до нескольких миллисекунд, столько,
сколько требуется операционной системе для обработки каждой ошибки отсут­
ствия страницы.
Поэтому многие системы замещения страниц пытаются отслеживать рабочий
набор каждого процесса и обеспечивают его нахождение в памяти еще до запуска
процесса. Такой подход носит название модели рабочеzо набора [33]. Эта модель
450 Гл ава 4 . Уп равл е н ие памятью

призвана значительно снизить процент ошибок отсутствия страниц. Загрузка


страниц перед тем, как разрешить процессу работать, также называется опере­
жающим замещением страниц (prepaging). Заметьте, что рабочий набор с тече­
нием времени изменяется.
Давно известно, что большинство программ обращается к различным областям
своего адресного пространства не в случайном порядке. Напротив, обращения
группируются на небольшом количестве страниц. При обращении к памяти мо­
жет выполняться чтение данных, чтение команды или запись данных. В любой
момент времени t существует набор, включающий все страницы k последних об­
ращений к памяти. Этот набор, w(k, t), является рабочим. Поскольку с увеличе­
нием k мы все дальше �заглядываем в прошлое�, число страниц рабочего набора
не может уменьшаться. Другими словами, функция w(k, t) представляет собой
монотонно неубывающую функцию от k. Кроме того, у этой функции есть конеч­
ный предел, поскольку программа не может обратиться к большему числу стра­
ниц, чем есть в ее адресном пространстве. Случаи, когда программа обращается
ко всем страницам своей памяти, очень редки. График зависимости размера ра­
бочего набора от числа последних обращений к памяти представлен на рис. 4 . 1 7 .

k
Рабочий набор - это множество страниц, использованных при последних
Рис . 4. 1 7 .
k обращениях к памяти. Функция w(k, t) представляет собой размер рабочего
набора в момент времени t
Быстрый рост кривой вначале и его последующее резкое замедление говорят
о том, что программы, как правило, имеют доступ к большинству своих страниц,
однако рабочий набор не подвержен значительным изменениям во времени. На­
пример, если программа исполняет цикл, занимающий 2 страницы памяти, и ис­
пользует данные, расположенные на 4 других страницах, то, возможно, за 1000 ко­
манд осуществляется обращение ко всем 6 страницам, а последнее обращение,
например, к странице 7 случилось миллион команд назад на этапе инициализа­
ции программы. Из-за такого асимптотического поведения содержимое рабочего
набора нечувствительно к выбранному значению k. Другими словами, существу­
ет большое количество значений k, для которых рабочие наборы одинаковы. Это
позволяет, на основе знаний о страницах, использованных при последнем завер­
шении программы, разумно спрогнозировать множество страниц, которые по­
требуются на начальной стадии работы программы при следующем ее запуске.
4.5. Разработка систем заме ще н ия стран иц 45 1

Благодаря опережающему замещению страниц эти страницы загружают в память


перед тем, как процесс получает разрешение на запуск.
Для реализации модели рабочего набора необходимо, чтобы операционная сис­
тема отслеживала, какие страницы в нем находятся. Один из способов получить
такую информацию - использовать описанный ранее алгоритм старения. Пусть
установленный старший бит счетчика у страницы означает, что она входит в ра­
бочий набор. Если в течение п последовательных тактов часов к такой странице
не было сделано обращений, она удаляется из рабочего набора. Параметр п при­
дется определить экспериментальным путем, но производительность системы
обычно не слишком чувствительна к его точному значению.
При помощи информации рабочего набора можно повысить производительность
алгоритма часов. Обычно, когда «стрелка часов» показывает на страницу, у кото­
рой бит R равен нулю, эта страница удаляется. Чтобы улучшить алгоритм, мож­
но дополнительно проверять, входит ли страница в рабочий набор текущего про­
цесса, и если да, оставлять ее. Такой алгоритм называется wsclock.

4 . 5 . 2 . Локал ьная и глобал ьная пол итики


распредел ения памяти
В предыдущих разделах мы обсудили несколько алгоритмов, вьmолняющих в слу­
чае ошибки поиск страницы для замещения. Основной вопрос, связанный с этим
выбором (который мы тщательно обходили до сих пор): как должна быть рас­
пределена память между параллельными конкурирующими работоспособными
процессами?
Обратим внимание на рис. 4. 18, а. Здесь три процесса, А, В и С, составляют набор
работоспособных процессов. Предположим, процесс А вызвал ошибку отсутствия
страницы. Должен ли алгоритм замещения страниц пытаться найти дольше
всех не использовавшуюся страницу, учитывая только 6 страниц, предоставлен­
ных в данный момент процессу А, или же он должен рассматривать все страницы
памяти? Если алгоритм производит поиск только среди страниц процесса А, наи­
меньший возраст имеет страница А5, и мы получаем ситуацию, изображенную
на рис. 4. 1 8, б.
В то же время, если удаляется страница с наименьшим возрастом, независимо от
того, к какому процессу она относится, то будет выбрана страница ВЗ, и система
попадет в состояние, показанное на рис. 4.18, в. Алгоритм на рис. 4. 18, б называет­
ся локальным, а на рис. 4. 18, в - глобальным. Локальные алгоритмы соответствуют
размещению каждого процесса в фиксированной области памяти. Глобальные
алгоритмы динамически распределяют страничные блоки между выполняющи­
мися процессами. Таким образом, количество страничных блоков, предоставлен­
ных каждому процессу, изменяется со временем.
В целом глобальные алгоритмы работают лучше, особенно если размер рабочего
набора может изменяться за время жизни процесса. Если используется локаль­
ный алгоритм и рабочий набор увеличивается в размере, мы получим пробуксов­
ку, даже если в системе имеется достаточное количество свободных страничных
452 Глава 4 . Уп равле н ие памят ью

блоков. Когда рабочий набор уменьшается, в случае локального алгоритма часть


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

Возраст
АО 10 АО АО
А1 7 А1 А1
А2. 5 А2. А2.
АЗ 4 АЗ АЗ
А4 6 А4 А4
А5 з СЕ;О) А5
во 9 во во
В1 4 В1 В1
В2 6 В2 В2
вз 2 вз СЕ;О)
В4 5 В4 В4
В5 6 В5 В5
В6 12 В6 В6
С1 з С1 С1
С2 5 С2 С2
сз 6 сз сз
а б в

Рис. 4 . 1 В. Локальный алгоритм замещения страниц в сравнении с глобальным:


а - исходная конфигурация; б локальное замещение страниц;
-

в глобальное замещение страниц


-

Другой подход состоит в том, чтобы реализовать в системе алгоритм распределе­


ния страничных блоков между процессами. Например, можно периодически оп­
ределять количество работающих процессов и предоставлять каждому равную
часть памяти. Соответственно, при наличии доступных (то есть не принадлежа­
щих операционной системе) 12 4 1 6 страничных блоков и 10 процессов каждый
процесс получает 1 2 4 1 блок. Оставшиеся 6 блоков поступают в резерв для ис­
пользования в тот момент, когда происходит ошибка отсутствия страницы.
Хотя этот метод кажется справедливым, существует вероятность, что процессы
размером 1 О и 300 Кбайт получат равные области памяти. Вместо этого можно
предоставлять страницы пропорционально абсолютному размеру каждого про­
цесса, тогда больший из этих двух процессов получит долю памяти в 30 раз
большую, чем меньший процесс. Разумно отдавать каждому процессу некоторый
минимум, чтобы он мог работать независимо от своего размера. На некоторых
машинах, например, одиночная команда процессора, включающая в себя два
операнда, может нуждаться в целых шести страницах, поскольку сама команда,
операнд-источник и операнд-приемник могут располагаться на разных страни­
цах. Если предоставить только пять страниц, программа, содержащая подобную
инструкцию, вообще не сумеет выполниться.
4 .5. Разработка систем замеще н ия стран иц 453

Если используется глобальный алгоритм, допустимо запускать каждый процесс


с некоторым количеством страниц, пропорциональным его размеру, но при этом
распределение памяти динамически менять во время работы. Алгоритм PFF (Page
Fault Frequency - частота ошибок отсутствия страниц) предоставляет один из
подходов к управлению размещением процессов в памяти. Он позволяет увели­
чивать или уменьшать количество страниц, предоставленных процессу, но не
указывает, какую страницу замещать по ошибке. Этот алгоритм только контро­
лирует размер набора страниц, назначенных процессу.
Как было отмечено ранее, для большого класса алгоритмов замещения страниц,
включая алгоритм LR U, известно, что частота ошибок уменьшается при уве­
личении числа предоставляемых страниц. Этот тезис лежит в основе алгорит­
ма PFF (рис. 4. 19).

Количество предоставленных страничных блоков


Рис. 4 . 1 9 . Частота ошибок отсутствия страниц как функция от количества
предоставленных процессу страничных блоков
Измерить частоту ошибок отсутствия страниц несложно: подсчитайте, сколько
их происходит в секунду, и, возможно, возьмите скользящее среднее за несколь­
ко последних секунд. Проще всего сложить текущее значение скользящего сред­
него со значением для последней секунды и разделить результат на 2. Прерыви­
стая линия, обозначенная буквой А, соответствует частоте ошибок отсутствия
страниц, выше которой она недопустимо высока, поэтому с целью уменьшения
числа ошибок увеличивается количество страничных блоков, предоставленных
прерванному процессу. Линия В соответствует очень низкой частоте ошибок
отсутствия страниц, позволяющей сделать вывод о том, что процесс занимает
слишком много памяти. В этом случае у него можно забрать несколько странич­
ных блоков. Таким образом, алгоритм PFF пытается сохранить частоту замеще­
ния страниц для каждого процесса внутри допустимых границ.
Если в памяти оказывается так много процессов, что удержать их все ниже линии А
оказывается невозможно, некоторые процессы удаляются из памяти, а освобо­
дившееся пространство распределяется между остальными или заносится в банк
свободных страниц для использования при последующих ошибках отсутст­
вия страниц. Решение о том, какой процесс удалить из памяти, является одной
из форм управления наzрузкой. Это показывает, что даже при замещении стра­
ниц нужна подкачка, только здесь она направлена на снижение потенциальной
454 Глава 4. Уп равление памятью

потребности в памяти, а не для возвращения и немедленного использования


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

4 . 5 . З . Раз м ер страницы
Зачастую размер страницы является параметром, выбираемым операционной
системой. Даже если аппаратное обеспечение предусматривает, например, раз­
мер страницы в 5 1 2 байт, операционная система может просто рассматривать
страницы О и 1, 2 и 3, 4 и 5 и т. д. как страницы размером 1 Кбайт, всегда предо­
ставляя для них два смежных страничных блока.
Определение оптимального размера страницы требует учета нескольких взаимо­
связанных факторов. Поэтому не существует абсолютного лучшего решения.
Прежде всего, есть два довода в пользу маленького размера страниц. Случайно
выбранный текст, данные или сегмент стека не заполняют страницы целиком.
В среднем половина последней страницы оказывается пустой, и это дополнитель­
ное пространство пропадает. Такие потери называют внутренней фрагментаци­
ей. Если в памяти п сегментов, а размер страницы равен р байт, то в результате
внутренней фрагментации пр/2 байт будет потрачено впустую. Это - разумный
аргумент в пользу страниц небольшого размера.
Другой довод становится очевидным, если мы представим себе программу, вы­
полняемую за 8 шагов, по 4 Кбайт каждый. При размере страницы 32 Кбайт про­
грамме должно быть постоянно выделено 32 Кбайт. При размере страницы
16 Кбайт ей необходимо только 16 Кбайт. При размере страницы 4 Кбайт или
меньше программа требует всего лишь 4 Кбайт в любой момент времени. То есть
большой размер страницы скорее, чем маленький, станет причиной того, что в па­
мяти окажется неиспользуемая часть страницы.
Однако небольшой размер страницы означает, что программам потребуется мно­
го страниц, для которых нужна огромная таблица страниц. Программа размером
32 Кбайт требует всего 4 страницы по 8 Кбайт или целых 64 страницы по 5 1 2 байт.
Как правило, страница за раз переносится на диск и с него, при этом большая
часть времени уходит на поиск цилиндра и задержку вращения, так что переме­
щение маленькой страницы занимает почти столько же времени, сколько и боль­
шой. То есть нужно 64 х 10 мс, чтобы загрузить 64 страницы размером 5 1 2 байт,
и всего лишь 4 х 1 0, 1 мс для загрузки четырех страниц по 8 Кбайт.
На некоторых машинах таблица страниц должна записываться в аппаратные ре­
гистры каждый раз, когда процессор переключается с одного процесса на другой.
Если на таком компьютере страница имеет маленький размер, то время, требуе­
мое для загрузки таблицы, растет пропорционально уменьшению размера стра­
ницы. Более того, пространство, занятое таблицей страниц, также возрастает
с уменьшением страницы.
4.5. Разработка систем замеще н ия стран иц 455

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


размер процесса равен s байт, а страницы - р байт. Кроме того, предположим,
что запись для каждой страницы требует е байт. Тогда приблизительное количе­
ство страниц, необходимое для процесса, равно s/p, что соответствует se/p байт
для таблицы страниц. Потеря памяти в последней странице процесса вследствие
внутренней фрагментации равна р /2. Таким образом, общие накладные расходы
вследствие поддержки таблицы страниц и потери от внутренней фрагментации
равны сумме этих двух составляющих:
Расход = se/p + р/2.
Первое слагаемое (размер таблицы страниц) растет при уменьшении размера
страницы. И наоборот, при увеличении размера страницы возрастает второе сла­
гаемое (внутренняя фрагментация). Оптимальный вариант следует искать где-то
посередине. Если взять первую производную по переменной р и приравнять ее
нулю, мы получим равенство:
-se/p2 + 1/2 = О.
Из этого равенства мы можем вывести формулу, дающую оптимально сбаланси­
рованный размер страниц (принимая во внимание только потери памяти на
фрагментацию, а также величину таблицы страниц). В результате получится:
р = ..fiiё .
Для среднего размера процесса s = 1 Мбайт и длины записи в таблице страниц
е = 8 байт оптимальный размер страницы равен 4 Кбайт. В серийно выпускае­
мых компьютерах были приняты значения в диапазоне от 5 1 2 байт до 1 Мбайт.
По мере увеличения объемов памяти размер страницы также имеет тенденцию к
росту (хотя и нелинейному): при возведении объема памяти в квадрат размер
страницы удваивается.

4 . 5 . 4 . И нтерфейс ви ртуал ьной памяти


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

способность совместного доступа: один процесс сможет просто писать в общую


память, а другой - читать из нее.
Совместное использование страниц находит также применение в высокопроизво­
дительных системах передачи сообщений. Когда передается сообщение, данные
обычно копируются из одного адресного пространства в другое, а это означает
значительные издержки. Если процессы будут управлять своей картой страниц,
можно передавать сообщения внутри общего адресного пространства: процесс­
отправитель будет убирать из карты страницу (страницы) с сообщением, а про­
цесс-получатель помещать ее (их) в карту. При этом достаточно копировать
только имена страниц вместо всех данных.
Еще один современный подход к управлению памятью носит название распреде­
ленной общей памяти [45, 80, 1 3 1 ] . Распределенная общая память позволяет не­
скольким процессам в сети совместно использовать набор страниц, возможно
(но не обязательно) как единое разделяемое линейное адресное пространство.
Когда процесс обращается к странице, не отображаемой в данный момент, он ини­
циирует ошибку отсутствия страницы. Обработчик ошибок отсутствия страниц,
находящийся в ядре или в пользовательском пространстве, находит машину, со­
держащую страницу, и посылает ей сообщение с запросом на выгрузку страницы
и ее отправку по сети. Когда страница прибывает, она попадает в карту, и пре­
рванная команда перезапускается.

4 . 6 . Сегм е нтаци я
Обсуждавшаяся до сих пор виртуальная память представляла собой одномерное
пространство, в котором виртуальные адреса идут один за другим от О до некото­
рого максимума. Для решения многих задач наличие двух и более отдельных
виртуальных адресных пространств может быть лучше, чем одно. Например, по
мере трансляции формируются несколько структур данных компилятора.
1. Исходный текст, сохраненный для печати листинга (в пакетных системах).
2. Символьная таблица с именами и атрибутами переменных.
3. Таблица со всеми используемыми константами: целыми и с плавающей точкой.
4. Дерево грамматического разбора с результатами синтаксического анализа про-
граммы.
5. Стек, используемый для процедурных вызовов внутри компилятора.
Во время компиляции каждая из первых четырех структур непрерывно растет.
Стек при компиляции непредсказуемо увеличивается или уменьшается. В одно­
мерной памяти эти пять структур должны размещаться в смежных частях вирту­
ального адресного пространства (рис. 4.20).
Рассмотрим, что происходит, если программа имеет исключительно большое
число переменных и обычное количество всего остального. Область адресного
пространства, предоставленная для символьной таблицы, может заполниться, но
в других структурах, скорее всего, останется пустым множество ячеек. Конечно,
4.6. Сегме нтация 457

компилятор может просто послать сообщение о том, что компиляция не может


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

Виртуальное адресное пространство

! }
Стек вызовов
} Свободно
Адресное пространство, "m�мв11111RВ11
Дерево Пространство, в данный
предоставленное синтаксического момент использУе мое
для дерева синтаксического анализа деревом синтаксического
пространства анализа

}
Таблица кодировки символов
достигла таблицы
с исходным текстом
Рис. 4.20. В одномерном адресном пространстве при росте таблиц
одна может упереться в другую
При другом подходе можно поиграть в Ра бин Гуда, забирая пространство у струк­
тур с излишками места и передавая их структурам с их недостатком. Такая пере­
тасовка реализуема, но она аналогична управлению собственными оверлеями, что
в лучшем случае неудобно, а в худшем случае требует большого объема скучной
и непродуктивной работы.
На самом деле нужно найти подход, освобождающий программиста от управле­
ния расширяющим ися и сокращающим ися структурами данных, так же как вир­
туальная память избавляет программы от возни с оверлеями.
Простое и предельно обобщенное решение заключается в том, чтобы обеспечить
машину множеством полностью независимых адресных пространств, называемых
сегм ентами. Каждый сегмент содержит линейную последовательность адресов
от О до некоторого максимума. Длина каждого сегмента может быть любой от
нуля до разрешенного максимума. Различные сегменты могут быть различной
длины. Более того, длины сегментов могут меняться во время выполнения про­
граммы. Длина сегмента стека может увеличиваться всякий раз, когда что-либо
помещается в стек, и уменьшаться при выборке данных из стека.
Поскольку каждый сегмент образует отдельное адресное пространство, разные
сегменты могут расти или сокращаться независимо друг от друга. Если стек,
находя щийся в определенном сегменте, нуждается в увеличении адресного про­
странства, он может получить его, потому что в его адресном пространстве нет
458 Глава 4. Уп равление памятью

больше ничего, что может стать препятствием для роста. Конечно, сегмент мо­
жет заполниться, но сегменты обычно очень большие, поэтому такие инциденты
редки. Чтобы определить адрес в такой сегментированной, или двухмерной,
памяти, программа должна указать адрес, состоящий из двух частей: номера сег­
мента и адреса внутри сегмента. Рисунок 4.2 1 иллюстрирует сегментированную
память, использующуюся для обсуждавшихся ранее структур данных компиля­
тора. Здесь показаны 5 независимых сегментов.

2 о к �---�

16 к - 16 к

12 к - 1 2 к�----, 12 к - 12 к
Таблица
кодировки
Дерево
8 к -символов 8 к- 8 К синтаксического вк-
Исходный анализа Стек
текст вызовов
4 к ,_. 4 К- 4К- 4К-

о к �---� о к �---� о к l Константы 1 о к ок


Сегмент Сегмент Сегмент Сегмент Сегмент
о 1 2 3 4
Рис. 4 . 2 1 . Сегментированная память позволяет каждой структуре расти
или уменьшаться независимо от других
Стоит подчеркнуть, что сегмент - это логический объект, о чем программист
знает и поэтому использует его как логический объект. Сегмент может иметь
в составе процедуру, массив, стек или набор скалярных переменных, хотя обыч­
но разных типов он не содержит.
Помимо простоты управления увеличивающимися или сокращающимися струк­
турами данных, сегментированная память обладает и другими преимуществами.
Если каждая процедура занимает отдельный сегмент и имеет нулевой начальный
адрес, компоновка отдельно скомпилированных процедур происходит намно­
го проще. После того как все процедуры, составляющие программу, скомпи­
лированы и скомпонованы, для адресации слова О (начальной точки) при об­
ращении к процедуре в сегменте п будет использоваться адрес, состоящий из
двух частей ( п, О).
Если потом процедура в сегменте п модифицируется и компилируется заново,
другие процедуры изменять не потребуется (потому что начальный адрес остает­
ся тем же), даже если новая версия больше предыдущей. В одномерной памяти
процедуры �упакованы� одна к одной, и между ними нет свободного адресного
пространства. В результате изменение размера одной процедуры может повли­
ять на начальный адрес другой, не имеющей отношения к первой. Это, в свою
4. 6 . Сегментация 459

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


нутых процедур, чтобы поместить в них новые начальные адреса. Если програм­
ма содержит сотни процедур, такой процесс оказывается очень затратным.
Сегментация также облегчает совместное использование процедур и данных
несколькими процессами. Хорошим примером является библиотека совместного
доступа. Современные рабочие станции, работающие с современными оконны­
ми системами, часто имеют крайне большие графические библиотеки, входящие
практически в каждую программу. В сегментированных системах графические
библиотеки могут располагаться в отдельном сегменте и совместно использо­
ваться несколькими процессами, что устраняет необходимость их присутствия
в адресном пространстве каждого процесса. В принципе, в •чистых» системах
замещения страниц также можно иметь совместно используемые библиотеки,
но это намного сложнее в реализации. Поэтому такие системы предоставляют
совместный доступ путем моделирования сегментации.
Поскольку каждый сегмент формирует логический объект (такой как процедура,
массив или стек), с которым общается программист, у различных сегментов мо­
гут быть разные виды защиты. Сегмент процедуры может быть определен, как
только исполняемый, что запрещает попытки чтения из него или сохранения
в него. Для массива чисел с плавающей точкой можно разрешить режим чтения
и записи, но не исполнения, чтобы перехватывать попытки передачи управления
по адресам, на которых располагается массив. Такая защита полезна при пере­
хвате программных ошибок.
Нужно попытаться понять, почему защита имеет смысл в сегментированной па­
мяти, а не в одномерной страничной памяти. В сегментированной памяти поль­
зователь знает о том, что представляет собой каждый сегмент. В обычном случае
сегмент не может содержать, например, и процедуру и стек, а только либо пер­
вое, либо второе. Так как каждый сегмент содержит только один тип объектов,
он может иметь защиту, соответствующую этому конкретному типу. Замещение
страниц и сегментация сравниваются в табл. 4.2.

Табл ица 4 . 2 . Сравнение замещения страниц и сегментации


Вопрос Замещение страниц Сегментация
Нужно ли программисту знать о том, Нет Да
что используется эта техника?
Сколько в системе линейных Много
адресных пространств?
Может ли суммарное адресное Да Да
пространство превышать
размеры физической памяти?
Возможно ли разделение процедур Нет Да
и данных, а также их раздельная
защита?
Легко ли размещать в памяти структуры Нет Да
данных с непостоянными размерами?
продолжение.,Р
460 Глава 4 . Упра вл ение памятью

Таблица 4. 2 ( пр одолжение )
Вопрос Замещение страниц Сегментация
Облегчен ли совместный доступ Нет Да
пользователей к процедурам?
Зачем была придумана эта техника? Чтобы получить Чтобы иметь возможность
большое линейное разбивать программы
адресное пространство и данные на логически
без дополнительных независимые адресные
затрат на физическую пространства, а также
память упростить совместный
доступ и защиту
Содержимое страниц в известной степени случайно. Программист не осведомлен
даже о том факте, что происходит замещение страниц. Хотя добавление несколь­
ких битов в каждую запись таблицы страниц для определения режима доступа
в принципе возможно, однако, чтобы использовать такой подход, программисту
пришлось бы отслеживать, где находятся границы страниц в его адресном про­
странстве. Это представляет собой в точности тот вид администрирования, для
устранения которого была придумана технология замещения страниц. Посколь­
ку пользователь сегментированной памяти имеет дело с иллюзией постоянного
нахождения всех сегментов в оперативной памяти (то есть он может адресовать­
ся к ним так, как будто они существуют), он может защищать сегменты по от­
дельности, не заботясь об управлении их загрузкой в память.

4 . 6 . 1 . Реал изация сегментаци и


Реализация сегментации и замещения страниц существенно различаются: стра­
ницы имеют фиксированный размер, а сегменты - нет. На рис. 4.22, а показан
пример физической памяти, изначально содержащей пять сегментов. РассмС1т­
рим ситуацию, когда удаляется сегмент 1, а на его место помещается сегмент 7
меньшего размера. В результате мы получаем конфигурацию памяти, изображен­
ную на рис. 4.22, б. Между сегментом 7 и сегментом 2 оказывается неиспользуе­
мая область, или дыра. Затем сегмент 4 замещается сегментом 5 (рис. 4.22, в),
а сегмент 3 - сегментом 6 (рис. 4.22, z ) . После того как система поработает ка­
кое-то время, память разделится на некоторое количество фрагментов, часть ко­
торых содержит сегменты, а часть - нет. Этот феномен разделения памяти на
маленькие свободные фрагменты называется внешней фрагментацией. С внеш­
ней фрагментацией можно бороться путем уплотнения (рис. 4.22, д).

4 . 6 . 2 . Сегментация с замещением
стран иц в l ntel Penti u m
Pentium поддерживает 16 К независимых сегментов по 232 байт виртуальной памя­
ти каждый. Операционная система может настроить Pentium на использование
сегментации, замещения страниц, либо того и другого вместе. Большинство опе­
рационных систем, включая Windows ХР и все семейство UNIX, поддерживают
4.6. Сегментация 46 1

только замещение страниц, при котором каждому процессу выделяется единст­


венный сегмент размером 2 32 байт. Поскольку Pentium обеспечивает выделение
процессам б ольших адресных пространств и одна из существующих операцион­
ных систем ( O S/2 ) на практике задействует все эти возможности адресации, мы
подробно рассмотрим виртуальную память Pentium.
Основой виртуальной памяти Pentium являются две таблицы: локальная (Local
Descriptor ТаЫе, LDT) и глобальная таблицы дескрипторов (Global Descriptor
ТаЫе, GDT). У каждой программы есть собственная локальная таблица дескрип­
торов, но глобальная таблица дескрипторов одна, ее совместно используют все
программы в компьютере. Локальная таблица дескрипторов описывает сегмен­
ты, локальные для каждой программы, - ее код, данные, стек и т. д., тогда как
глобальная таблица дескрипторов несет информацию о системных сегментах,
включая саму операционную систему.

Сегмент 4 Сегмент 4
(7 К) (7 К) Сегмент 5
(4 К)
Сегмент 3 Сегмент 3 Сегмент 3 Сегмент 5
(8 К) (8 К) (8 К) (4 К)
Сегмент б
Сегмент 2 Сегмент 2 Сегмент 2 (4 К)
(5 К) (5 К) (5 К) Сегмент 2
(5 К)
Сегмент 1
(8 К) Сегмент 7 Сегмент ? Сегмент ? Сегмент ?
(5 К) (5 К) (5 К) (5 К)
Сегмент а Сегмент а Сегмент а Сегмент а Сегмент а
(4 К) (4 К) (4 К) (4 К) (4 К)
а б в г д
Рис. 4 . 2 2 . Решение проблемы фрагментации: а-гразвитие внешней фрагментации;
-

д - устранение фрагментации путем уплотнения

Чтобы получить доступ к сегменту, программа процессора Pentium сначала за­


гружает селектор этого сегмента в один из шести сегментных регистров машины.
Во время выполнения регистр CS содержит селектор сегмента кода, а регистр
DS - селектор сегмента данных. Каждый селектор представляет собой 16-раз­
рядный номер (рис. 4.23).

Биты 13 2
Индекс
)1 � Уровень
а = GDT/1 = LDT привилегированности (0-3)
Рис. 4 . 2 3 . Селектор в системе Peпtium
462 Глава 4 . Уп равление памятью

Один из битов селектора говорит, является ли данный сегмент локальным или


глобальным (то есть находится он в локальной или глобальной таблице дескрип­
торов). Следующие 13 бит - это номер записи в таблице дескрипторов, поэтому
размер этих таблиц ограничен: каждая содержит 8 К сегментных дескрипторов.
Остальные два бита относятся к защите и рассматриваются позже. Дескрип­
тор О запрещен - его можно безопасно загрузить в сегментный регистр, чтобы
показать, что сегментный регистр в данный момент недоступен, но при попытке
его использовать вырабатывается прерывание.
Во время загрузки селектора в сегментный регистр, соответствующий элемент
извлекается из локальной или глобальной таблицы дескрипторов и сохраняется
в микропрограммных регистрах, что обеспечивает к нему быстрый доступ. Деск­
риптор состоит из 8 байт, в которые входят базовый адрес сегмента, размер
и другая информация (рис. 4.24 ).

О: 16-разрядный сегмент } О: Сегмент отсутствует в памяти


1 : Сегмент присутствует в памяти
1 : 32-разрядный сегмент
О: Предел в байтах
�---- Уровень привилегированности (0--3)
О: Система
1 : Предел в страницах 1 : Программы-приложения
Тип сегмента и защита
База 24-31 База 16--23 4

База 0-- 1 5 Предел 0-- 1 5 о

Относительная
адресация
Рис. 4.24. Дескриптор программного сегмента в системе Peпtium. Сегменты данных
немного отличаются
Формат селектора сознательно продуман так, чтобы упростить определение ме­
стоположения дескриптора. Сначала выбирается локальная или глобальная таб­
лица дескрипторов, основываясь на бите 2 селектора. Затем селектор копируется
во внутренний рабочий регистр и 3 младших бита сбрасываются в нули. Нако­
нец, к нему прибавляется адрес одной из таблиц, с целью получить прямой ука­
затель на дескриптор. Например, селектор 72 ссылается на запись 9 в глобальной
таблице дескрипторов, расположенную по адресу GDT + 72.
Теперь проследим, как пара селектор-смещение преобразуется в физический ад­
рес. Как только микропрограмма узнает, какой сегментный регистр использует­
ся, она может найти в своих внутренних регистрах полный дескриптор, соответ­
ствующий этому селектору. Если сегмент не существует (селектор равен О) или
в данный момент выгружен, происходит прерывание.
Затем микропрограмма проверяет, выходит ли смещение за пределы сегмента,
в этом случае также возникает прерывание. Логически, в дескрипторе просто
должно существовать 32-разрядное поле размера сегмента, но там доступны толь­
ко 20 бит, поэтому действует другая схема. Если поле G ( Granularity - глубина
4 . 6. Сегментация 463

детализации) равно О, то поле лимита содержит точный размер сегмента, до


1 Мбайт. Если поле G равно 1 , поле лимита дает размер сегмента в страницах,
а не в байтах. Размер страницы в системе Pentiurn фиксирован и равен 4 Кбайт,
поэтому 20 бит достаточно для сегментов размером до 2 32 байт.
Предположим, что сегмент находится в памяти, а смещение попало в нужный ин­
тервал. Тогда Pentiurn прибавляет 32-разрядное поле базы в дескрипторе к смеще­
нию, формируя то, что называется линейным адресом, как показано на рис. 4.25.
Поле базы разбито на три части, которые разбросаны по дескриптору для совмес­
тимости с процессором Intel 80286, где размер этого поля равен только 24 бита.
В сущности, поле базы позволяет каждому сегменту начинаться в произвольном
месте внутри 32-разрядного линейного адресного пространства.

Селектор Смещение
Дескриптор
Базовый адрес

�---<-1

Предел
Другие поля

32-разрядный
линейный адрес
Рис. 4 . 2 5 . Преобразование пары селектор-смещение в физический адрес

Если режим замещения страниц отключен (за счет бита в глобальном управляю­
щем регистре), линейный адрес интерпретируется как физический адрес и посы­
лается в память для чтения или записи. Таким образом, при отключении режима
замещения страниц мы получаем чистую схему сегментации с базовым адресом
каждого сегмента, выдаваемым его дескриптором. Сегментам разрешено пере­
крываться случайным образом, возможно потому, что реализация контроля за
тем, чтобы они не пересекались, была бы слишком трудоемкой и заняла бы слиш­
ком много времени.
В то же время в режиме замещения страниц линейный адрес интерпретируется
как виртуальный адрес и отображается на физический адрес с помощью таблицы
страниц практически так же, как в наших предыдущих примерах. Единственная
серьезная трудность заключается в том, что при 32-разрядном виртуальном адресе
и странице размером 4 Кбайт сегмент может содержать 1 млн страниц, поэтому
имеет место двухуровневое отображение с целью уменьшения размера таблицы
страниц для маленьких сегментов.
У каждой работающей программы есть странwтый каталог, состоящий из 32-раз­
рядных записей в количестве 1 024. Он расположен по адресу, хранящемуся в гло­
бальном регистре. Каждая запись в каталоге ссылается на таблицу страниц, так­
же содержащую 1 024 записей (тоже 32-разрядных). Записи в таблицах страниц,
в свою очередь, указывают на страничные блоки. Эта организация продемонст­
рирована на рис. 4.26.
464 Глава 4. Уп равление памятью

Линейный адрес
Биты 10 10 12
Каталог Страница Смещение
а

Страничный каталог Табnица страниц Страничный блок


l Слово l

i
выбранное
......

1024
записей t
Смещение
t
i
Каталог f-------1

• >------< +
Запись каталога Запись в табnице
указывает на страниц указывает
табnицу каталога на слово
б
Рис. 4.26. Отображение линейного адреса на физический
На рис. 4.26, а мы видим линейный адрес, разделенный на три поля: каталога,
страницы и смещения. Поле каталога используется как индекс в страничном
каталоге, определяющий расположение указателя на правильную таблицу стра­
ниц. Затем обрабатывается поле страницы в качестве индекса в таблице страниц,
с целью найти физический адрес страничного блока. И наконец, чтобы получить
физический адрес требуемого байта или слова, к адресу страничного блока при­
бавляется последнее поле - поле смещения.
Каждая запись в таблице имеет размер 32 бита, двадцать из которых содержат
номер страничного блока. Остальные биты - это биты доступа и �грязный• бит,
задаваемые аппаратно для операционной системы, биты защиты и другие полез­
ные биты.
Каждая таблица страниц включает в себя записи для 1 024 страничных блоков
размером по 4 Кбайт, таким образом, одна таблица страниц обеспечивает управ­
ление памятью размером 4 Мбайт. Сегмент, размер которого меньше 4 Мбайт,
будет иметь страничный каталог с единственной записью - указателем на его
единственную таблицу страниц. Следовательно, в случае короткого сегмента на
поддержку таблиц страниц расходуется только две страницы вместо миллиона
в случае одноуровневой таблицы страниц.
Чтобы избежать повторных обращений к памяти, система Pentium имеет неболь­
шой буфер быстрого преобразования адреса (TLB), который напрямую отобра­
жает наиболее часто используемые комбинации полей каталога и страницы на
физический адрес страничного блока. Только когда текущая комбинация от­
сутствует в TLB, действительно выполняется процедура, которую иллюстрирует
4. 6 . Сегментация 465

рис. 4.26, после чего TLB обновляется. Система обладает хорошей производи­
тельностью до тех пор, пока обращения к отсутствующим в TLB страницам про­
исходят относительно редко.
Если немного поразмыслить над механизмом замещения страниц, можно прий­
ти к выводу о том, что смысла в использовании нулевого значения поля базы
нет. Единственное, для чего нужно это поле, - обеспечить смещение, позволяющее
использовать записи, находящиеся в середине, а не в начале страничного катало­
га. Фактически база требуется лишь для поддержки «чистой• (без замещения
страниц) сегментации, а также для совместимости с процессором 286, в котором
режим замещения страниц всегда отключен.
Также следует отметить, что даже если приложение не требует сегментации, а до­
вольствуется единым разбитым на страницы 32-разрядным адресным простран­
ством, эта модель все равно работает. Все сегментные регистры могут быть на­
строены тем же самым селектором, в дескрипторе которого поле базы равно О,
а поле лимита установлено на максимум. Тогда при использовании единого ад­
ресного пространства смещение команды будет линейным адресом - в сущно­
сти, это обычное замещение страниц. Фактически все современные операцион­
ные системы для компьютера Pentium работают таким образом. Система OS/2
была единственной, в которой были задействованы все возможности архитекту­
ры блока управления памятью (MMU) компании Intel.
В конце концов, кто-то должен похвалить разработчиков Pentium. При постав­
ленных перед ними противоречивых задачах - реализовать «чистое• замещение
страниц памяти, «чистую• сегментацию и страничные сегменты и в то же время
обеспечить совместимость с процессором 286, а кроме того, сделать все это эф­
фективно - то, что у них получилось, на удивление просто и понятно.
Мы, хотя и кратко, но целиком описали архитектуру виртуальной памяти про­
цессоров Pentium и теперь следует сказать несколько слов о защите, так как эта
тема тесно связана с виртуальной памятью. Pentium поддерживает четыре уров­
ня защиты, где уровень О является наиболее привилегированным, а уровень 3 -
наименее привилегированным (рис. 4.27). В каждый момент времени работаю­
щая программа находится на определенном уровне, что отмечается 2-разрядным
полем в его слове состояния программы (PSW). Каждый сегмент в системе так­
же имеет свой уровень.
До тех пор пока программа сама ограничивает использование сегментов на своем
собственном уровне, система прекрасно работает. Разрешаются попытки получе­
ния доступа к данным более высокого уровня. Попытки доступа к данным более
низкого уровня запрещены и вызывают прерывания. Попытки вызвать процеду­
ры различного уровня (более высокого или низкого) позволяются, но тщательно
контролируются. Чтобы сделать межуровневый вызов, инструкция CALL должна
содержать селектор вместо адреса. Этот селектор определяет дескриптор, назы­
ваемый шлюзом вызова ( call gate) и передающий адрес вызываемой процедуры.
Таким образом, попасть в середину произвольного сегмента кода другого уровня
невозможно, открыты лишь официальные точки входа.
466 Гла в а 4 . Уп ра влен ие памятью

Уровень
Рис. 4.27. Защита в системе Pentium
Рисунок 4.27 иллю стрирует типичное применение этого механизма. На уровне О мы
находим ядро операционной системы, занимающееся обработкой операций ввода­
вывода, управлением памятью и другими первоочередными задачами. На уров­
не 1 находится обработчик системных вызовов. Пользовательские программы
этого уровня могут обращаться к процедурам для вьmолнения системных вызовов,
но только к определенному и защищенному списку процедур. Уровень 2 содер­
жит библиотечные процедуры, возможно, совместно используемые несколькими
работающими программами. Пользовательские программы вправе вызывать эти
процедуры и читать их данные, но не могут их изменять. И наконец, пользова­
тельские программы работают на наименее защищенном уровне 3.
В программные и аппаратные прерывания заложен механизм, аналогичный шлю­
зам вызовов. Они тоже обращаются к дескрипторам, а не к абсолютным адресам,
а эти дескрипторы указывают на определенные процедуры. Поле типа на рис. 4.24
позволяет различать программные сегменты, сегменты данных и шлюзы вызовов
различных видов.

4 . 7 . Знакомство с ме н еджер ом
про цессов в M I N IX 3
В MINIX 3 управление памятью реализовано элементарно: ни замещение стра­
ниц, ни подкачка попросту не используются. Тем не менее код поддержки под­
качки входит в полную версию операционной системы и позволяет применять
MINIX 3 в условиях нехватки физической памяти. На практике объемы памяти
таковы, что к подкачке приходится прибегать достаточно редко.
4.7. З наком ство с менеджером п роцессов в M I N IX З 467

В этой главе мы изучим сервер, работающий в пользовательском пространстве


и называемый менеджером процессов ( Process Manager, Р М ) . Менеджер про­
цессов обрабатывает системные вызовы, связанные с управлением процессами.
Среди этих вызовов есть и те, которые тесно связаны с управлением памятью,
в частности fork, ехес и brk. Управление процессами также включает обработ­
ку системных вызовов, имеющих отношение к сигналам, установке и считыва­
нию свойств процессов (к примеру, пользователя и группы, владеющих процес­
сом), времени работы процессора. Кроме того, менеджер процессов в MINIX 3
устанавливает и опрашивает часы реального времени.
Иногда мы называем часть менеджера процессов, осуществляющую управление
памятью, менеджером памяти. Возможно, в будущих версиях функции управле­
ния процессами и памятью будут полностью разделены, однако в MINIX 3 они
интегрированы в один процесс.
Менеджер процессов поддерживает список свободных участков памяти (�дыр•),
отсортированный по адресам. Когда процессу вследствие вызова fork или ехес
требуется память, элементы списка перебираются до тех пор, пока не обнаружи­
вается первая достаточно большая дыра. В отсутствие подкачки процесс, распо­
ложившись в памяти, всегда остается на прежнем месте до своего завершения.
Он никогда не выгружается на диск и не перемещается по другому адресу. Кроме
того, он никогда не увеличивает и не уменьшает выделенную ему область памяти.
Такая стратегия требует некоторых пояснений. Ее определяют три фактора.
1 . Желание сделать систему максимально простой для понимания.
2. Архитектура оригинального процессора IВМ РС (Intel 8088).
3. Желание обеспечить простоту переноса MINIX 3 на другие аппаратные плат-
формы.
Поскольку MINIX 3 представляет собой обучающую систему, было крайне жела­
тельно не усложнять ее. Размер исходного кода в 250 страниц был сочтен доста­
точным. Так как система разрабатывалась для оригинального компьютера IBM
РС, у которого не было даже блока управления памятью, на начальном этапе
обеспечить поддержку замещения страниц было невозможно. Другие компьюте­
ры в то время также были лишены блока управления памятью, а, следовательно,
выбранная стратегия управления памятью упрощала задачу переноса системы на
платформы Macintosh, Atari, Amiga и др.
Разумеется, правомерным является вопрос: а имеет ли смысл описанная страте­
гия на сегодняшний день? Первый пункт до сих пор актуален, хотя система зна­
чительно расширилась с течением времени. Тем не менее появились и несколь­
ко новых важных факторов. Память современных компьютеров по объему в 1 000
с лишним раз превышает память первых машин IBM РС. Хотя и программы ста­
ли больше, памяти большинства систем достаточно для того, чтобы не прибегать
к подкачке и замещению страниц. Наконец, система MINIX 3 в определенной
степени рассчитана на низкопроизводительные и встроенные системы. Цифровые
камеры, DVD-плееры, аудиоаппаратура, сотовые телефоны и прочее оборудование
зачастую имеют операционную систему, однако, очевидно, не поддерживающую
468 Глава 4. Уп равление памятью

подкачку и замещение страниц. Поскольку система MINIX 3 рассчитана на при­


кладное применение, ни подкачка, ни замещение страниц не являются для нее
высокоприоритетными задачами. Тем не менее в настоящее время анализируется
возможность реализации в MINIX 3 простейших механизмов виртуальной памяти.
Сведения о текущих разработках можно получить на веб-сайте MINIX.
Следует обратить внимание на еще одну особенность управления памятью в
MINIX 3, отличающую ее от большинства других операционных систем. Менед­
жер процессов является не частью ядра, а процессом, выполняющимся в пользо­
вательском пространстве и взаимодействующим с ядром при помощи стандарт­
ного механизма сообщений. Местоположение менеджера процессов в структуре
операционной системы иллюстрирует рис. 2 . 1 4 .
Исключение менеджера процессов из ядра - пример разделения политики и ме­
ханизма. Решение о том, какая область памяти будет отведена каждому из процес­
сов (политика), принимается менеджером процессов. Реальное же обслуживание
карт памяти для процессов (то есть механизм) выполняется системным заданием
ядра. Подобное разделение позволяет легко изменить политику управления па­
мятью (алгоритмы и т. д. ), не затрагивая нижние уровни операционной системы.
Большая часть кода менеджера процессов в MINIX 3 относится к обработке сис­
темных вызовов, связанных с созданием процессов (в основном это f o rk и ехе с ) ,
а не манипулированию списками процессов и свободных блоков памяти. В сле­
дующем разделе мы изучим распределение памяти, а еще дальше последует об­
зор процедур обработки системных вызовов менеджером процессов.

4 . 7 . 1 . Распределение памяти
Программы в MINIX 3 могут быть скомпилированы так, чтобы использовать
обьединенное пространство данных и кода. В этом случае под все составные час­
ти процесса (текст, данные и стек) выделяется единый блок памяти. Этот блок
используется ими совместно и освобождается единовременно. Такая схема распре­
деления памяти применялась по умолчанию в исходной версии MINIX. В MINIX 3,
напротив, по умолчанию принято компилировать программы с раздельными про ­
странствами данных и кода. Для ясности м ы сначала рассмотрим более простую
модель с общей памятью. Процессы с раздельными пространствами данных и ко­
да позволяют расходовать память эффективнее, но при этом возникают сложно­
сти, которые мы изучим позднее.
В обычном режиме выделение памяти в MINIX 3 происходит в двух ситуациях.
Во-первых, когда процесс разветвляется, дочернему процессу предоставляется
необходимая ему память. Во-вторых, когда процесс при помощи системного
вызова ехес заменяет свой образ, старый образ памяти процесса возвращается
в список свободных блоков памяти, а новая память выделяется на новом месте.
Положение нового образа процесса в памяти зависит от того, где найден первый
подходящий по размеру блок. Кроме того, когда процесс завершается (самостоя­
тельно или принудительно, по сигналу), занятая им память освобождается. Име­
ет место и третий случай, в котором системный процесс запрашивает память под
4. 7. Знакомство с менеджером п роцессов в M I N IX 3 469

собственные нужды. Например, драйвер памяти может запросит память под вир­
туальный диск. Это возможно лишь на этапе инициализации системы.
На рис. 4.28 показаны оба варианта выделения памяти. На рис. 4 . 28, а мы ви­
дим в памяти два процесса, А и В. Если процесс А разветвляется, мы попадаем
в ситуацию, представленную на рис. 4.28, б. Дочерний процесс является точной
копией процесса А. Если теперь дочерний процесс выполнит файл С, память
придет в состояние, показанное на рис. 4.28, в. Образ дочернего процесса был
заменен образом С.

Предел
верхней
памяти

А А А

M I N IXЗ M I N IXЗ M I N IXЗ


о �---�
а б в

Выделение памяти:
Рис. 4 . 2 8 . исходное состояние; б состояние после
а - -

вызова fork; в дочерний процесс сделал вызов ехес. Неиспользованные


-

области памяти отмечены штриховкой. Код и данные процесса находятся


в одном адресном пространстве
Обратите внимание, что область памяти, занимаемая потомком, освобождается
перед тем, как выделить память для нового образа, поэтому С может занять па­
мять, в которой раньше располагался потомок. Таким образом, после выпол­
нения нескольких пар вызовов f o rk и ехе с все процессы будут соседствовать
в памяти впритирку друг к другу. Если бы память для нового образа выделялась
первой, между процессами обязательно зияли бы дыры.
Добиться такого результата непросто. Представим себе возможное состояние
ошибки, когда памяти для выполнения вызова е х е с оказывается недостаточно.
Чтобы дочерний процесс мог каким-либо образом среагировать на ошибку, опре­
деление объема памяти, необходимой для выполнения операции, должно выпол­
няться до освобождения памяти дочерним процессом. Это означает, что память
дочернего процесса нужно рассматривать как свободную, в то время как она еще
используется.
Когда память распределяется по вызову f ork или ехе с , некоторая ее часть от­
дается новому процессу. В первом случае запросы дочернего и родительского
процессов удовлетворяются в равной мере. Во втором - менеджер процессов
оперирует значением из заголовка исполняемого файла. После того как память
однажды выделена, ни при каких условиях процесс не может посягнуть на до­
полнительную память.
Сказанное относится к программам, которые были скомпилированы с объеди­
ненными адресными пространствами кода и данных. Программы, у которых эти
470 Глава 4 . Уп равление памятью

пространства разделены, получают улучшенный механизм управления памятью,


который называется общим кодом (shared text). Когда подобный процесс делает
вызов f o rk, память выделяется только под стек и данные дочернего процесса.
Потомок получает в наследство исполняемый код, который используется роди­
тельским процессом. Когда делается вызов ехе с , в таблице процессов ищется
процесс, уже применяющий требуемый код. Если такой процесс обнаруживает­
ся, память, опять же, выделяется только для данных и стека. Однако разделение
кода усложняет завершение процесса. При завершении процесса всегда осво­
бождается память, занимаемая его данными и стеком. Память же, занимаемая
сегментом текста, освобождается только тогда, когда поиск в таблице процессов
показал, что ни один другой процесс этот код больше не интересует. Может по­
лучиться так, что процессу при запуске выделяется больше памяти, чем освобо­
ждается при его завершении. Мы говорим про случай, когда при запуске процесс
загружает собственный код, а по окончании оказывается, что этот код уже ис­
пользуется другими процессами.
На рис. 4.29 показано, как программа хранится в виде файла на диске и как она
располагается в памяти, когда MINIX 3 запускает процесс. Информация о раз­
мерах различных частей образа процесса находится в заголовке файла, так же
как и сведения о его полном размере. Для программы с объединенными адресны­
ми пространствами кода и данных в заголовке указывается суммарный размер
двух частей, они копируются напрямую в образ в памяти. Для сегмента данных
образа при копировании в память выделяется больший объем. Размер дополни­
тельной области памяти в байтах хранится в поле bss заголовка. Эта область
заполняется нулями и используется для неинициализированных статических
данных. Общий объем памяти, который будет выделен процессу, задается по­
лем общего размера в заголовке. Если, например, у программы код имеет размер
4 Кбайт, данные плюс дополнительный объем занимают 2 Кбайт, стек 1 Кбайт, -

а в заголовке указано выделить всего 40 Кбайт, то неиспользуемый промежуток


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

т Символы
Размер Данные
файла
на диске 1--_Код
_j_ ___-1
за гол овок
а б
Рис. 4.29. Распределение памяти дл я программы:а программа хранится на диске в виде
-

файла ; б внутреннее распределение памяти для одного процесса. На обеих частях


-

рисунка в нижней части расположены нижние адреса


4. 7. Знакомство с менедже ром пр оцессов в M I NIX З 47 1

Если программист знает, что общий объем памяти, необходимый для стека и дан­
ных программы в файле а . ou t , не превышает 10 Кбайт, то при помощи следую­
щей команды он может изменить поле заголовка исполняемого файла:
chmem = 1 0 2 4 0 a . ou t

После этого вызов е х е с будет выделять только 1 0 240 байт дополнительной па­
мяти. Так, для описанного примера будет выделено 1 6 Кбайт памяти. Из этой
памяти верхний килобайт отводится под стек, а 9 Кбайт образуют свободную об­
ласть для будущего роста стека и (или) области данных.
Для программ с раздельными адресными пространствами кода и данных (у кото­
рых компоновщик устанавливает специальный бит в заголовке программы) по­
ле общего размера в заголовке относится только к стеку и данным. Так, при за­
грузке программы с 4 Кбайт кода, 2 Кбайт данных, 1 Кбайт стека и суммарным
размером 64 Кбайт будет выделено 68 Кбайт памяти ( 4 Кбайт на код, 64 Кбайт
образуют адресное пространство данных, а 61 Кбайт останется для роста стека
и данных). Граница сегмента данных может быть изменена только при помощи
системного вызова brk. При выполнении этого вызова проверяется, не упирает­
ся ли новая граница сегмента данных в нижнюю границу стека, и соответствую­
щие изменения вносятся во внутренние таблицы. Это делается исключительно
внутри памяти, выделенной процессу, поскольку у операционной системы не за­
прашивается никаких новых блоков. Если же после изменения границы сегмент
данных будет пересекаться со стеком, вызов завершается ошибкой.
Тут нужно упомянуть небольшую семантическую сложность. Когда мы говорим
4:Сегмент� , мы имеем в виду область памяти, определяемую операционной систе­
мой. Но у процессоров Intel 80х86 есть специальные внутренние сегментные ре­
гистры и (у более новых из них) таблицы дескрипторов сегментов, обеспечи­
вающие аппаратную поддержку 4Сегментов�. Концепция сегмента в архитектуре
Intel сходна с тем, как сегменты определяются в MINIX 3, но это - не одно и то
же. Все ссылки на сегменты в этом тексте следует рассматривать в контексте их
определения в MINIX 3. Когда мы будем говорить об аппаратных сегментах, мы
явно укажем сегментные регистры и дескрипторы сегментов.
Это предупреждение может быть обобщено. Разработчики аппаратного обеспече­
ния часто целенаправленно стараются реализовать поддержку на своем оборудова­
нии конкретных операционных систем. Поэтому терминология описания регистров
и других аспектов архитектуры процессора обычно отражает то, как все это будет
использоваться. Подобные возможности часто полезны для разработчиков опера­
ционной системы, но практика не обязательно соответствует идеям производителя
оборудования . В результате термины, звучащие одинаково, но имеющие разное зна­
чение для операционной системы и оборудования, моrут привести к непониманию.

4 . 7 2 Обработка сообщени й
. .

Как и все остал ь ные компоненты О С M INIX 3, менеджер памяти управляется


с помощью сообщений. После инициализации системы менеджер памяти входит
в свой главный цикл, в котором сообщение принимается, выполняется содержа­
щийся в сообщении запрос и отправляется ответное сообщение.
472 Глава 4. Уп равление памятью

Менеджер процессов может получать две категории сообщений. Для высокопри­


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

Таблица 4 . 3 . Типы и параметры сообщений для менеджера памяти и ответные значения


Тип сообщен ия Параметры Ответное значение
fork Нет PID потомка
exit Код возврата При успехе без ответа
wait Нет Состояние
waitpid Нет Состояние
brk Новый размер Новый размер
ехес Указатель на исходный стек При успехе без ответа
kill Идентификатор процесса и сигнал Состояние
alarm Время ожидания в секундах Оставшееся время
pause Нет При успехе без ответа
sigaction Номер сигнала, код операции, старый код Состояние
операции
sigsuspend Маска сигналов При успехе без ответа
sigpending Нет Состояние
sigprocmask Способ изменения, новый набор, старый набор Состояние
sigreturn Контекст Состояние
getuid Нет Идентификатор UID его
и
действующее значение
getgid Нет Идентификатор GID и его
действующее значение
getpid Нет Идентификатор PID и его
действующее значение
setuid Новый идентификатор UID Состояние
setgid Новый идентификатор GID Состояние
setsid Новый идентификатор SID Группа процессов
getpgrp Новый идентификатор GID Группа процессов
ptrace Запрос, PID, адрес, данные Состояние
reboot Действие (останов, перезагрузка, сбой) При успехе без ответа
svrctl Запрос, данные (в зависимости от функции) Состояние
getsysinfo Запрос, данные (в зависимости от функции) Состояние
getprocnr Нет Номер процедуры
memalloc Размер, указатель на адрес Состояние
memfree Размер, адрес Состояние
getpriority PID, тип, значение Приоритет
setprio rity PID, тип, значение Приоритет
gettimeofday Нет Время, время работы
4. 7. Знакомство с менеджером пр оцессов в M I NIX 3 473

Вызовы f o rk, exi t , wa i t , wa i t p i d, brk и ехес тесно связаны с выделением


и освобождением памяти. Вызовы k i l l , a l arrn и p au s e связаны с сигналами,
такими как s i gac t i on, s i g s u sp end, s i gpendi ng, s i grna s k и s i greturn.
Они оказывают влияние и на память, так как при завершении процесса по сигна­
лу занимаемая этим процессом память высвобождается. Семь вызовов get / s e t
не имеют никакого отношения к управлению памятью, н о , очевидно, связаны
с управлением процессами. Остальные вызовы с одинаковым успехом могли бы
обрабатываться и менеджером процессов, и файловой системой, поскольку дру­
гих компонентов, которые могли бы их обслужить, нет. Они были помещены в код
менеджера процессов просто потому, что код файловой системы и без того доста­
точно объемен. Вызов p t r a c e , применяемый при отладке, а также вызовы t irne,
s t irne и t irne s попали сюда по той же причине.
Вызов reboot действует в масштабе всей системы, однако его главным назначени­
ем является передача сигналов для контролируемого завершения всех процессов,
поэтому было логично поместить его в менеджер процессов. То же касается и вызо­
ва svrc t l , основное применение которого - включение и отключение подкачки.
Возможно, вы заметили, что последние два упомянутых вызова, reboot и svrct l ,
н е приведены в табл. 1 . 1 . Т о ж е касается и других системных вызовов, представ­
ленных в табл. 4.3: ge t sy s i n f o , getpro cnr, rnerna l l o c , rnernfree и get s e t ­
p r i o r i ty. Все они н е предназначены для обычных пользовательских процес­
сов и не являются частью стандарта POSIX. Они нужны лишь операционным
системам, подобным MINIX 3. В системах с монолитным ядром действия, вы­
полняемые этими вызовами, могли бы осуществляться функциями, встроенны­
ми в ядро. Однако в MINIX 3 традиционные компоненты операционной системы
выполняются в пользовательском пространстве, что и требует дополнительных
системных вызовов. Некоторые системные вызовы несут чуть больше функций,
чем обеспечение интерфейсов с вызовами ядра (имеются в виду вызовы, запра­
шивающие различные службы ядра у системного задания).
Как отмечалось в главе 1, несмотря на наличие библиотечной подпрограммы
sbrk, одноименного системного вызова не существует. Подпрограмма вычисля­
ет объем необходимой памяти, инкрементируя или декрементируя текущий раз­
мер согласно передаваемому параметру, и выполняет вызов brk для установки
нового размера. Аналогично, не существует отдельных системных вызовов для
функций g e t e u i d и ge t eg i d. Вызовы ge t u i d и getp i d возвращают соответ­
ственно эффективный и реальный идентификаторы. Аналогично, getp i d воз­
вращает идентификаторы вызывающего процесса и его родителя.
Ключевой структурой данных для обработки сообщений является c a l l_vec,
определяемая в файле t а Ы е . с . Она содержит указатели на подпрограммы, об­
рабатывающие все типы сообщений. Когда сообщение попадает в менеджер про­
цессов, команды в главном цикле извлекают из него тип сообщения и записыва­
ют его в глобальную переменную c a l l_nr. Позже это значение используется
как индекс в таблице c a l l_vec, и по нему находится адрес подпрограммы для
обработки прибывшего сообщения. Затем эта подпрограмма отрабатывает, выпол­
няя системный вызов. Значение, которое возвращает обработчик, отправляется
474 Глава 4. Уп равление памят ью

обратно, чтобы сообщить о выполнении или ошибке. Этот механизм подобен ме­
ханизму обработки системных вызовов (шаг 7 на рис. 1 . 1 2), но только в адрес­
ном пользовательском пространстве, а не в пространстве ядра.

4 . 7 3 Структуры дан н ых и ал горитм ы


. .

менеджера процессов
У менеджера процессов есть две ключевые структуры данных: таблица процес­
сов и таблица свободных блоков памяти.
Из табл. 2 . 1 видно, что одни поля таблицы процессов необходимы ядру, дру­
гие нужны для управления процессами, а третьи требуются файловой системе.
В MINIX 3 каждая из трех частей операционной системы поддерживает собст­
венную таблицу процессов, содержащую только те поля, которые интересны ей.
Записи всех трех таблиц соответствуют друг другу, чтобы не усложнять дело.
Так, ячейка k таблицы процессов менеджера процессов соответствует тому же
процессу, что и ячейка k таблицы процессов файловой системы. При создании
или уничтожении процесса необходимо обновлять записи во всех трех таблицах,
чтобы поддерживать их в согласованном состоянии.
Исключения составляют процессы, не видимые за пределами ядра. К ним от­
носятся встроенные в ядро таймерное и системное задания ( CLOCK и SYSTEM ) ,
а также �заполнители� I DLE и KERNEL. В таблице процессов ядра их номера отри­
цательны, а в таблицах менеджера процессов и файловой системы соответствую­
щих им записей вообще нет. Таким образом, строго говоря, утверждение о ячейках
таблиц с номером k справедливо лишь при значении k, большем или равном О.

П роцессы в памяти
Таблица процессов менеджера процессов называется mp r o c , она определена
в файле src / serve rs / pm/mproc . h. В этой таблице содержатся поля, связанные
с выделением памяти, а также некоторые дополнительные сведения. Самым важ­
ным полем является массив mp_s eg, у которого есть три записи, для сегмента
текста (кода), данных и стека. Все его элементы представляют собой структуры,
содержащие виртуальный и физический адреса, а также длину сегмента, причем
эти величины измеряются не в байтах, а в кликах. Размер клика (минимального
блока памяти) зависит от реализации. В ранних версиях MINIX используется
значение 256 байт, а в MINIX 3 - 1 024 байта. Все сегменты начинаются на гра­
нице клика и содержат целое число кликов.
Способ записи информации о выделении памяти иллюстрирует рис. 4.30. На ри­
сунке показан процесс с такой структурой: 3 Кбайт кода, 4 Кбайт данных, зазор
имеет величину 1 Кбайт, а после него следует стек объемом 2 Кбайт. Всего вы­
делено 1 О Кбайт памяти. На рис. 4.30, б показано, что хранится в полях длины
и виртуального и физического адресов для этих сегментов при условии, что
процесс не разделяет адресные пространства кода и данных. В этой модели
размер сегмента кода всегда считается равным нулю, а сегмент данных содержит
и данные, и код. Когда показанный на рисунке процесс обращается к виртуаль-
4 .7. Знакомст в о с м енеджер ом пр оце ссо в в M I NIX 3 475

ному адресу О или совершает переход по этому адресу, происходит обращение


к физическому адресу Ох32000 (в десятичной записи - 200 К). В кликах этот
адрес записывается как Охс8.

Адрес Виртуальный Физический Длина


l 1 (шестнадцатеричный)
Стек Ох8 OxdO Ох2
21 О К (Ох34800) Данные Охс8 Ох7
Стек 208 К (Ох34000)
о
Код о Охс8 о
207 К (ОхЗЗсОО)
б
Данные
203 К (Ох32с00) Виртуальный Физический Длина
Код Стек Ох5 OxdO Ох2
200 К (Ох32000) Данные о ОхсЬ Ох4
1 r Код о Охс8 ОхЗ
а в

Рис . 4.30.Способ записи информации о выделении памяти: - процесс в памяти;


а
б- представление процесса с единым адресным пространством данных и кода;
в - представление процесса с раздельными пространствами данных и кода
Нужно отметить, что виртуальный адрес, с которого начинается стек, зависит от
общего объема памяти, выделенной процессу. Так, если при помощи команды
chmem модифицировать заголовок исполняемого файла, чтобы при запуске для
программы резервировалось больше динамически распределяемой памяти ( проме­
жуток между сегментами стека и данных), то при следующем запуске программы
начало стека будет расположено выше. Если стек вырастет на один клик, его запись
должна измениться с тройки (Ох8, OxdO, Ох2 ) на тройку (Ох7, Oxcf, Ох3). Обратите
внимание на то, что в этом примере рост стека на один клик привел бы к исчез­
новению промежутка, если бы общий объем выделенной памяти не увеличился.
Аппаратное обеспечение процессоров 8088 не поддерживает прерывание пере­
полнения стека, и в M INIX стек задается так, чтобы на 32-разрядных процес­
сорах не инициировать прерывание до тех пор, пока сегмент стека не пересечется
с сегментом данных. Таким образом, информация о стеке будет обновлена толь­
ко при следующем системном вызове brk, при этом операционная система явно
считывает значение SP (указатель стека) и пересчитывает параметры стека. На
машинах, поддерживающих аппаратный контроль переполнения стека, инфор­
мация о стеке должна обновляться, как только стек переполнит свой сегмент.
По причинам, которые мы далее обсудим, в 32-разрядной версии MINIX 3 для
процессоров Intel этого не делается.
Ранее мы уже упоминали, что усилия разработчиков аппаратного обеспечения
не всегда приводят к тем результатам, которые нужны программистам. Даже ра­
ботая в защищенном режиме Pentium, MINIX 3 не отслеживает ситуацию, когда
стек переполняет свой сегмент. Хотя в этом режиме MINIX 3 обрабатывает попыт­
ки обратиться к памяти за пределами сегмента, параметры которого определены
476 Глава 4 . У правление памятью

дескриптором сегмента (см. рис. 4.24), в MINIX 3 дескрипторы сегмента данных


и стека всегда идентичны. В MINIX 3 стек и данные находятся в едином адрес­
ном пространстве, и благодаря этому они могут расширяться за счет межсегмент­
ного зазора. Но это не более чем внутреннее представление MINIX 3. С точки
зрения процессора при обращении к памяти между сегментами нет никакой
ошибки, так как она является частью общего сегмента данных и стека. Аппарат­
ное обеспечение помогает отслеживать серьезные ошибки, например попытки
обращения к памяти за пределами комбинированной области из данных, зазора
и стека, благодаря чему один процесс можно защитить от ошибок другого про­
цесса, но уберечь процесс самого от себя таким образом нельзя.
MINIX 3 это делает сознательно. Если у вас есть аргументы за то, чтобы отка­
заться от разделяемого аппаратного сегмента и зазора, мы не будем спорить. Аль­
тернативой могут быть два раздельных аппаратных сегмента для стека и данных.
Такой подход обеспечивает большую безопасность в отношении некоторых оши­
бок, но превращает MINIX 3 в чудовище, пожирающее память. Исходные коды
системы открыты для всех, кто желает поэкспериментировать с таким подходом.
На рис. 4.30, в показано, как будут определены сегменты в случае раздельных ад­
ресных пространств кода и данных. Здесь уже и сегмент стека, и сегмент данных
имеют ненулевую длину. Показанный на рис. 4.30, б и в массив mp_seg большей
частью применяется для преобразования виртуальных адресов в физические.
Зная виртуальный адрес и адресное пространство, которому он принадлежит,
несложно проверить, является этот адрес допустимым (то есть попадает ли он
в сегмент), и рассчитать, какому физическому адресу он соответствует. Напри­
мер, такое преобразование осуществляет вспомогательная подпрограмма umap_
l o c a l , которую задания ввода-вывода привлекают для обмена данными с поль­
зовательскими процессами.

Общий код
При выполнении процесса содержимое его областей данных и стека может ме­
няться, но код не меняется никогда. Часто случается, что несколько процессов
выполняют одну и ту же программу, например, несколько пользователей могут
работать с одной оболочкой. Поэтому общий код повышает эффективность памя­
ти. Когда системный вызов е х е с собирается загрузить процесс, он открывает
файл с образом этого процесса и считывает заголовок. Если у процесса раздель­
ные адресные пространства кода и данных, среди всех ячеек таблицы mproc осу­
ществляется поиск по полям mp_dev, mp_i no, mp_c t ime. Эти поля содержат
информацию о номере индексного узла и времени модификации образов, испол­
няемых другими процессами. Если обнаруживается процесс, который уже вы­
полняет нужную программу, то выделять память под еще одну копию кода не
нужно. Вместо этого в карту памяти нового процесса в поле mp_seg [ Т ] записы­
вается указатель на ту область памяти, где уже хранится код, а память выделяет­
ся только под данные и стек (рис. 4.3 1 ). Если загруженного образа найдено не
было или адресные пространства кода и данных объединены, память выделяется
согласно рис. 4.30 и заполняется данными с диска.
4 .7. Знакомство с менеджером пр оцессов в M I N IX 3 477

J 1 •S
:а •S
::i: s
..а ""
t:; (,)
Q)

� 7 С\1
s ::i:
"' s
s s
a:i & .§:
Ох5 Oxf5 Ох2 Стек
о OxfO Ох4 Данные
о Охс8 ОхЗ Код
•S

::i:
•S
s
Процесс 2
..а "" в
t:; (,)
Q)


7 С\1
s ::i:
"' s
s s
a:i & .§:
Стек Ох5 OxdO Ох2
Данные о ОхсЬ Ох4
Код о Охс8 ОхЗ
Процесс 1
а
Ох32000
1 r
б
Использование общего кода:
Ри с . 4 . 3 1 . карта памяти для раздельных адресных
а -
пространств кода и данных, как на предыдущем рисунке; б распределение памяти после
-
запуска второго процесса, выполняющего тот же код; в карта памяти второго процесса
-

Помимо информации о сегментах, mpr oc хранит идентификатор самого процес­


са и его родителя, идентификаторы пользователя и группы (реальное и эффек­
тивное значения), информацию о сигналах и код возврата, если процесс уже за­
вершился, но его родитель еще не завершил вызов wa i t . В mp r oc есть поля
таймера для s i ga l arm, а также накопленного времени пользователя и системы
для дочерних процессов. В ранних версиях MINIX за эти поля отвечало ядро,
однако в MINIX 3 это делает менеджер процессов.

Список свободных участков


Таблица свободных участков памяти, hole, заданная в файле sr c / s e rve r s /
pm / a l l o c . с, является другой важнейшей таблицей менеджера процессов. За­
писи в этой таблице расположены в порядке возрастания адресов памяти. Про­
межутки между сегментами стека и данных зарезервированы для определенного
процесса и не считаются незанятыми участками памяти, поэтому они не входят
в эту таблицу. Каждая запись таблицы 4дыр» содержит три поля: базовый адрес
свободного блока памяти в кликах, длину блока тоже в кликах и указатель на
следующую запись в списке. Список является односвязным, другими словами,
зная одну запись, легко найти любую следующую, но если необходимо найти
предшествующую запись, придется перебирать список с самого начала. Из-за ог­
раниченности объема книги текст файла a l l oc . с в ней не приведен. Вы можете
найти этот файл на компакт-диске. Тем не менее код, определяющий список
свободных участков, прост и приведен в листинге 4. 1 .
478 Глава 4. У п р авление п ам ятью

Листинг 4. 1 . Список свободных участков представляет собой массив структур hole


PRIVATE s t ruct ho l e {
s t ruct ho l e * h_next ; / * Ука з атель на сле ду ющий элемент с пис ка дыр * /
phys_c l i c ks h_ba s e ; / * Н ачало списк а * /
phys_c l i ck s h_l en ; / * Р а з мер с писка * /
ho l e [ NR_HOLES ] ;

Причина, по которой положение сегментов и свободных блоков измеряется


в кликах, а не в байтах, проста - это более эффективно. В 1 6-разрядном режиме
для записи адресов памяти используются 1 6-разрядные целые числа, что позво­
ляет при размере клика 1 024 байт поддерживать до 64 Мбайт памяти. В 32-раз­
рядном режиме таким образом может адресоваться до 2 42 байт (2 32 х 2 10 ), что со­
ставляет 4096 Гбайт.
Основными действиями над списком свободных участков являются выделение
блока заданного размера и освобождение ранее выделенного блока. При вьщелении
блока список просматривается в порядке возрастания адресов, пока не обнару­
живается достаточно большой свободный блок. Затем свободный участок умень­
шается на величину выделенного сегмента или же, в том редком случае когда
размер �дыры» равен затребованному размеру блока, свободный участок вовсе
исключается из списка. Это - быстрый и простой механизм, но он подвержен
как внутренней (при выделении блока может быть впустую израсходовано до
1 023 байт, так как всегда выделяется целое число кликов), так и внешней фраг­
ментации.
Когда процесс завершается и удаляется из памяти, память, выделенная под дан­
ные и стек, возвращается в список свободных блоков. Если адресные простран­
ства кода и данных процесса объединены, это означает, что возвращается вся за­
нятая процессом память, поскольку выделение отдельного блока под код здесь
не предусмотрено. Если адресные пространства разделены и обнаруживается,
что код процесса больше никем не используется, то занятая кодом память также
высвобождается. При возврате блока в список свободных участков он, если
это возможно, объединяется с соседними свободными блоками, соответственно,
двух смежных свободных участков никогда не возникает. Таким образом, при
работе системы постоянно меняются количество, положение и размеры свобод­
ных участков памяти. Когда все пользовательские процессы завершаются, вся
свободная память вновь готова к распределению. Она не обязательно образует
один сплошной блок, так как физическая память может прерываться областями,
недоступными даже операционной системе. Например, в IBM РС-совместимых
системах доступна память ниже 640 Кбайт и выше 1 Мбайт, а промежуток между
этими адресами занят постоянной памятью и памятью, зарезервированной для
операций ввода-вывода.

4. 7 . 4 . Системные вызовы fork, exit и wait


Когда создаются или уничтожаются процессы, необходимо выделять и освобож­
дать память. Кроме того, нужно обновлять таблицу процессов, в том числе и те
ее части, которые поддерживаются ядром и файловой системой. Эту деятельность
4 .7. Знакомство с менеджером п роцессов в M I N IX 3 479

координирует менеджер процессов. За создание процесса отвечает вызов f ork,


выполняющийся за несколько шагов. Эта последовательность такова.
1. Проверить, заполнена ли таблица процессов.
2. Попытаться выделить память для данных и стека дочернего процесса.
3. Скопировать содержимое данных и стека родительского процесса в память
потомка.
4. Найти свободную ячейку в таблице процессов и скопировать в нее запись ро-
дительского процесса.
5. Поставить на учет карту памяти дочернего процесса в таблице процессов.
6. Выбрать идентификатор (PID) для дочернего процесса.
7. Передать информацию о потомке ядру и файловой системе.
8. Сообщить ядру сведения о карте памяти потомка.
9. Отправить дочернему и родительскому процессам ответные сообщения.
Остановить вызов f ork на полпути сложно и неудобно, поэтому менеджер про­
цессов, чтобы всегда знать, есть ли свободные ячейки в таблице процессов, под­
держивает счетчик существующих процессов. Если таблица еще не заполнена,
делается попытка выделить память под данные и стек дочернего процесса. Для
процессов с разделенными пространствами кода и данных запрашивается только
память, достаточная для размещения стека и данных. Если этот шаг пройден
успешно, f ork гарантированно выполняется. Затем выделенная область памя­
ти заполняется, в таблице процессов находится и заполняется ячейка нового
процесса, для него выбирается PID и другие части системы информируются
о создании нового процесса.
Процесс полностью завершается только по факту наступления двух следующих
событий:
+ Процесс закончил выполняться (самостоятельно или по сигналу).
+ Родительский процесс, чтобы выяснить, чем все закончилось, выполнил сис-
темный вызов wa i t .
Процесс, прекративший выполняться или завершенный по сигналу, при условии,
что его родитель еще не выполнил вызов wa i t , попадает в своего рода состояние
зомби. Он исключается из планирования, сигнальный таймер у него отключается
(если он был включен), но процесс остается в таблице процессов. Память про­
цесса при этом освобождается. В состоянии зомби процесс находится временно,
и оно редко длится долго. Когда родительский процесс наконец выполняет вы­
зов wa i t, занятая ячейка в таблице процессов освобождается, и файловая систе­
ма и ядро уведомляются об этом.
Проблема возникает в случае, если родитель завершающегося процесса сам уже
завершен. Если не предпринять никаких действий, потомок станет полноправным
зомби. Поэтому таблицы загодя меняются так, чтобы процесс стал потомком про­
цесса i n i t . При запуске системы процесс i n i t считывает файл / e t c / t ty t ab
с информацией обо всех терминалах и для обслуживания каждого терминала
480 Глава 4. Уп равление памятью

ответвляет от себя дочерний процесс. Затем он входит в состояние блокировки,


ожидая уведомлений о завершении. Таким образом, осиротевшие зомби быстро
добиваются.

4 . 7 . 5 . Систем н ы й вызов ехес


Когда с терминала поступает команда, оболочка ответвляет новый процесс, который
выполняет запрошенную команду. Этим мог бы заняться один системный вызов,
который бы одновременно решал задачи вызовов f ork и ехе с , но они разделены
по одной очень веской причине: чтобы упростить реализацию перенаправления
ввода-вывода. Если стандартный ввод перенаправлен и оболочка выполняет ветв­
ление, то потомок, перед тем как выполнить команду, закрывает стандартный
ввод, а затем открывает новый. Таким образом, запущенный процесс наследует
перенаправление стандартного ввода. То же относится и к стандартному выводу.
Системный вызов ех е с является самым сложным в MINIX 3. Он должен за­
менить текущий образ процесса новым и, в том числе, установить новый стек.
Разумеется, образ должен быть исполняемым двоичным файлом. Допустимо,
чтобы исполняемый файл представлял собой сценарий, интерпретируемый дру­
гой программой, например оболочкой или программой p e r l . В этом случае в об­
раз помещается двоичный код интерпретатора, а имя сценария передается ему
в качестве аргумента. В данном разделе мы рассмотрим простой пример вызова
ехес для бинарного исполняемого файла, а при обсуждении реализации выяс­
ним, что ему нужно дополнительно сделать для исполнения сценария.
Этапы выполнения этого системного вызова.
1. Проверить, является ли файл исполняемым.
2. Считать из заголовка файла размеры сегментов и общий требуемый объем
памяти.
3. Узнать у выполнившего запуск процесса аргументы и переменные окружения.
4. Выделить область памяти и освободить старую область.
5. Копировать в новый образ стек.
6. Записать в новый образ в памяти данные (и, возможно, код).
7. Проверить и, если они установлены, обработать биты s e t u i d, s e t g i d.
8. Подправить запись в таблице процессов.
9. Сообщить ядру, что процесс готов к запуску.
Каждый из шагов сам, в свою очередь, состоит из серии более мелких шагов, неко­
торые из них могут завершиться неудачей. Например, доступной памяти может
не хватить для запуска процесса. Порядок, в котором производятся проверки,
продуман так, чтобы гарантировать, что старый образ процесса в памяти остает­
ся до тех пор, пока не будет уверенности, что вызов е х е с завершится успехом.
Это делается во избежание ситуации, когда новый образ сформировать невозмож­
но, а к старому уже не вернуться. Как правило, вызов е х е с не передает управле­
ние обратно, но если вызов �провалился» , процесс вновь получает управление
и уведомляется об ошибке.
4 .7 . Знакомство с менеджером п роцессов в M I N IX 3 48 1

Некоторые из перечисленных шагов нуждаются в небольшом пояснении. Преж­


де всего, это вопрос о том, достаточно ли для нового процесса места. После того
как выяснена потребность в памяти (с возможными проверками, есть ли уже за­
пущенные процессы с тем же кодом), то, чтобы узнать, достаточно ли памяти,
просматривается список свободных блоков. Это делается до освобождения ста­
рой памяти, так как в противном случае, если памяти не хватит, вернуть старый
образ будет проблематично.
Однако такая проверка чересчур строга. Иногда отклоняется вызов ехе с , кото­
рый, фактически, мог бы быть выполнен. Например, предположим, что выпол­
няющий вызов е х е с процесс занимает 20 Кбайт и другие процессы его код не
разделяют. Далее, представим, что есть свободный блок объемом 30 Кбайт, а но­
вый образ требует 50 Кбайт памяти. Выполняя проверку до освобождения памя­
ти, мы обнаружим, что доступно только 30 Кбайт памяти, и вызов будет откло­
нен. Если бы память сначала высвобождалась, то вызов мог бы быть выполнен,
в зависимости от того, объединятся ли при освобождении памяти имеющийся
свободный блок размером 30 Кбайт с новым, размером 20 Кбайт. Более сложный
алгоритм мог бы лучше обрабатывать подобные ситуации.
Еще одно потенциальное улучшение - искать два свободных блока, один для
сегмента кода и один для сегмента данных, если адресные пространства запус­
каемого процесса разделены. Сегменты не обязательно располагать непрерывно.
Более тонкий нюанс - умещается ли новый процесс в виртуальном адресном
пространстве. Суть проблемы в том, что память выделяется не байтами, а 1024-
байтовыми кликами. Каждый клик должен целиком принадлежать одному сегмен­
ту и не может, например, быть наполовину занят данными, наполовину стеком,
так как все управление памятью производится в кликах.
Чтобы стало очевиднее, почему это приводит к проблемам, заметим, что на
1 6-разрядных системах (8086 и 80286) адресное пространство ограничено вели­
чиной 64 Кбайт, что составляет 64 клика ( 1 024-байтовых). Представим теперь,
что программе с раздельными адресными пространствами кода и данных требу­
ется 40 ООО байт под код, 32 770 байт под данные и 32 760 байт под стек. Тогда
сегмент данных займет 33 клика, причем в последнем клике будут использованы
лишь 2 байта, хотя он будет принадлежать сегменту целиком. Под стек потребу­
ется 32 клика. Для данных и стека вместе нужно более 64 кликов, таким образом,
они не смогут сосуществовать, хотя необходимое количество байтов умещается
в виртуальной памяти (едва-едва). В теории эта проблема относится ко всем ма­
шинам, у которых размер клика более одного байта, но на практике для процес­
соров класса Pentium она возникает исключительно редко, так как на таких ма­
шинах допустимы большие сегменты (более 4 Гбайт). К сожалению, код должен
выявлять подобные ситуации. Система, не проверяющая маловероятные, но воз­
можные ошибки, находится под угрозой неожидаемой аварии в случае, если хоть
одна из ошибок произойдет.
Другой важный вопрос - начальная установка стека. Библиотечный вызов, обыч­
но используемый для выполнения ехе с , выглядит так:
execve ( name , a rgv , envp ) ;
482 Глава 4. Уп равление п амят ью

Здесь name указатель на имя загружаемого файла, argv ссылается на массив


-

указателей на аргументы, а указатель envp содержит адрес массива указателей,


ссылающихся на строки переменных окружения.
Достаточно просто реализовать вызов е х е с , передав эти три указателя в сооб­
щении менеджеру процессов и позволив ему самостоятельно извлечь имя файла
и адреса двух массивов. Но тогда для каждого аргумента потребовалось бы отправ­
лять системному заданию как минимум одно сообщение, а возможно, и больше,
так как менеджер процессов не знает, какого размера каждая последующая порция.
Чтобы снизить накладные расходы, связанные со считыванием всех этих кусоч­
ков, была выбрана совершенно иная стратегия. Библиотечная процедура execve
сама строит новый стек и передает менеджеру процессов его базовый адрес и раз­
мер. Создавать новый стек в пользовательском пространстве гораздо эффектив­
нее, поскольку указатели на аргументы окажутся локальными и не будут ссы­
латься на другое адресное пространство.
Чтобы яснее понять этот механизм, рассмотрим пример. Пусть пользователь
вводит в оболочке следующую команду:
ls -1 f . c g . c

В этом случае оболочка интерпретирует ее, вызывая библиотечную процедуру:


execve ( " / Ь i n / l s " , argv , envp ) ;

Содержимое двух массивов указателей показано на рис. 4.32, а. Затем процедура


execve, работая в пользовательском пространстве, строит новый стек (рис. 4.32, б).
В конечном итоге стек при выполнении менеджером памяти системного вызова
ехес копируется без изменений.

Когда стек, в конце концов, попадает в пользовательский процесс, он не помещает­


ся по нулевому виртуальному адресу. Вместо этого он записывается в конец выде­
ленной области памяти, размер которой определяется по заголовку исполняемо­
го файла. Для примера предположим, что общий размер составляет 8 1 92 байт, то
есть последний доступный программе байт имеет адрес 8 1 9 1 . Тогда менеджер
процессов располагает в стеке указатели так, как показано на рис. 4.32, в.
Когда системный вызов ехес завершится и программа начнет работу, стек при­
дет в состояние, показанное на рис. 4.32, в, а указатель стека станет содержать
значение 8 1 36. Однако еще одна проблема остается нерешенной. Главная проце­
дура исполняемого файла, скорее всего, объявлена примерно так:
ma in ( argc , argv , envp ) ;

Поскольку рассматривается компилятор С, ma i n является всего лишь одной из


функций. Компилятор не знает о ее особой роли, поэтому строит ее код так, как
если бы аргументы передавались ей по стандартному соглашению вызова языка С,
когда последний аргумент идет в стеке первым. Поскольку значениями аргумен­
тов являются одно целое число и два указателя, ожидается, что они займут три
слова, предшествующие адресу возврата. Естественно, показанный на рис. 4.32, в
стек выглядит совершенно иначе.
Решение в том, чтобы выполнение программы не начиналось с функции ma i n.
Вместо нее управление сначала получает маленькая стартовая ассемблерная
4. 7. Знакомство с менеджером п роцессов в M I N IX 3 483

подпрограмма crt s o, которую компоновщик помещает в сегмент кода по �нуле­


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

\О t s а 52 \О t s а 81 88 \О t s а 8188
Массив 1 r s u 48 1 r s u 8184 1 r s u 8184
переменных Е м =
44 =
Е м 81 80 =
Е м 81 80
окружения 1
с
1 1

о
о Н \О 40 о Н \О с 81 76 о Н \О с 81 76
g \О с 36 g \О с 81 72 g \О с 81 72
с ____ _,
". f \О 1 32 f \О 1 81 68 f \О 1 81 68
НОМЕ /usr/ast
=
- \О s 1 28 - \О s 1 81 64 - \О s 1 81 64
о 24 о 81 60 о 81 60
42 20 81 78 81 56 81 78 81 56
о 16 о 81 52 о 81 52
38 12 81 74 8148 81 74 8148
Массив 34 8 81 70 8144 81 70 8144
аргументов 31 4 81 67 8140 81 67 8140
с 28 о 81 64 81 36 81 64 81 36
�� епvр
81 56
81 36
81 32
81 28
-1
ls argv 4 8124
argc returп 81 20
а б в г

Создание стека в пользовательском пространстве:


Рис . 4 . 3 2 . массивы, передаваемые а -

подпрограмме execve; б созданный стек; в стек после перемещения менеджером


- -

памяти; стек, который процедура видит в начале выполнения


г -

Если программист в конце кода функции rna i n не сделал вызов exi t, то после
ее завершения управление передается обратно в подпрограмму runt irne. Опять
же, с точки зрения компилятора, rna i n обычная функция, и для возврата из
-

нее компилятор генерирует стандартный код. Большая часть кода 32-разрядной


версии c r t s o приведена в листинге 4.2. Комментарии поясняют выполняемые
действия. Не вошли в этот листинг лишь фрагменты кода, инициализирующие
окружение (на случай, если это не сделает программист), загружающие из стека
помещенные туда регистры, а также несколько строк, устанавливающие флаг на­
личия/отсутствия математического сопроцессора. Весь код вы можете просмот­
реть, открыв файл s rc / l iЬ / i 3 8 6 / r t s / c r t s o . s .
Л истинг 4 . 2 . Ключевая часть стартовой подпрограммы ruпtime
push есх push envi ron
push edx push argv
push еах push argc
call _ma i n ma i n ( a rgc , a rgv , envp )
push еах push exi t_s t a t u s
call _ex i t
hlt е с л и в ы з о в ex i t не удалс я , вызывается прерыв ание
484 Глава 4. Уп равление памятью

4 . 7 . 6 . Систем н ы й вызов brk


При помощи библиотечных подпрограмм brk и sbrk изменяется положение
верхней границы сегмента данных. Первая из них берет абсолютное значение но­
вого адреса (в байтах) и передает его системному вызову brk. Вторая вычисляет
положительное или отрицательное приращение текущего положения, вычисляет
новый размер и вызывает brk. Отдельного системного вызова sbrk в действи­
тельности нет.
Интересен вопрос: как sbrk определяет текущий размер, чтобы вычислить но­
вый? Ответом на него является переменная brks i z e, в которой всегда хранится
размер и откуда brk и sbrk всегда могут его считать. Эта переменная инициали­
зируется генерируемым компилятором символом, определяющим либо суммар­
ный размер кода и данных (общее адресное пространство), либо только размер
данных (раздельные адресные пространства). Имя и даже само существование
такого символа привязаны к компилятору, поэтому вы не найдете его обозначе­
ние ни в одном из заголовочных файлов исходных кодов. Символ задается в биб­
лиотеке, в файле brks i z e . s. Где именно он расположен, зависит от системы, но
он будет в том же каталоге, что и c r t o . s .
Для менеджера процессов несложно выполнить вызов brk. Все, что ему нужно
сделать, - это проверить, что сегмент умещается в адресном пространстве, обно­
вить таблицы и уведомить ядро.

4 . 7. 7 . Обработка си гналов
В главе 1 сигналы были определены как средство передачи информации процес­
су, который не обязательно ждет ввода. Задается набор сигналов, и у каждого
сигнала есть действие по умолчанию: либо завершить процесс, которому сигнал
адресован, либо игнорировать сигнал. Если бы других альтернатив не бьmо, обра­
ботку сигналов было бы просто понять и реализовать. Но при помощи системных
вызовов процессы способны менять это поведение. Процесс может потребовать,
чтобы любой сигнал (за исключением особого сигнала s i gki l l ) игнорировался.
Более того, процесс может перехватить сигнал, предписав, чтобы вместо дейст­
вия по умолчанию был вызван указанный им обработчик сиzнала (опять же, это
не относится к сигналу s i gki l l ). Таким образом, с точки зрения программиста
есть два этапа работы с сигналами: подготовительная фаза, когда определяется
ответная реакция на будущий сигнал, и ответная, когда сигнал генерируется и об­
рабатывается. Ответным действием может быть выполнение собственной подпро­
граммы-обработчика. В действительности есть и третья фаза. Когда пользова­
тельский обработчик завершается, специальный системный вызов восстанавли­
вает нормальную работу получившего сигнал процесса. Программисту об этой
третьей фазе знать не нужно, он пишет обработчики сигналов как обычные
функции, а заботу о вызове и завершении обработчиков и управлении стеком
берет на себя операционная система.
4. 7. Знакомство с менеджером пр оцессов в M I N IX 3 485

Таким образом, три фазы обработки сигналов выглядят следующим образом.


1. Подготовка - программный код готовится к возможному сигналу.
2. Реакция - сигнал принимается и в ответ выполняется действие.
3. Очистка - восстанавливается нормальное функционирование процесса.
В подготовительной фазе программа вправе в любой момент изменить реакцию
на сигнал при помощи нескольких системных вызовов. Самый общий из них -
вызов s i gac t i on, посредством которого можно указать, чтобы сигнал игнори­
ровался, обрабатывался (при этом вместо действия по умолчанию для такого
сигнала выполняется некоторый заданный пользователем код из самого процес­
са), или же восставить ответную реакцию, предлагаемую по умолчанию. При по­
мощи другого системного вызова, s i gp r ocmask, сигнал можно заблокировать,
тогда он будет поставлен в очередь и обработан только тогда, когда процесс раз­
блокирует сигналы этого типа. Эти вызовы можно делать в любой момент даже
из самой функции-обработчика. В MINIX 3 действия подготовительной стадии
осуществляются исключительно в менеджере процессов, так как все необходи­
мые структуры данных расположены в его части таблицы процессов. Для каж­
дого процесса в этой таблице имеется несколько переменных типа s i g s e t_t ,
в которых за каждый сигнал отвечает определенный бит. Одна из переменных
хранит информацию о том, какие сигналы необходимо игнорировать, другая -
какие сигналы обрабатывать и т. д. Кроме того, у каждого процесса есть массив
структур s i gac t i on, по одной на каждый сигнал, как показано в листинге 4.3.
В этой структуре присутствует переменная, хранящая адрес пользовательского
обработчика сигнала, а также дополнительное поле типа s i g s e t_t , где запоми­
нается информация о сигналах, заблокированных во время исполнения другого
обработчика. В поле адреса обработчика вместо адреса пользовательской функ­
ции могут храниться специальные данные, означающие, что данный сигнал дол­
жен быть игнорирован или обработан по умолчанию.
Л истинг 4 . 3 . Структура sigaction
s t ru c t s i ga c t i on
�s i ghand l e r_t s a_hand l e r ; / * S I G_DFL , S I G_IGN , S I G_ME S S
или у к а з атель на функцию * /
s i g s e t_t s a_ma s k ; / * сигналы , которые должны быть
блокиров аны в обработчике * /
int sa_f l a g s ; / * специал ьные флаги * /

Здесь следует отметить, что системные процессы, в частности менеджер процес­


сов, не могут перехватывать сигналы. Системные процессы используют новый
тип обработчика, S I G_ME S S , указывающий менеджеру процессов передать сиг­
нал с помощью уведомления SYS_S IG. Для уведомлений типа SYS_S I G не тре­
буется очистка.
В генерации сигнала участвуют многие компоненты операционной системы
M INIX 3. Начинается все с менеджера процессов, который решает, какой про­
цесс должен при помощи упомянутых выше структур получить сигнал. Если
данный сигнал обрабатывается, его необходимо доставить процессу. Для этого
486 Глава 4. Уп равление памятью

требуется сначала сохранить информацию о процессе, чтобы после обработки вос­


становить его нормальное состояние. Эта информация сохраняется в стеке про­
цесса, причем предварительно делается проверка, достаточно ли места в стеке.
Стеком заведует менеджер процессов, который и выполняет этот контроль, а за­
тем, чтобы поместить информацию в стек, вызывает системное задание, которое
также изменяет значение счетчика команд процесса, чтобы выполнился код обра­
ботчика. Когда обработчик завершается, делается системный вызов s i gr e turn.
Посредством этого вызова менеджер процессов и ядро участвуют в восстановлении
контекста - сигналов и регистров процесса, возвращая его к обычному режиму
работы. Если сигнал не обрабатывается, выполняется действие по умолчанию.
При этом если необходимо получить дамп ядра (то есть записать образ процесса
в файл для последующего анализа под управлением отладчика), может быть затро­
нута файловая система, а если процесс должен быть прекращен, затрагивается
как менеджер процессов, так и файловая система и ядро. Наконец, если сигнал
адресован группе процессов, менеджер памяти может предписать повторить эти
действия несколько раз.
Сигналы, о которых известно MINIX 3, определяются в файле i nc lude /
s i gnal . h, согласно стандарту POSIX. Они перечислены в табл. 4.4. В MINIX 3
объявлены все требуемые POSIX сигналы, но пока что не все они поддержива­
ются. Например, стандартом определен набор сигналов для управления задания­
ми, позволяющих перевести задание в фоновый режим и вернуть его обратно.
В MINIX 3 управление заданиями не поддерживается, но программы, которые
генерируют такие сигналы, могут быть перенесены для работы под управлением
MINIX 3. Неподдерживаемые сигналы в этом случае просто игнорируются. Под­
держка управления заданиями отсутствует потому, что при разработке пред­
полагалось реализовать возможность запуска программы с ее последующим
переводом в фоновый режим, что позволяет пользователю совершать другие
действия. В MINIX 3 по нажатию комбинации клавиш Alt+ F2 открывается но­
вый виртуальный терминал, дающий такую возможность. Виртуальные терми­
налы представляют собой примитивное подобие оконной системы, однако их на­
личие освобождает от необходимости управления заданиями и их сигналами, по
крайней мере, при работе на локальной консоли. Кроме того, M INIX определяет
ряд сигналов, не описанных в POSIX, а также несколько синонимов для РОSIХ­
имен с целью обеспечить совместимость с более старым исходным кодом.

Таблица 4.4. Сигналы MINIX 3, регламентированные POSIX. Знак (*) означает, что сигнал
зависит от аппаратной помержки, сигналы с пометкой (М) не описаны в POSIX и введены
в MINIX 3 для помержания совместимости с устаревшим кодом. Сигналы ядра являются
специфичными для MINIX 3 и предназначены для информирования системных процессов
о событиях системы. В таблицу не попали несколько устаревших имен и синонимов
Сигнал Описание Источни к
SIGHUP Отбой Системный вызов kill
SIGINT Прерывание Ядро
SIGQUIT Завершение Ядро
SIGILL Недопустимая инструкция Ядро (*)
4 .7. Знакомство с менеджером пр оцессов в M I N IX 3 487

Сигнал Описание Источник


SIGTRAP Прерывание трассировки Ядро (М)
SIGABRT Аномальное завершение Ядро
SIGFPE Ошибка при вычислениях с плавающей точкой Ядро (*)
SIGKILL Принудительное завершение (сигнал не может Системный вызов kill
быть игнорирован или обработан)
SIGUSR1 Задаваемый пользователем сигнал номер 1 Не поддерживается
SIGSEGV Нарушение целостности памяти Ядро (*)
SIGUSR2 Задаваемый пользователем сигнал номер 2 Не поддерживается
SIGPIPE Запись в канал, из которого никто не читает Файловая система
SIGALRM Сигнал таймера, истечение тайм-аута Менеджер процессов
SIGTERM Сигнал завершения программы Системный вызов kill
SIGCHLD Дочерний процесс завершил работу или остановился Не поддерживается
SIGCONT Продолжить работу после останова Не поддерживается
SIGSTOP Остановить выполнение процесса Не поддерживается
SIGTSTP Интерактивный сигнал останова Не поддерживается
SIGТТIN Фоновый процесс пытается выполнить ввод Не поддерживается
SIGТТOU Фоновый процесс пытается выполнить вывод Не поддерживается
SIGKMESS Сообщение ядра Ядро
SIGKSIG Ожидание сигнала ядра Ядро
SIGKSTOP Завершение работы ядра Ядро
В традиционной UNIХ-подобной системе сигналы генерируются либо ядром, либо
при помощи системного вызова ki l l . В MINIX 3 некоторые процессы, функ­
ционирующие в пользовательском пространстве, выполняют действия, которые
в других операционных системах делало бы ядро. В табл. 4.4 показаны все сигна­
лы, известные MINIX 3, и их источники. Сигналы s i gi n t , s i gqu i t и s i gki l l
можно инициировать нажатием специальных сочетаний клавиш. Сигнал s i ga larm
управляется менеджером процессов, а s i gp i p e генерируется файловой систе­
мой. Для того чтобы послать любой сигнал любому процессу, можно воспользо­
ваться вызовом ki l l. Некоторые сигналы ядра зависят от аппаратной поддерж­
ки. Например, процессоры 8086 и 8088 не умеют обнаруживать недопустимые
инструкции, а процессоры 286 и выше при попытке выполнить такую инструкцию
вызывают прерывание. Это обусловлено аппаратными возможностями. Чтобы
в ответ на прерывание генерировать сигнал, разработчики операционной систе­
мы должны заготовить для этого код. В главе 2 мы видели, что в файле kerne l /
except i on . с как раз содержится нужный код для разных условий. Таким обра­
зом, когда M INIX 3 работает на машине с процессором 286 или выше, недо­
пустимая инструкция приведет к появлению сигнала s i gki l l , но на компьютере
с процессором 8088 такого никогда не произойдет.
То, что оборудование способно генерировать аппаратное прерывание при возник­
новении некоторой ситуации, не означает, что разработчики операционной сис­
темы могут во всем понадеяться на эту возможность. Так, все процессоры Intel,
начиная с 286, распознают несколько типов нарушений целостности памяти,
488 Глава 4. Уп равление памятью

вызывающих исключения. Код в файле kerne l / excep t i on . с преобразует эти


исключения в сигналы s i gs egv. Нарушениям аппаратных границ стека и границ
других сегментов соответствуют разные типы исключений в расчете на различную
их обработку. Тем не менее, в силу особенностей работы MINIX 3 с памятью, не
все возникающие нарушения могут быть обнаружены. Аппаратные регистры за­
дают базовый адрес сегмента и его длину. В MINIX 3 базовый адрес аппаратного
сегмента стека совпадает с базовым адресом аппаратного сегмента данных, но
аппаратно заданный размер сегмента данных превышает контролируемую про­
граммно границу. Другими словами, контролируемый аппаратно размер сегмен­
та данных соответствует ситуации, когда стек уменьшился до нуля. Аналогично,
заданный аппаратно объем стека соответствует нулевому объему данных. Таким
образом, хотя некоторые из нарушений и поддаются аппаратному обнаружению,
наиболее вероятная ошибка - повреждение области данных из-за переполнения
стека - не выявляется. Это следствие того, что с точки зрения аппаратных реги­
стров и таблиц дескрипторов сегменты данных и стека перекрываются.
Имеется потенциальная возможность добавить в ядро код, который бы прове­
рял регистры процесса всякий раз после того, как процесс получает управление,
и в случае нарушения программно заданных границ сегментов данных или стека
генерировал бы сигнал s igsegv. Но не совсем понятно, стоит ли это делать, ведь
аппаратные ловушки в силах обнаружить сбой по доступу сразу же после того,
как он произошел. Программная же проверка может случиться спустя много ты­
сяч инструкций после момента переполнения, когда обработчик прерывания уже
мало на что годится и восстановление неосуществимо.
Где бы ни брали свое начало сигналы, менеджер процессов обрабатывает их оди­
наково. Сначала для каждого процесса-получателя делается набор проверок с це­
лью убедиться, можно ли передать ему сигнал. Один процесс может передавать
другому сигнал в том случае, если первый принадлежит суперпользователю ли­
бо если его эффективный идентификатор пользователя ( U I D ) равен реальному
или эффективному идентификатору второго. Но существует еще несколько усло­
вий, вмешивающихся в отправку сигнала. Например, нельзя передавать сигнал
процессу, находящемуся в состоянии зомби. Также процессу нельзя передавать
сигнал, если он явно сделал вызов s i gac t i on, чтобы игнорировать возможный
сигнал, или вызов s i gpro crna s k, чтобы его блокировать. Блокировка сигнала
и пренебрежение им - разные вещи. Заблокированный сигнал запоминается и пе­
редается процессу, когда тот снимает блокировку (если он ее вообще снимает).
Наконец, если у процесса-получателя недостаточно места в стеке, этот процесс
принудительно завершается.
Когда все тесты пройдены, сигнал можно отправлять. Если процесс не сделал
ничего, чтобы обработать сигнал, то и никакой информации передавать ему не
требуется. В таком случае менеджер процессов выполняет обработку сигнала,
предлагаемую по умолчанию; обычно это означает либо завершение процесса,
либо сброс дампа памяти в файл. Несколько сигналов по умолчанию игнориру­
ются. Сигналы, которые в табл. 4.4 обозначены как неподдерживаемые, рекомен­
дованы стандартом POSIX, но в MINIX 3 игнорируются.
4 .7. Знакомство с менеджером п роцессов в M I N IX 3 489

Обработка сигнала процессом заключается в исполнении заданного пользователем


кода обработчика, адрес обработчика сохранен в структуре s igac t i on в таблице
процессов. В главе 2 мы видели, как в кадр стека в пределах таблицы процессов
записывается информация, необходимая для восстановления процесса после его
прерывания. Путем модификации кадра стека процесса-получателя сигнала можно
добиться того, чтобы в следующий раз, когда процесс получит управление, он на­
чал исполнять код обработчика сигнала. Посредством манипуляций с собственным
стеком процесса в пользовательском пространстве делается так, чтобы по заверше­
нии обработчика произошел системный вызов s igreturn. Явно эта подпрограм­
ма из пользовательского кода никогда не вызывается. Вызов исполняется за счет
того, что ядро помещает его адрес в стек, то есть здесь это - адрес возврата, на ко­
торый осуществляется переход после завершения обработчика. Вызов s igreturn
восстанавливает исходный кадр стека получившего сигнал процесса, чтобы тот
мог продолжить свое выполнение с момента, на котором его застал сигнал.
Хотя финальная стадия отправки сигнала происходит в системном задании, пора
подытожить все то, что мы уже узнали. Когда сигнал должен быть обработан, не­
обходимо действие, во многом сходное с обычным переключением контекстов,
которое происходит, когда один процесс переводится в ожидание, а вместо него
начинает выполняться другой. Но в таблице процессов есть только одно место,
где можно сохранить все регистры процессора, необходимые для восстановле­
ния исходного состояния процесса. Достаточно ли этого? Для ответа на вопрос
посмотрим на рис. 4.33, а. Здесь изображен в упрощенном виде стек процесса
и часть его записи в таблице процессов в тот момент, когда процесс только что
был приостановлен по факту прерывания. На время бездействия содержимое всех
регистров копируется в запись s t a c k f rame этого процесса в части таблицы
процессов, принадлежащей ядру. Данная ситуация соответствует времени гене­
рации сигнала. Источник и приемник сигнала - разные процессы, поэтому при­
емник при этом не может выполняться.
В ходе подготовки к обработке сигнала содержимое кадра стека копируется из
таблицы процессов в собственный стек процесса в виде структуры s i gcontext,
тем самым сохраняется информация о состоянии. Затем в стек помещается
структура s i g f rame , содержащая информацию, которая потребуется вызову
s i greturn после завершения обработчика. Кроме того, в ней хранится и адрес
самой библиотечной подпрограммы s i gre turn - r et addr l , и еще один адрес
возврата, ret addr 2 , то есть адрес, с которого необходимо продолжить выполне­
ние прерванной программы. Как мы увидим в дальнейшем, при нормальной ра­
боте второй адрес использоваться не должен.
Хотя обработчик и пишется программистом как обычная функция, он не вызы­
вается инструкцией c a l l . Чтобы началось исполнение кода обработчика, изме­
няется значение поля счетчика команд в кадре стека в таблице процессов, в ре­
зультате, когда re s t ar t переводит процесс в состояние исполнения, начинает
выполняться обработчик. На рис. 4.33, б показана ситуация, когда все эти приго­
товления завершены и обработчик уже занимается своим делом. Ну, а поскольку
обработчик все-таки является обычной процедурой, когда он завершает работу,
из стека извлекается адрес r e t addr l и запускается s i greturn.
490 Глава 4. Уп равление памятью

Адрес Адрес Адрес Адрес


возврата возврата возврата возврата
Локальные Локальные Локальные Локальные
переменные переменные переменные переменные
процесса процесса процесса процесса
Фрейм стека Фрейм стека
(регистры (регистры
процессора) процессора)
(исходные) (исходные) Стек
Адрес Адрес
возврата 2
- - - - - - - - -
возврата 2
- - - - - - - - -

Структура Локальные
sigframe переменные
- - - - - - - - -

Адрес sigreturп
возврата 1
Локальные
переменные
обработчика

}т·�-
Фрейм стека Фрейм стека Фрейм стека Фрейм стека
(регистры (регистры (регистры (регистры
процессора) процессора) процессора) процессора) процессов
(исходные) (изменены, (изменены, (исходные)
ip = обработчик) ip = обработчик)
Исходное Во время работы Выполняется Возврат в исходное
состояние обработчика sigгetuгп состояние
а б в г

Рис . 4.33.Стек процесса (сверху) и кадр стека (снизу), соответствующие различным фазам
обработки сигнала: а состояние, в котором процесс приостанавливается; б состояние
- -

на начало исполнения обработчика; в состояние во время исполнения вызова sigreturп;


-

г состояние после завершения вызова sigreturп


-

Ситуация, когда исполняется s i greturn, показана на рисунке 4.33, в. Оставшая­


ся часть структуры s i g f rarne используется как локальные переменные вызова
s igreturn. Одно из действий этого системного вызова направлено на такое из­
менение собственного указателя стека, когда при обычном возврате осуществля­
ется переход на адрес ret addr 2 . Но в действительности вызов s i gre turn не
завершается как вызов обычной функции. Он, как и другие системные вызовы,
передает право решать, какой процесс станет активным, планировщику в ядре.
В конечном итоге, в какой-то момент времени получивший сигнал процесс вы­
бирается для выполнения. Выполнение продолжается с этого адреса, продуб­
лированного в оригинальном кадре стека процесса. Данный адрес помещается
в стек для того, чтобы отладчик, проходя по программе, не испытывал проблем
со стеком при трассировке обработчика сигнала. Благодаря таким манипуляциям
4. 7. Знакомство с менеджером п роцессов в M I N IX 3 49 1

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

стояние, то есть к моменту получения сигнала. Важнее всего восстановить кадр


стека процесса, для чего и используется его копия, сохраненная в собственном
стеке процесса. В результате после завершения s igreturn процесс возвращает­
ся в исходное состояние (рис. 4.33, z ).
По умолчанию большинство сигналов приводят к завершению процесса. Забо­
тится об этом менеджер процессов, завершающий процесс, когда сигнал не игно­
рируется по умолчанию или процесс-приемник не требует его блокировать, об­
рабатывать или игнорировать. Если завершения процесса ожидает его родитель,
процесс завершается , и соответствующая информация удаляется из таблицы
процессов. Если родитель пренебрегает своим долгом, процесс превращается в зом­
би. Кроме того, для некоторых типов сигналов (например, s i gqu i t ) менеджер
процессов записывает в текущий каталог дамп памяти процесса.
Легко может случиться так, что получивший сигнал процесс находится в состоя­
нии блокировки, скажем, читает с терминала при помощи вызова read, когда на
терминал не поступает данных. Если процесс не указал, что сигнал должен обра­
батываться, он просто завершается обычным образом. Но если сигнал обрабаты­
вается, закономерен вопрос: что должен делать процесс после того, как обработано
прерывание сигнала. Должен ли он вернуться в состояние ожидания или про­
должить работу со следующей инструкции?
В MINIX 3 делается следующее: системный вызов завершается с кодом возвра­
та E I NTR, поэтому процесс может понять, что он был прерван сигналом. Узнать
о том, что процесс заблокирован на системном вызове, не так просто. Менеджеру
процессов придется спрашивать об этом файловую систему.
Такое поведение предполагается стандартом POSIX, который позволяет вызову
read возвращать байты, считанные до прихода сигнала, хотя и не жестко пред­
писано. Кроме того, возврат с кодом E I NTR позволяет легко реализовать таймер
и обработать сигнал s iga l arm, например, чтобы завершить операцию l ogin и по­
весить трубку модема, если пользователь некоторое время (тайм-аут) не отвечает.

Таймеры пользовательско го пространства


Генерация оповещения для активизации процесса через заданный интервал време­
ни - одно из наиболее распространенных применений сигналов. В традиционных
операционных системах работа с оповещениями полностью выполняется ядром
или драйвером часов в пространстве ядра. В операционной системе MINIX 3 ответ­
ственность за оповещения пользовательских процессов возлагается на менеджер
процессов. Это сделано для того, чтобы снизить нагрузку на ядро и упростить
код, выполняемый в пространстве ядра. Если заданному объему кода соответст­
вует определенное число неизбежных ошибок, то логично сделать вывод о том,
что с уменьшением ядра число ошибок также сокращается. Однако даже если ко­
личество ошибок останется неизменным, их последствия окажутся менее серьез­
ными, если они будут происходить в пользовательском пространстве, а не в ядре.
492 Глава 4. Уп равление памятью

Можно ли обеспечить обработку оповещений вообще без участия ядра? Разуме­


ется, в MINIX 3 ответ на этот вопрос отрицательный. В первую очередь, обработ­
кой оповещений занимается таймерное задание, которое выполняется в про­
странстве ядра. Оно управляет связанным списком, или очередью, таймеров, как
показано на рис. 2.27. При каждом прерывании от микросхемы часов время исте­
чения таймера, находящегося в начале очереди, сравнивается с текущим време­
нем, и если таймер истек, таймерное задание входит в главный цикл и посылает
оповещение запросившему его процессу.
В MINIX 3 нововведение заключается в том, что в пространстве ядра поддержива­
ются лишь таймеры системных процессов. Менеджер процессов управляет другой
очередью таймеров, которые запущены пользовательскими процессами, запросив­
шими оповещения. Менеджер процессов запрашивает оповещение от часов только
для таймера, находящегося в начале своей очереди. Если очередь пуста, то и необ­
ходимости обращаться к часам с запросом нет (разумеется, на самом деле запрос
посылается через системное задание, поскольку таймерное задание не взаимодей­
ствует с какими-либо процессами напрямую). Когда после прерывания от часов
определяется истечение таймера, менеджеру процессов передается уведомление.
Менеджер процессов проверяет собственную очередь таймеров, посылает сигнал
пользовательскому процессу и подает новый запрос, если его очередь не пуста.
В изложенных идеях не видно значительного снижения нагрузки на ядро, здесь
имеет место ряд других соображений. Во-первых, существует вероятность исте­
чения нескольких таймеров в одном такте часов. То, что два процесса могут за­
требовать оповещения одновременно, кажется неправдоподобным, однако не все
так просто. Хотя проверка таймеров на истечение выполняется при каждом пре­
рывании от микросхемы часов, мы знаем, что иногда прерывания запрещены.
Обращение к BIOS может привести к пропуску столь большого числа прерыва­
ний, что предусмотрено средство «наверстывания• , которое «переводит• время,
отсчитываемое таймерным заданием, на несколько тактов вперед. Следователь­
но, возможна ситуация, в которой необходимо обработать сразу несколько ис­
текших таймеров. Менеджер процессов берет эту задачу на себя, освобождая яд­
ро от поиска и очистки собственной очереди, а также от генерации множества
уведомлений.
Во-вторых, оповещения могут быть отменены. Не исключено, что пользователь­
ский процесс завершится до истечения установленного им таймера, или таймер
будет использоваться для предотвращения бесконечного ожидания события, кото­
рое может никогда не произойти. Если событие происходит, оповещение можно
отменить. Очевидно, нагрузка на код ядра снижается, если отмена выполняется
в очереди менеджера процессов, находящегося за пределами ядра. Очередь ядра
требует внимания лишь при истечении таймера, находящегося в ее начале, а также
при изменении процесса, находящегося в начале очереди менеджера процессов.
Реализацию таймеров проще понять, кратко рассмотрев функции, обрабатываю­
щие оповещения. В обработке принимает участие множество функций менедже­
ра процессов и ядра, поэтому их последовательное и подробное изучение затруд­
нит восприятие общей концепции.
4. 7 . Знакомство с менеджером п роцессов в M I NIX 3 493

Когда менеджер процессов устанавливает оповещение от имени пользователь­


ского процесса, функция s e t_a l arm инициализирует таймер. Структура тайме­
ра содержит поля времени истечения, процесса, от имени которого установлено
оповещение, и указателя на исполняемую функцию. Для оповещений всегда вы­
зывается функция c aus e_s igal arm. Далее происходит обращение к системному
заданию для установки оповещения в пространстве ядра. При истечении тайме­
ра вызывается сторожевая функция c au s e_a l arm, которая посылает уведомле­
ние менеджеру процессов. В этом участвуют различные функции и макросы, од­
нако в конечном счете уведомление принимает функция ge t_work менеджера
процессов. Главный цикл менеджера процессов определяет, что уведомление
имеет тип SYN_ALARМ, и вызывает функцию pm_exp i r e_t ime r s . После этого
используется несколько функций, расположенных в пространстве менеджера про­
цессов. Библиотечная функция tmrs_expt imers вызывает сторожевую функцию
c au s e_s i g a l arm, та, в свою очередь, - функцию che c k s i g, а che c k s i g
-

функцию s i g_pr o c . В s i g_proc принимается решение о том, следует ли унич­


тожить процесс или послать ему сигнал S I GALRМ. Для передачи сигнала нужно
обратиться за помощью к системному заданию, выполняющемуся в пространст­
ве ядра, поскольку требуется манипуляция с данными таблицы процессов и сте­
ка адресата сигнала (см. рис. 4.33).

4 . 7 . 8 . П рочие систем н ы е вызовы


Менеджер процессов отвечает еще за несколько простых системных вызовов. Вызо­
вы t ime и s t ime предназначены для работы с часами реального времени. Вызов
t ime s получает учетное время процесса. Все эти вызовы обрабатываются менед­
жером процессов в основном потому, что их удобно направлять ему. Еще один
системный вызов, и t ime, рассматривается в главе 5, посвященной файловой
системе, поскольку он хранит время модификации файлов в индексных узлах.
Две библиотечные функции, ge t u i d и ge t e u i d, пользуются одним и тем же
системным вызовом ge t u i d, который в ответном сообщении возвращает оба
значения. Аналогично, системный вызов ge t g i d возвращает как реальное, так
и действующее значения идентификаторов, нужные соответственно функциям
getgid и ge tegid. Подобно работает и вызов getp id, возвращающий идентифи­
катор самого процесса и его родителя, а при помощи вызовов s e t u i d и s e t g i d
можно устанавливать как реальное, так и эффективное значения сразу, одним
вызовом. В этой группе есть два дополнительных системных вызова, getpgrp
и s e t s i d. Первый возвращает идентификатор группы процессов, а второй уста­
навливает его равным текущему идентификатору процесса ( P I D ) . Эти семь
функций - самые простые системные вызовы в MINIX 3.
Вызовы pt race и reboot также выполняются в менеджере процессов. Первый
из них помогает отлаживать программы. Второй оказывает влияние на многие
аспекты системы. В первую очередь этот вызов отправляет сигналы, чтобы за­
вершить все процессы, кроме i ni t , поэтому его код помещен именно в менед­
жер процессов. Чтобы завершить работу после того, как отправлены сигналы,
в процесс выполнения вовлекаются файловая система и системное задание.
494 Глава 4 . Уп равление п амят ью

4 . 8 . Уп равление пам я ть ю в M I N IX
В общих чертах разобравшись в том, как работает менеджер процессов, давайте
обратимся к самому коду. Менеджер процессов полностью написан на языке С,
его код прост и снабжен множеством комментариев, поэтому мы, как правило, не
будем слишком глубоко вдаваться в детали. Сначала мы изучим заголовочные
файлы, затем - главную программу, а потом - файлы с кодом описанных ранее
системных вызовов.

4 . 8 . 1 . Заголовоч н ы е файл ы и структуры дан н ых


В каталоге исходных файлов менеджера процессов есть несколько заголовочных
файлов, имена которых совпадают с именами файлов ядра. Эти же имена мы
встретим еще раз при изучении файловой системы. Такие файлы имеют 4одно­
именные� функции (в своем контексте). Параллельная структура была выбрана
для того, чтобы упростить понимание устройства MINIX 3 в целом. Помимо озна­
ченных, к менеджеру памяти относятся несколько заголовочных файлов с уникаль­
ными именами. Как и в других частях системы, место для хранения глобальных
переменных выделяется в файле t ab l e . с. Его рассмотрением, а также рассмот­
рением сопутствующих заголовочных файлов мы займемся в этом разделе.
Как и у всех остальных основополагающих компонентов MINIX 3, у менеджера
процессов есть свой главный заголовочный файл, prn . h (строка 17000). Он вклю­
чается в каждый файл с кодом и, в свою очередь, включает в себя все общесис­
темные заголовочные файлы из каталога / u s r / i n c lude и его подкаталогов,
нужные каждому объектному модулю. С ним присоединяется большая часть фай­
лов, включаемых в ke rne l / ke rnr l . h, а также собственные версии файлов
c on s t . h, type . h, p r o t o . h и g l o . h. Кроме того, менеджеру процессов необ­
ходимы некоторые определения из файлов inc l ude / f cn t l . h и inc l ude /
uni s t d . h. Аналогичную структуру мы наблюдали при изучении ядра.
Файл c o ns t . h (строка 1 7 1 00 ) задает ряд констант, необходимых менеджеру
процессов.
Файл type . h в текущей версии не используется и содержит только каркас, что­
бы структура менеджера процессов была такой же, как и прочих частей системы.
Файл pro t o . h (строка 1 7300) предназначен для того, чтобы в одном месте со­
брать прототипы всех необходимых менеджеру процессов функций. В строках
17313 и 173 14 содержатся определения пустых функций, необходимые при компи­
ляции MINIX 3 с использованием подкачки. Данные макросы упрощают компи­
ляцию без подкачки, поскольку в противном случае ненужные вызовы пришлось
бы удалять из многих файлов исходного кода.
Глобальные переменные менеджера процессов декларируются в файле g l o . h
(строка 17500). Здесь применен тот же самый трюк с макросом EXTERN, что и в яд­
ре. А именно, макрос EXTERN разворачивается в ключевое слово ext ern во всех
файлах, за исключением t аЫ е . с, где он порождает пустую строку. В результате
в файле t abl e . с резервируется память для хранения глобальных переменных.
4. 8 . Уп равление памятью в M I NIX 495

Первая из глобальных переменных, rnp, является указателем на структуру rnproc.


Эта структура описывает ту часть таблицы процессов, которая принадлежит ме­
неджеру процессов, а переменная rnp ссылается на процесс, системный вызов ко­
торого обрабатывается в текущий момент. Вторая переменная, proc s_in_u s e ,
служит для подсчета имеющихся процессов, эта цифра помогает выяснить, вы­
полним ли вызов f ork.
Буфер сообщений rn_i n предназначен для запросов. Переменная who содержит
индекс текущего процесса. Она связана с переменой rnp следующим соотношением:
mp = &mproc [ who ] ;

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


ется в переменную c a l l_nr.
Если процесс завершается аварийно, M INIX 3 записывает его образ в файл дам­
па. Переменная core_narne содержит имя этого файла, cor e_s s e t - это бито­
вая карта, определяющая сигналы, которые должны генерировать дампы, а i gn_
s s e t - битовая карта сигналов, которые следует игнорировать. Обратите вни­
мание на то, что переменная c ore_narne и массив c a l l_vec определены с клю­
чевым словом ext e rn, а не EXTERN. Причину этому мы объясним при рассмот­
рении файла t аЫ е . с .
Т а часть таблицы процессов, которой заведует менеджер процессов, описана
в следующем файле, rnp roc . h (строка 1 7600). По большей части, назначение по­
лей этой таблицы объясняется комментариями. Несколько полей связаны с об­
работкой сигналов. Поля rnp_i gnore, rnp_ca t ch, rnp_s i g2rne s s , rnp_s i grnask,
rnp_s i grnask 2 и rnp_s i gpendi ng представляют собой битовые карты, в кото­
рых каждый бит означает один из сигналов, разрешенный к отправке процессу.
Эти поля имеют тип s i gs e t_t , являющийся 32 -разрядным целым, следователь­
но, MINIX 3 может поддерживать до 32 сигналов. Сейчас определено только
22 сигнала, а отсутствующие определения допускаются стандартом POSIX. Сиг­
налу номер 1 соответствует наименее значимый (самый правый) бит карты.
Впрочем, согласно P O S I X , требуются специальные функции для добавления
и удаления сигналов в эти наборы, поэтому при манипуляциях с битовыми кар­
тами программисту не нужно вдаваться в такие детали. А вот массив rnp_s i gact
важен для обработки сигналов. В нем имеется по одной структуре типа s i gac t i on
(файл i nc lude / s i gnal . h ) на каждый возможный тип сигнала. Эта структура
составлена из трех полей:
+ s a_handl e r - признак, который определяет, будет ли для сигнала выполне­
но действие по умолчанию, специальное действие по обработке или же сиг­
нал должен игнорироваться;
- битовая карта, в которой отмечается, какие сигналы были забло-
+ s a_rna s k
кированы во время выполнения пользовательского обработчика;
+ s a_ f l ags - набор флагов сигнала.
Благодаря массиву rnp_s i ga c t обеспечивается большая гибкость при обра­
ботке сигналов.
496 Глава 4. Уп равление п ам ят ью

Поле mp_f l ags, как показано в конце файла, необходимо для хранения разно�
образных битовых сочетаний. В нем хранится беззнаковое целое, 16-разрядное
для самых старых процессоров и 32-разрядное для процессоров 386 и выше.
Последнее поле таблицы процессов называется mp_procargs. Когда запускаетоя
новый процесс, строится стек, подобный показанному на рис. 4.32, и указатель
на массив argv нового процесса сохраняется в этой переменной. Например, на
рис. 4.32 в поле будет сохранено значение 8 1 64, благодаря чему, пока выполняется
команда l s , команда p s может вывести содержимое командной строки:
ls -l f . c g . c

Благодаря данному массиву обеспечивается значительная гибкость при обработ­


ке сигналов.
Поле mp_swapq не используется в рассматриваемой версии MINIX. Оно приме­
няется при включенной подкачке и указывает на очередь ожидающих подкачки
процессов. Поле mp_re p l y предназначено для ответного сообщения. В более
ранних версиях MINIX такое поле было всего одно, определялось оно в файле
g l o . h и компилировалось вместе с t аЫ е . с. В MINIX 3 память под ответные
сообщения предоставляется каждому процессу. Это позволяет менеджеру про­
цессов приступать к обработке следующего входящего сообщения, если ответ Ra
предыдущее сообщение не может быть отправлен сразу по окончании его генера­
ции. Менеджер процессов не способен обрабатывать два запроса одновременно,
но при необходимости может откладывать отправку ответов, пытаясь послать все
активные ответы каждый раз по завершении обработки запроса.
Последние два элемента таблицы процессов можно считать украшательствами.
Поле mp_nice содержит значение, позволяющее пользователям снизить приори­
тет процесса, например, для того, чтобы другой, более важный, процесс мог вы­
теснить его. Тем не менее MINIX 3 использует это поле для собственных нужд,
назначая различные приоритеты различным системным процессам (драйверам
и серверам) в зависимости от их потребностей. Поле mp_name удобно для отлад­
ки, помогая программисту найти запись таблицы процессов в дампе памяти.
Имеется системный вызов, выполняющий поиск в таблице процессов по задан­
ному имени и возвращающий соответствующий идентификатор.
Обратите внимание на то, что часть таблицы процессов, принадлежащая ме­
неджеру процессов, объявлена как массив размером NR_PROC S (строка 1 7655 ).
Вспомните, что часть, принадлежащая ядру, имеет размер NR_TASKS + NR_
PROCS и объявлена в файле ke rne l / p r o c . h (строка 5593). Как было отмечено
ранее, процессы, являющиеся частью ядра, невидимы для компонентов операци­
онной системы, находящихся в пользовательском пространстве, например, менед­
жеру процессов. На самом деле, эти процессы не играют ключевой роли.
Следующий файл, p a r am . h (строка 1 7700), содержит макросы, помогающие
формировать сообщения для многих системных вызовов. Кроме того, здесь же
описаны четыре макроса для формирования ответных сообщений. В любом фай­
ле, куда включен файл param . h, может быть указано такое выражение:
k = m_in . p i d ;
4 . 8 . Уп равление памятью в M I N IX 497

В этом случае перед компиляцией препроцессор преобразует его следующим об­


разом (строка 1 7707) :
k = m_in . ml_i l ;

Перед тем как продолжить, давайте обратимся к файлу t аЫ е . с (строка 17800).


При его компиляции получается объектный файл, в котором выделяется место
для хранения глобальных переменных и структур, объявленных с директивой
EXTERN. Такие конструкции мы видели в файлах g l o . h и mproc . h. Благодаря
тому, что в этом файле присутствует следующее выражение, макрос EXTERN раз­
ворачивается в пустую строку:
Jl d e f ine _TABLE

Это - тот же самый механизм, который мы могли наблюдать в коде ядра. Как
уже было отмечено, переменная core_name объявлена в файле g l o . h с ключе­
вым словом ext e rn, а не EXTERN. Теперь становится ясно, почему: core_name
объявляется со строкой инициализации, а инициализация в определении ext ern
невозможна.
Еще один важный элемент файла t аЫ е . с - массив c a l l_ve c (строка 17815).
О н также инициализирован, а потому в g l o . h его нельзя было бы объявлять
с ключевым словом EXTERN. С его помощью номер системного вызова преобра­
зуется в адрес выполняющей его функции, номер вызова играет роль индекса
в массиве. Если в сообщении указан несуществующий номер системного вызова,
управление передается функции no_sy s , которая просто возвращает код ошиб­
ки. Несмотря на то что при объявлении массива c a l l_vec применяется макрос
_PROTOTYPE, в действительности это - описание не прототипа, а инициализи­
рованного массива. Но так как он же массив указателей на функции, проще всего
получить код, компилируемый как классическим (Керниган и Ричи), так и стан­
дартным компилятором С при помощи макроса _PROTOTYPE.
И наконец, последнее замечание о заголовочных файлах. Поскольку MINIX 3 до
сих пор находится в стадии активной разработки, некоторые <1:острые углы� до
сих пор не <1:сглажены�. Один из них состоит в том, что некоторые исходные файлы
в каталоге pm / включают заголовочные файлы из каталога ядра. Если не знать
об этом, можно столкнуться с проблемами при поиске некоторых важных опреде­
лений. Вероятно, определения, используемые более чем одним из основных компо­
нентов MINIX 3, следует разместить в заголовочных файлах каталога inc lude / .

4 . 8 . 2 . Главная п рограмма
Менеджер процессов компилируется и компонуется независимо от ядра и фай­
ловой системы. Следовательно, у него имеется собственная главная процедура,
которая запускается после того, как ядро заканчивает свою инициализацию. Она
расположена в файле ma i n . с (строка 1 804 1 ) . В ней менеджер процессов сначала
выполняет собственную инициализацию (функция pm_i n i t ) , а затем входит
в цикл (строка 1 805 1 ) . В этом цикле сначала, чтобы дождаться входящего сооб­
щения, делается вызов get_work. Затем вызывается одна из функций do_XXX,
498 Глава 4. Уп равление памятью

адрес которой берется из массива c a l l_ve c , а далее, при необходимости, от­


правляется ответное сообщение. Такая конструкция должна быть вам уже знако­
ма, по тому же принципу работают задания ввода-вывода.
Данное описание несколько упрощено. Как было отмечено в главе 2, уведом ­
ления могут быть посланы любому процессу. Уведомления идентифицируются
особыми значениями в поле c a l l_nr. В строках 1 8055 - 1 8062 выполняется про­
верка для двух типов уведомлений , которые может принимать менеджер про­
цессов, и для каждого из них выполняется специальное действие. Кроме того,
в строке 1 8064 проверяется корректность значения c a l l_nr, а затем делается
попытка обслужить запрос (строка 1 8067). Хотя вероятность некорректного за­
проса невелика, проверить его несложно, в то время как последствия некоррект­
ности будут весьма серьезными.
Еще один заслуживающий внимания момент - функция swap_in (строка 18073).
Рассматривая файл pro t o . h, мы отметили, что в описываемой версии MINIX
эта функция пустая. Тем не менее в полноценной версии функция swap_ i n
проверяет, возможна л и подкачка для процесса.
Комментарий в строке 1 8070 говорит о том, что здесь выполняется отправка от­
вета, однако этот комментарий несколько упрощен. Вызов s e t rep ly формирует
ответное сообщение в записи таблицы процессов текущего процесса. В цикле
выполняется проверка всех записей таблицы процессов и отправка всех ожидаю­
щих ответов, которые можно отправить (строки 1 8078- 1 809 1 ) . Ответы, которые
не могут быть посланы, пропускаются.
Соответственно, две следующие функции, get_work (строка 1 8099) и s e t rep ly
(строка 1 8 1 16), отвечают за реальные прием и отправку сообщений соответст­
венно. С помощью особого трюка функция ge t_work делает так, что сообщение,
посылаемое ядром, выглядит как отправленное менеджером процессов, посколь­
ку у ядра нет собственной записи в таблице процессов. Функция s e t reply фак­
тически не отправляет ответ, а, как уже отмечалось, откладывает его для отправ­
ки в будущем.

И нициализация м енеджера процессов


Самая длинная процедура в ma i n . с - pm_ini t, она инициализирует менеджер
процессов. После того как система заработала, эта процедура больше не нужна.
Хотя драйверы и серверы компилируются раздельно и выполняются как незави­
симые процессы, некоторые из них помещаются в загрузочный образ монитором
загрузки. Весьма затруднительно представить себе запуск операционной системы
без менеджера процессов и файловой системы, поэтому эти компоненты, вероят­
но, всегда будут загружаться в память монитором. В загрузочный образ также
входят некоторые драйверы устройств. Хотя в MINIX 3 поставлена цель обеспе­
чить независимость загрузки как можно большего числа драйверов, без драйвера
диска едва ли удастся обойтись уже на ранней стадии работы системы.
Основное предназначение pm_ini t заключается в инициализации таблиц ме­
неджера процессов с целью обеспечить возможность работы процессов, предва­
рительно загруженных в память. Как было отмечено ранее, менеджер процессов
4. 8 . Уп равле н ие памятью в MI N IX 499

поддерживает две важные структуры данных таблицу свободных участков (или


-

таблицу свободной памяти) и часть таблицы процессов. Сначала мы рассмотрим


таблицу свободных участков. Инициализация памяти - сложная задача. Приво­
димое далее описание проще понять, сначала ознакомившись с организацией па­
мяти на момент активации менеджера процессов. MINIX 3 предоставляет для
этого всю необходимую информацию.
Перед тем как сам загрузочный образ MINIX 3 попадает в память, монитор за­
грузки определяет структуру доступной памяти. Находясь в загрузочном меню
и нажав клавишу Esc, вы получите доступ к параметрам загрузки. Одна из
строк на экране показывает блоки неиспользуемой памяти и выглядит сле­
дующим образом:
memory = 8 0 0 : 9 2 3 e0 , 1 0 0 0 0 0 : 3df0 0 0 0

После запуска MINIX 3 вы можете получить эту информацию при помощи ко­
манды sys env или клавиши F5 . Разумеется, числа, которые вы увидите, могут
отличаться от приведенных в этом примере.
Здесь мы видим два блока свободной памяти. Кроме того, два блока памяти
заняты: ниже адреса Ох800 находятся данные BIOS, главная загрузочная запись
и загрузочный блок. Не имеет значения, как используется эта память, важно, что
на момент запуска монитора загрузки она недоступна. Память, начинающаяся
с адреса Ох800, является •базовой памятью� для I В М-совместимых компьюте­
ров. В данном примере свободный блок с началом по адресу Ох800 (2048) имеет
размер Ох923е0 (599008) байт. Далее следует блок • верхней области памяти�
с 640 Кбайт до 1 Мбайт, который недоступен обычным программам. Он зарезер­
вирован под память для чтения и выделенную память адаптеров ввода-вывода.
Начиная с адреса Ох 1 00000 ( 1 Мбайт), свободно Ox3df0000 байт. Как правило,
этот участок называют расширенной памятью. В показанном примере компью­
тер оснащен в общей сложности 64 Мбайт оперативной памяти.
Если вы внимательно следили за числами, то заметили, что сумма свободной
базовой памяти составляет менее 638 Кбайт, как •должно бы быть�. Загрузочный
монитор MINIX 3 загружает себя как можно выше в данном диапазоне и в рас­
сматриваемом случае занимает около 52 Кбайт. Таким образом, свободными ока­
зываются приблизительно 584 Кбайт памяти. Следует отметить, что механизм
использования памяти может быть сложнее, чем описано здесь. Например, один
из методов запуска M INIX, к моменту написания книги еще не перенесенный
в MINIX 3, предполагает применение D ОS-файла для имитации диска MINIX.
Этот метод требует загрузки некоторых компонентов M S - D O S до запуска мо­
нитора загрузки MINIX 3. Если компоненты окажутся загруженными в области
памяти, несмежные с уже занятыми, число областей свободной памяти будет
больше двух.
Когда монитор загрузки помещает загрузочный образ в память, информация о его
компонентах отображается на экране консоли. Часть экрана показана на рис. 4.34.
В этом примере (типичном, но, вероятно, отличающемся от вашего экрана, по­
скольку для его создания использовалась рабочая редакция MINIX 3) монитор
загрузки поместил ядро в свободную область памяти, начиная с адреса Ох800.
500 Глава 4. Уп равление памятью

Менеджер процессов, файловая система, сервер реинкарнации и прочие компо­


ненты, не показанные в листинге, устанавливаются в блок памяти, начинающий,
ся по адресу 1 Мбайт. Это было не единственным возможным решением - ниже
588 Кбайт нашлось бы достаточно места для некоторых из этих компонентов.
Тем не менее, когда MINIX 3 компилируется с кэшем блоков большого объема
(как в данном примере), файловая система не умещается в пространство, нахо­
дящееся над ядром. Загрузить все в верхнюю область памяти было бы проще, но
совсем не обязательно. Такой метод не приводит к каким-либо потерям, менед­
жер памяти способен использовать свободную память ниже 588 Кбайт уже после
того, как система запущена и начали работу пользовательские процессы.

cs ds text data bss stack


0000800 0005800 1 9552 3 1 40 30076 о kerпel
0 1 00000 0 1 04с00 1 9456 2356 486 1 2 1 024 pm
0 1 1 1 800 0 1 1 с400 432 1 6 591 2 6224364 2048 fs
070е000 070f400 4352 616 4696 1 31 072 rs

Рис. 4.34. Использования памяти монитором загрузки для нескольких


первых компонентов загрузочного образа
Инициализация менеджера процессов начинается с циклического просмотра
таблицы процессов с целью отключить таймер каждой записи во избежание лож­
ных срабатываний. Затем инициализируются глобальные переменные, опреде­
ляющие наборы сигналов, по умолчанию игнорируемые или вызывающие гене­
рацию дампов. Далее обрабатывается информация об использовании памяти.
В строке 1 8 1 82 системное задание считывает строку rnernory монитора загрузки,
которую мы уже видели. В нашем примере свободная память описывается двумя
парами базовый адрес:размер. Вызов функции get_rnern_chunk s (строка 1 8 1 84 )
преобразует ASCII -строку в двоичные данные и вводит значения базового адре­
са и размера в массив rnern_chunks (строка 1 8 192), элементы которого определе­
ны следующим образом:
s t ru c t memory { phy s_c l i c k s ba s e ; phy s_c l i c k s s i z e ; }

Массив rnern_chunk s не является списком свободных участков. Это всего лишь


небольшой массив, в котором накапливается информация до инициализации
списка свободных участков.
После опроса ядра и преобразования информации об использовании ядром опе­
ративной памяти в число кликов вызывается функция pat ch_rnern_chunks для
исключения памяти ядра из массива rnern_chunks . Теперь учтена и память, ко­
торая использовалась до запуска MINIX 3, и память, занятая ядром. Массив
rnern_chunks содержит не всю информацию, однако память, требуемая обычным
процессам загрузочного образа, учитывается внутри цикла, инициализирующего
записи таблицы процессов (строки 1 820 1 - 1 8239).
Информация об атрибутах всех процессов, входящих в состав загрузочного
образа, находится в таблице irnage, объявленной в файле k e rne l / t aЫ e . с
(строки 6095 - 6 1 09 ) . Перед входом в основной цикл менеджер процессов по­
лучает копию таблицы irnage вызовом sy s_ge t i rnage ядра (строка 1 8 1 97 ) .
4 . 8 . Уп равление памятью в M I N IX 501

Строго говоря, sys_ge t irnage является не вызовом ядра, а одним из более 10 мак­
росов, определенных в файле i n c l ude / rn i n i x / sys l i b . h и обеспечивающих
простой интерфейс к вызову ядра sys_ge t i n f o . Процессы ядра невидимы из
пользовательского пространства, и части таблицы процессов, принадлежащие
менеджеру процессов и файловой системе, не нуждаются в инициализации со
стороны компонентов ядра. Фактически место под записи процессов ядра не ре­
зервируется. Каждый из них имеет отрицательный номер (индекс в таблице про­
цессов) и игнорируется при проверке в строке 1 8202. Кроме того, вызывать
функцию pat ch_rnern_chunks для процессов ядра не обязательно; при выделе­
нии памяти под ядро потребности его процессов учитываются.
Как системные, так и пользовательские процессы необходимо добавить в табли­
цу процессов, хотя работа с ними ведется по-раз ному (строки 1 82 1 0- 1 82 1 9 ) .
Единственным пользовательским процессом, включенным в состав загрузочного
образа, является ini t , поэтому в строке 1 82 1 0 выполняется проверка для INIT_
PROC_NR. Все остальные процессы, входящие в загрузочный образ, - системные.
Системные процессы являются особыми: они не могут быть вытеснены, каждый
из них обладает собственной записью в таблице p r i v ядра и набором привиле­
гий, задаваемым флагами. Для каждого процесса определены надлежащие пара­
метры обработки сигналов по умолчанию, при этом между системными про­
цессами и ini t имеются определенные различия. Далее карта памяти каждого
процесса извлекается из ядра при помощи функции get_rnern_rnap, совершаю­
щей вызов ядра sys_ge t i n f o, и pat ch_rnern_rnap , соответствующим образом
изменяющей массив rnern_chunk s (строки 1 8225 - 1 8230).
Наконец, файловой системе передается сообщение, с помощью которого каждый
процесс инициализируется в принадлежащей ей части таблицы процессов ( стро­
ки 1 8233- 1 8236). Сообщение содержит только номер и идентификатор процесса;
этого достаточно, чтобы инициализировать запись в таблице файловой системе,
поскольку все процессы загрузочного образа принадлежат суперпользователю
и могут получить одинаковые значения по умолчанию. Каждое сообщение от­
правляется при помощи операции s end, поэтому ответа на него не ожидается.
После передачи сообщения имя процесса отображается на консоли (строка 18237):
Bui l d ing pro c e s s t аЫ е : pm fs r s tty memory log driver i n i t

Здесь driver заменяет стандартный драйвер диска. В загрузочный образ можно


включить несколько драйверов диска, один из которых выбирается по умолча­
нию в соответствии со значением параметра загрузки l abe l .
Собственная запись менеджера процессов в таблице процессов представляет со­
бой особый случай. После завершения основного цикла менеджер процессов вы­
полняет некоторые изменения в своей записи и посылает итоговое сообщение
файловой системе с символьным значением NONE в качестве номера процесса.
Передача сообщения осуществляется вызовом s endrec, поэтому менеджер процес­
сов ждет ответа. Пока менеджер процессов выполняет цикл инициализирующего
кода, файловая система в строках 24 1 89-24202 выполняет цикл r e c e ive (дан­
ный код рассматривается в следующей главе). Получение сообщения со значением
502 Глава 4. Уп равле н ие памятью

NONE в качестве номера процесса указывает файловой системе на то, что все сис­
темные процессы инициализированы, можно выйти из цикла и послать синхрони­
зирующее сообщение для снятия блокирования с менеджера процессов.
После этого файловая система может продолжить собственную инициализацию,
а инициализация менеджера процессов почти завершена. В строке 1 8253 вызы­
вается функция mem_ini t. Она инициализирует связанный список свободных
областей памяти и нужные переменные, которые будут использованы для управ­
ления памятью в процессе работы системы, на основе данных массива mem_
chunks . Управление памятью в обычном режиме начинается после того, как на
консоль выводится сообщение, содержащее общий объем памяти, память, заня­
тую MINIX 3, и объем свободной памяти:
Phy s i c a l memory : t o t a l 6 3 9 9 6 КВ , sy s t em 1 2 8 3 4 КВ , f r e e 5 1 1 6 2 КВ

Следующая функция, get_n i c e_va l u e (строка 1 8263), вызывается для опреде­


ления •уровня вежливости� каждого из процессов загрузочного образа. В таблице
image содержится значение, определяющее приоритет очереди, в которую процесс
будет помещен при планировании. Это значение варьируется от О для самых вы­
сокоприоритетных процессов, таких как C LOCK, до 15 для процесса I DLE. Одна­
ко для UNIХ-подобных систем традиционным является диапазон •уровней веж­
ливости� с положительными и отрицательными значениями. По этой причине
функция get_n i c e_va l ue переводит для пользовательских процессов значения
приоритетов ядра в шкалу с нулем посередине. Масштабирование производится
с использованием констант PRI O_MIN и PRI O_МAX ( -20 и +20 соответственно),
определенных в виде макросов в файле inc l ude / sy s / r e s our c e . h ( отсутству­
ет в листинге). Они масштабируются между значениями MIN_USER_Q и МАХ_
USER_Q, определенными в файле ke rne l / p roc . h. Таким образом, при увеличе­
нии и уменьшении числа приоритетных очередей команда n i c e продолжает ра­
ботать. Процесс i n i t, корень дерева пользовательских процессов, помещается
в очередь с приоритетом 7 и получает •уровень вежливости� О, который насле­
дуется дочерними процессами после вызова f o rk.
Последние две функции файла ma in . с уже упоминались. Функция get_mem_
chunks (строка 18280) вызывается лишь однажды. Она принимает информацию,
возвращаемую монитором загрузки в виде АSСП-строки с парами базовый ад­
рес : размер, преобразует ее в клики и сохраняет в массиве mem_chunk s . Функ­
ция pat ch_mem_chunk s (строка 1 8333) продолжает формирование списка сво­
бодных участков памяти и вызывается несколько раз - один раз для ядра, один
раз для процесса ini t и по одному разу для каждого системного процесса, ини­
циализированного в главном цикле процедуры pm_ini t. Она корректирует пер­
воначальную информацию, предоставленную монитором загрузки. Работа функ­
ции pat ch_mem_chunks проще, поскольку она оперирует данными в кликах.
Для каждого процесса она получает базовые адреса и размеры сегментов кода
и данных. Сначала к базовому адресу последнего элемента массива свободных
блоков памяти обрабатываемого процесса прибавляется сумма размеров сегмен­
тов кода и данных, а затем полученный размер блока уменьшается на ту же вели­
чину, чтобы соответствующая память была помечена как используемая.
4. 8 . Уп равление памятью в M I N IX 503

4 . 8 . 3 . Реал изация системных


вызовов fork, exit и wait
Системные вызовы f o rk, e x i t и wa i t реализуются соответственно процеду­
рами do_ f o rk, do_pm_exi t и do_wa i t p i d из файла forkexi t . с. Процедура
do_ f o rk (строка 18430) руководствуется последовательностью действий, пере­
численных в пункте 4.7.4. Обратите внимание, что второй вызов proc s_in_u s e
(строка 1 8445) резервирует для суперпользователя несколько последних ячеек
в таблице процессов. При вычислении необходимой потомку памяти в сумму
включается промежуток между данными и стеком, но не сегмент кода. Так дела­
ется потому, что сегмент кода либо разделяемый, либо, если адресные простран­
ства кода и данных процесса объединены, его размер равен нулю. После того как
нужный объем памяти подсчитан, чтобы получить ее, делается вызов a l l oc_
mem. Если память успешно выделена, базовые адреса родителя и потомка преоб­
разуются из кликов в байты, и чтобы выполнить копирование, вызывается функ­
ция sy s_copy, которая отправляет сообщение системному заданию.
После того как память предоставлена, в таблице процессов ищется свободная
ячейка. Предыдущая проверка, затрагивающая переменную p r o c s_in_u s e,
гарантирует, что такая ячейка найдется. Найденная ячейка заполняется, для это­
го в нее сначала копируются данные родительского процесса, затем обновляют­
ся поля mp_parent , mp_f l ag s , mp_ch i l d_ut ime, mp_chi l d_st ime, mp_s eg,
mp_ex i t s t at u s и mp_s i g s t at u s . Некоторые из этих полей требуют специаль­
ной подготовки. Так, в поле шp_ f l ag s наследуется лишь часть битов. Поле mp_
s eg является массивом, элементы которого соответствуют сегментам кода, дан­
ных и стека, и если обнаруживается, что у процесса адресные пространства раз­
делены, ячейка, соответствующая сегменту кода, не меняется и продолжает ука­
зывать на код родительского процесса.
На следующем шаге дочернему процессу назначается идентификатор (PID). Вызов
функции g et_ f r e e_p i d получает свободное значение идентификатора. Это не
так просто, как может показаться, и мы рассмотрим данную функцию позднее.
Вызовы sy s_fork и t e l l_ f s информируют ядро и файловую систему о рожде­
нии нового процесса, чтобы они могли обновить свои структуры таблицы про­
цессов. ( Все процедуры, имена которых начинаются с префикса sy s_, служат
для отправки системному заданию сообщений, запрашивающих различные услу­
ги согласно табл. 2.5). Создание или уничтожение процесса всегда инициируется
менеджером процессов и только потом в число участников этого таинства допус­
каются ядро и файловая система.
Ответное сообщение процессу-потомку отправляется точно в конце кода do_
f o rk. Ответ родителю, содержащий идентификатор нового процесса, посылает­
ся из главного цикла в ma in, как обычный ответ на запрос.
Следующим системным выэовом, который выполняется менеджером процессов,
является ex i t . Вызов принимает процедура do_pm_e x i t (строка 1 8509), но
большую часть черновой работы делает pm_ex i t несколькими строками позже.
Причина такого разделения труда в том, что функция pm_ex i t требуется также
504 Глава 4 . Уп равление памятью

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


Действия в этом случае те же самые, но другие аргументы, и такое разделение
делается, по сути, для удобства.
Прежде всего, pm_ex i t останавливает таймер, если у процесса он активен. Затем
файловой системе и ядру сообщается, что процесс более не может быть запущен
на выполнение (строки 1 8550 и 1 855 1 ) . Вызов ядра sy s_ex i t отправляет сис­
темному заданию сообщение, по получении которого оно очищает запись про­
цесса в своей таблице процессов. Затем освобождается память. Функция f ind_
share определяет, разделяется ли сегмент кода с другими программами. Если
нет, сегмент кода освобождается вызовом f r e e_mem. Следом за этим аналогич­
ный вызов освобождает память, занимаемую стеком и данными. Иногда всю па­
мять можно освободить одним вызовом f r ee_mem, но дело того не стоит. Если
родитель процесса ожидает, то чтобы освободить ячейку, вызывается функ­
ция c l eanup. Если нет, процесс превращаеtся в зомби, это индицируется битом
Z OMB I E в поле mp_ f l ag s , и родителю отправляется сигнал S I GCHI LD.
Полностью уничтожив процесс или превратив его в зомби, функция pm_exi t
ищет в таблице процессов его потомков (строки 1 8582 - 1 8589). Если таковые об­
наруживаются, они делаются потомками процесса ini t. Когда ini t находится
в ожидании и один из его потомков становится зомби, для этого потомка вызы­
вается c l e anup. Таким образом обрабатываются ситуации, подобные показан­
ной на рис. 4.35, а. На этом рисунке мы видим процесс 1 2, который собирается
завершиться, и его родителя, процесс 7, находящегося в ожидании. Чтобы из­
бавиться от процесса 12, для него будет вызвана функция c l eanup, процессы 52
и 53 станут потомками ini t (рис. 4.35, б). В результате оказывается, что про­
цесс 53, который уже завершился, является потомком процесса, выполняющего
вызов wa i t. Следовательно, записи о нем будут корректно очищены.

Ожидание

а б
Рис. 4.35. Иллюстрация механизма завершения процесса: а процесс 1 2 собирается
-

завершиться; б ситуация после его завершения


-

Когда родитель делает вызов wa i t или wa i t p i d, управление передается сле­


дующей функции, do_wa i tp i d (строка 1 8598). Параметры этих двух систем­
ных вызовов различны, ожидаемые действия также различны, но благодаря
специальной настройке значений внутренних переменных (строки 1 8 6 1 3 - 1 8 6 1 5 ),
функция do_wa i t p i d может выполнять оба вызова. После того как присвоены
4. 8 . Уп равление памятью в M I NIX 505

значения внутренним переменным, стартует цикл, в котором просматривается


вся таблица процессов, с целью выяснить, есть ли вообще у процесса потомки
(строки 18623- 1 864 2 ) . Если есть, проверяется, есть ли среди них зомби, кото­
рых теперь можно добить. Когда обнаруживается зомбированный процесс ( стро­
ка 1 8630 ), он уничтожается и функция do_wa i t p i d возвращает код SUS PEND.
Если обнаруживается отслеживаемый процесс-потомок, do_wa i t р id передает
управление назад, отправив предварительно ответное сообщение, говорящее, что
процесс остановлен.
Если оказалось, что у вызвавшего wa i t процесса нет потомков, возвращается
код ошибки (строка 18653). Если потомки есть, но среди них нет ни зомби, ни
отслеживаемых процессов, проверяется , хотел ли сделавший вызов процесс
дожидаться завершения работы потомков. Если да (это - обычный случай) ,
устанавливается бит, означающий, что процесс находится в ожидании (строка
1 8648) , и родитель приостанавливается до тех пор, пока один из его потомков не
завершится.
Когда процесс заканчивает выполняться, а родитель этого процесса ожидает его
завершения (в каком бы порядl\е это ни произошло), для исполнения последних
церемоний вызывается функция c l e anup (строка 1 8660 ). У нее не слишком
много работы. Родительский процесс, прохлаждающийся в ожидании в резуль­
тате вызова wa i t или wa i tp i d, пробуждается, ему передается PID завершивше­
гося потомка, а также код выхода и состояние сигналов. В этот момент память
потомка уже освобождена файловой системой, а ядро исключило его из плани­
рования, поэтому все, что осталось сделать для завершения ритуала, - очистить
занимаемую процессом ячейку в таблице процессов.

4 . 8 . 4 . Реал изация системного вызова ехес


Код системного вызова е х е с соответствует пошаговой процедуре, представлен­
ной в пункте 4.7.7. Этот код находится в функции do_exec (строка 18747) файла
е хе с . с. Сделав несколько простых проверок правильности данных, менеджер
процессов извлекает из пользовательского адресного пространства имя файла
программы, которая будет запущена как новый процесс (строки 18773- 18776).
Вспомним, что библиотечные процедуры, реализующие вызов ехе с , создают стек
в старом образе памяти, как мы видели на рис. 4.32. Этот стек переносится в про­
странство памяти менеджера процессов (строка 1 8782).
Следующие несколько шагов выполняются в цикле (строки 18789 - 1 880 1 ) . Од­
нако для обычных двоичных исполняемых файлов цикл имеет одну итерацию,
и именно этот случай мы рассмотрим в первую очередь. В строке 1879 1 файло­
вой системе отправляется специальное сообщение, меняющее текущий каталог,
чтобы полученный путь интерпретировался относительно рабочего каталога
пользователя, а не каталога менеджера процессов. Затем вызывается процедура
a l l owed, которая открывает файл в случае, если его исполнение разрешено.
Если же проверка оканчивается неудачей, вместо дескриптора файла возвращает­
ся отрицательное число, и процедура do_exec завершается с указанием ошибки.
506 Глава 4 . Уп равление памя т ью

Если же файл существует и является исполняемым, менеджер процессов вы­


зывает функцию re ad_header и считывает размеры сегментов. Для обычного
двоичного файла выход из цикла по коду, возвращаемому функцией r e ad_
header, выполняется в строке 18800.
Теперь рассмотрим, что происходит, если исполняемый файл является сценари­
ем. Как и большинство UNIХ-подобных операционных систем, MINIX 3 поддер­
живает исполняемые сценарии. Процедура re ad_header проверяет, составляют
ли первые два байта �волшебную� последовательность ( # ! ), и если такая после­
довательность обнаружена, возвращает специальный код, указывающий на сце­
нарий. Первая строка помеченного таким образом сценария содержит интерпре­
татор для его исполнения и , возможно, флаги и параметры, передаваемые этому
интерпретатору. Например, сценарий, первая строка которого выглядит так,
будет обработан интерпретатором Bourne:
11 ! /Ьin/ sh

В то же время сценарий, начинающийся со следующей строки, обработает интер­


претатор Perl:
11 ! / u s r / l o c a l / Ь i n / p e r l - wт

В последнем случае флаги указывают интерпретатору выдавать предупреждения


о возможных проблемах. Однако поддержка сценариев усложняет вызов е х е с .
Если требуется запустить сценарий, функция do_exec должна загрузить в память
не сам сценарий, а двоичный файл его интерпретатора. После идентификации сце­
нария в нижней части цикла вызывается функция pat ch_s t ack (строка 1880 1 ).
Действие функции pat ch_s t ack можно проиллюстрировать следующим при­
мером. Предположим, что Реrl-сценарий вызывается из командной строки с не­
сколькими аргументами следующим образом:
per l_prog . p l f i l e l f i l e 2

Если в сценарий была включена �волшебная� строка, аналогичная показанной


ранее, функция pat ch_s t ack создает стек для исполнения двоичного Реrl-фай­
ла так, как будто бы командная строка имела вид
/ u s r / l o c a l / Ь i n / p e r l -wт pe r l ll prog . p l f i l e l f i l e 2

Если это действие выполняется успешно, возвращается первая часть строки, то


есть путь к двоичному исполняемому файлу интерпретатора. Далее тело цикла
исполняется еще раз, при этом считывается заголовок файла и размеры сегмен­
тов файла, который необходимо выполнить. В первой строке сценария не разре­
шается указывать другой сценарий в качестве интерпретатора. Именно поэтому
используется переменная r: ее можно инкрементировать лишь однажды, дав воз­
можность однократного вызова функции p a t ch_ s t a c k . Если на второй ите­
рации цикла обнаруживается код, указывающий на сценарий, проверка в стро­
ке 1 8800 прерывает цикл. Код, обозначающий сценарий, имеет символьное имя
ESCRI PT и является отрицательным числом, определенным в строке 1874 1 . В этом
случае проверка в строке 18803 приведет к тому, что процедура do_ex i t завер­
шится с кодом ошибки, который указывает на ее источник: файл, не подлежа­
щий исполнению, или слишком длинную командную строку.
4. 8 . Уп равление памя т ью в M I N IX 507

Чтобы завершить вызов ехе с , необходимо выполнить еще несколько действий.


Функция f ina l_share проверяет, может ли новый процесс разделять код с уже
работающими ( строка 1 8809 ) , а new_rnern выделяет память для нового образа
и освобождает память, отведенную под старый. Перед началом выполнения про­
граммы, для которой совершен вызов е х е с , требуется обеспечить готовность
образа в памяти и таблицы процессов. В строках 1 8 8 1 9 - 1 882 1 в таблице про­
цессов сохраняются индексный узел, файловая система и время модификации
исполняемого файла. Затем стек устанавливается, как показано на рис. 4.32, в,
и копируется в новый образ памяти. Далее с помощью процедуры rw_seg ( стро­
ки 1 8834 - 1 884 1 ) считываются в память сегменты кода (если код не является раз­
деляемым) и данных. Если установлен бит s e t u i d или s e t g i d, файловой сис­
теме необходимо послать уведомление с тем, чтобы она поместила эффективный
идентификатор процесса в свою часть таблицы процессов (строки 18845- 18852).
Указатель на аргументы новой программы сохраняется в таблице процессов ме­
неджера процессов, что позволяет команде p s отобразить командную строку.
В таблице процессов менеджера процессов также инициализируются битовые
маски сигналов. Далее файловой системе передается уведомление, предписываю­
щее закрыть все дескрипторы файлов, которые должны быть закрыты по завер­
шении вызова ехе с , и сохраняется имя команды, выводимое утилитой ps или
при отладке (строки 1 8856- 1 8877) . Как правило, последним шагом является
информирование ядра, однако если трассировка включена, требуется послать
сигнал (строки 1 8878- 1888 1 ) .
Описывая функцию do_exe c , м ы упомянули множество действий, вынесенных
во вспомогательные процедуры в файле е х е с . с. Например, функция re ad_
header (строка 1 8889) не только считывает заголовок и возвращает размеры
сегментов, но и проверяет, является ли файл нормальным исполняемым файлом
MINIX 3 для данного типа процессора. Константа A_I 8 О З 8 6 в строке 18944 опре­
деляется в директивах # i fde f ... # endi f на этапе компиляции. Двоичные исполняе­
мые программы для 32-разрядной операционной системы MINIX 3 и платформы
Intel должны иметь эту константу в заголовке. Если же MINIX 3 компилиро­
валась для 1 6-разрядного режима, вместо константы A_I 8 0 3 8 6 используется
A_I 8 0 8 6 . Кроме того, функция re ad_he ade r проверяет, умещаются ли сегмен­
ты в оперативной памяти. При желании вы можете ознакомиться со значениями
для других процессоров в файле inc l ude / а . ou t . h.
Процедура new_rnern (строка 18980) выясняет, достаточно ли доступной памяти
для загрузки нового образа. Для этого она ищет свободный блок, достаточно
большой для размещения данных и стека в том случае, если код разделяется, а если
код не разделяется, ищется блок для размещения текста, данных и стека в совокуп­
ности. Этот алгоритм можно улучшить, если отдельно искать свободное место
для кода и для стека с данными, так как эти сегменты не обязательно распола­
гать вместе. Данное требование было обязательным для ранних версий MINIX,
однако в MINIX 3 оно снято. Если нужная память найдена, ранее занятая память
освобождается и выделяется новый блок. Если памяти недостаточно, вызов ехес
508 Глава 4 . Уп равление п амятью

завершается неудачей. Выделив память, new_rnern обновляет карту памяти (rnp_


seg ) и передает ее ядру вызовом sy s_newrnap ядра.
Оставшаяся часть кода new_rnern связана с обнулением области неинициализи­
рованных глобальных переменных (сегмент b s s ) , области зазора и сегмента сте­
ка. Многие компиляторы сами генерируют обнуляющий код, но благодаря тому,
что очистка выполняется менеджером процессов, MINIX 3 может работать с ком­
пиляторами, которые так не поступают. Обнуляется и участок между сегмента­
ми данных и стека, чтобы при расширении области данных вызовом brk выде­
ляемая память уже содержала нули. Это - не только дополнительное удобство
для программиста, который может рассчитывать на то, что новые переменные
инициализируются нулем, но еще и средство защиты информации в многополь­
зовательских системах, так как процесс, ранее занимавший память, мог содер­
жать данные, которые нельзя показывать другим процессам.
Следующая процедура называется p a t c h_pt r (строка 19074); ее цель - исправ­
ление значений указателей, показанных на рис. 4.32, б, в форму на рис. 4.32, в.
Алгоритм ее работы прост: найти в стеке все указатели и добавить к ним значе­
ние базового адреса.
Следующие две функции работают совместно. Мы уже ощ1сывали их назначение.
Когда системный вызов е х е с выполняется над сценарием, в качестве исполняе­
мого файла используется двоичный файл интерпретатора. ·Функция i n s e rt_
arg (строка 1 9 1 06) вставляет строки в копию стека, принадлежащую менеджеру
процессов. Она работает под управлением функции pat ch_s tack (строка 19162),
которая ищет все слова в (<Волшебной• строке сценария и вызывает i n s e rt_
arg. Разумеется, значения указателей также должны быть надлежащим образом
скорректированы. Функция i h s e rt_arg проста, однако для ее правильной ра­
боты требуется выполнить несколько проверок. Здесь стоит упомянуть о том,
что при обработке сценариев проверка проблем особенно важна. Сценарии могут
быть с успехом написаны пользователями, а всем компьютерным профессиона­
лам известно, что пользователи по определению являются основным источни­
ком проблем. Если же говорить серьезно, то принципиальным отличием между
сценарием и скомпилированным двоичным файлом является то, что компилятор
отказывается преобразовывать в двоичную форму исходный код, содержащий
какие-либо из выявленных им ошибок. Сценарии подобную проверку не про­
ходят.
На рис. 4.36 показано, как будет обработан вызов сценария оболочки s . sh, опери­
рующий файлом f l . Команда выглядит следующим образом:
s . sh f l

При этом @олшебная• строка сценария указывает, что его следует интерпрети­
ровать с помощью оболочки Bourne:
#! / Ь in / sh

Рисунок 4.36, а иллюстрирует копирование стека из пространства вызываю­


щего процесса, а рис. 4.36, б - преобразование стека функциями pat ch_s t ack
и insert_arg. Обе дщ1граммы соответствуют рис. 4.32, б.
4. 8 . Уп равление памя т ью в M I NIX 509

Массив \О t 52
переменных s а 1 r 48
окружения s u 1 44
=

о \О t 40 о Е М о н 40
s а 1 r 36 \О 1 f \О 36
С-----1•• s u 1 32 =
с_____,"
. h s s 32
НОМЕ /usr/ast Е
=
м о н 28 НОМЕ /usr/ast
=

\О h s 1 28
\О 1 f \О 24 п i ь 1 24
h s s 20 о 20
о 16 Действующий 40 16
32 12 массив о 12
о 8 аргументов 37 8
25 4 32 4
20 о f1 24 о
s.sh
/Ьiп/sh
а б
Рис. 4.36.Обработка сценария: массивы, переданные вызову execve, и стек, созданный
а -

при исполнении сценария; б - вид массивов и стека после обработки функцией patch_stack.
Имя сценария передается программе, интерпретирующей сценарий
Следующая функция, определенная в файле е х е с . с , - это функция rw_seg
( строка 1 9208). При каждом вызове е х е с она исполняется один или два раза.
Обязательный вызов выполняет загрузку сегмента данных, а необязательный
вызов - сегмента кода. Файловая система прибегает :к специальному трюку, что­
бы поместить сразу весь сегмент в пользовательское пространство, а не считы­
вать и копировать файл блок за блоком. Суть в том, что вызов особым образом
декодируется файловой системой и выглядит та�<, как будто сам пользователь­
ский процесс считывает весь сегмент. Лишь несколько первых строк процедуры
считывания файловой системы знают о том, как в действительности обстоит де­
ло. Подобный маневр значительно ускоряет процесс загрузки сегмента.
Последняя подпрограмма в файле ехе с . с называется f i nd_share (строка 19256).
Она по номеру индексного узла, номеру устройства и времени модификации ищет
в таблице процессов процесс, с которым можно разделить код. Это - простой
последовательный поиск подходящего поля в таблице mproc. Конечно, при про­
смотре необходимо игнорировать сам процесс, для которого выполняется поиск.

4 . 8 . 5 . Реал изация системного вызова brk


Как мы видели, модель памяти в MINI X 3 довольно проста: каждому процессу
при его создании выделяется один непрерывный участок памяти для данных
и стека. Процесс никогда не перемещается в памяти, никогда из нее не выгружа­
ется, не растет и не уменьшается. Могут произойти только два важных события:
область данных может израсходовать резерв и достигнуть области стека, и, на­
оборот, стек может разрастись на всю область зазора и попасть в область дан­
ных. С учетом этих обстоятельств , реализация системного вызова brk (файл
51 0 Глава 4. Уп равление памятью

break . с ) относительно проста. При его выполнении сначала просто проверяет­


ся, что указанные размеры допустимы, а затем в таблицы вносятся изменения.
Выполняет вызов подпрограмма do_brk (строка 1 9328), но большая часть рабо­
ты делается в процедуре adj u s t (строка 1936 1 ). Последняя проверяет, не пере­
секлись ли сегменты данных и стека. Если да, вызов brk завершается с ошибкой,
но процесс не уничтожается немедленно. При выполнении сравнения к верхней
границе области данных добавляется значение множителя безопасности, SAFETY_
BYTES , поэтому остается надежда на то, что в стеке есть немного места и процесс
может еще немного поработать. Управление возвращается процессу, чтобы он
хотя бы вывел соответствующее сообщение и правильно завершился.
Обратите внимание: значения SAF ETY_BYTES и SAFETY_C L I CKS заданы в сере­
дине процедуры при помощи директивы # de f i ne. Это довольно необычно, тра­
диционно подобные объявления размещаются в начале файла или в отдельных
заголовочных файлах. Комментарий рядом поясняет, что программист нашел
сложным выбор значения множителя безопасности. Без сомнения, описание бы­
ло расположено таким необычным образом для привлечения внимания и, воз­
можно, чтобы стимулировать дальнейшие эксперименты.
Базовый адрес сегмента данных не меняется, поэтому вызову adj us t требуется
обновлять только длину сегмента. Стек растет вниз с фиксированного конечного
адреса, поэтому если adj u s t обнаруживает, что указатель стека (он передается
в виде параметра) вышел за пределы области стека (то есть достиг более низких
адресов), обновляются как адрес начала сегмента стека, та�< и его длина.

4 . 8 . 6 . Реализация сигналов
С сигналами связаны 8 системных вызовов, перечисленных в табл. 4 . 5 . Как сами
сигналы, так и эти системные вызовы обрабатываются кодом из файла s i gna l . с .

Таблица 4 . 5 . Системные вызовы, относящиеся к сигналам


Системный вызов Назначение
sigactioп Изменение реакции на будущий сигнал
sigprocmask Модификация набора блокируемых сигналов
kill Отправка сигнала другому процессу
alarm Отправка сигнала ALRM самому себе после задержки
pause Приостановка работы до следующего сигнала
sigsuspeпd Изменение набора блокируемых сигналов с последующим вызовом pause
sigpeпdiпg Определение набора текущих (то есть заблокированных) сигналов
sigreturп Восстановление после завершения обработчика сигнала
Системный вызов s i gac t i on поддерживает две функции, s i gac t ion и s i gna l ,
позволяющие процессу изменять свою реакцию н а сигнал. Функция s i gac t i on
регламентирована стандартом POSIX и является в любом случае предпочти­
тельной, однако библиотечная функция s ignal удовлетворяет стандарту ANSI С,
и если программа должна быть переносима на системы, не соответствующие
4. 8 . Уп равление памятью в M I N IX 51 1

стандарту POSIX, необходимо использовать вторую функцию. Код do_s igact ion
(строка 1 9544) начинается с проверок правильности номера сигнала и отсутст­
вия попыток изменить реакцию на сигнал s i gk i l l (строки 19550- 1 955 1 ) . Сиг­
нал s i gk i l l нельзя ни игнорировать, ни блокировать, ни обрабатывать. Это то
исключительное средство, при помощи которого пользователь может контро­
лировать собственные процессы, а системный оператор - пользователей. При
вызове s i gac t ion передаются указатели на структуру типа s i gac t i on, по ад­
ресу s ig_o s a помещаются старые значения атрибутов, а по адресу s i g_ns a -
новый набор атрибутов.
На первом шаге, чтобы скопировать текущие значения атрибутов по указателю
s i g_o s a , вызывается системное задание. Далее, при вызове s igac t i on указа­
тель s i g_ns a может иметь значение NULL. Это означает, что требуется считать
текущие значения атрибутов, не меняя их. В таком случае s i gac t ion немедлен­
но возвращает управление (строка 19560). Если указатель s i g_n s a не равен
NULL, в пространство менеджера процессов копируется новая структура, описы­
вающая действие сигнала.
Код в строках 19567 - 1 9585 модифицирует битовые карты rnp_cat ch, rnp_ignore
и rnp_s i gp ending, чтобы указанный сигнал либо игнорировался, либо обраба­
тывался так, как предлагается по умолчанию, либо вызывал обработчик. Поле
s a_handl er структуры s i ga c t i on используется для передачи указателя на про­
цедуру в исполняемую функцию, если сигнал вызывает обработчик, либо одного
из специальных кодов S I G_ I GN и S I G_DFL, значения которых понятны, если вы
ориентируетесь в рассмотренных ранее стандартах обработки сигналов POSIX.
Кроме того, может использоваться код S I G_ME S S , специфичный для MINIX 3;
мы рассмотрим его позже.
Библиотечные функции s i gadd s e t и s i gde l s e t модифицируют битовые кар­
ты сигналов, хотя те же действия можно реализовать и при помощи макроса, как
и другие простые манипуляции с битами. Тем не менее эти функции требуются
по стандарту P O S I X с целью упростить перенос программ на другие системы,
в том числе те, в которых общее число сигналов превышает число битов в целом
значении. Использование библиотечных функций упрощает и перенос самой
системы MINIX 3 на различные платформы.
Ранее мы упомянули особый случай - код S I G_ME S S . Этот код, проверяемый
в строке 1 9576, доступен только для привилегированных (системных) процессов.
Как правило, такие процессы блокируются, ожидая сообщений-запросов. Это оз­
начает, что обычный метод получения сигнала, при котором менеджер процессов
просит ядро выставить кадр сигнала на стек приемника, будет отложен до мо­
мента, когда сообщение активизирует приемник. Код S I G_ME S S предписывает
менеджеру процессов доставить уведомление, имеющее более высокий приори­
тет, чем обычные сообщения. Уведомление содержит набор активных сигналов
в качестве аргумента, допуская передачу нескольких сигналов в одном сообщении.
В завершение заполняются другие относящиеся к сигналам поля той части
таблицы процессов, которая принадлежит менеджеру процессов. Для каждого
512 Глава 4 . Уп равление памятью

потенциального сигнала здесь есть битовая карта, s a_rna s k , определяющая,


какие сигналы будут блокироваться при обработке. Кроме того, для каждого сиг­
нала хранится указатель, s a_hand l e r. Он может содержать либо указатель на
функцию обработки, либо специальное значение, означающее, что сигнал дол­
жен быть блокирован, обработан, как предписано по умолчанию, либо использо­
ван для генерации сообщения. Адрес библиотечной функции, по завершении об­
работчика выполняющей системный вызов s i greturn, хранится в поле rnp_
s i gre t run. Этот адрес передается в одном из полей сообщения, которое полу­
чает менеджер процессов.
POSIX позволяет процессу изменять способ обработки получаемых им сигналов
даже внутри обработчика. Благодаря этому можно, например, изменить реакцию
на сигнал на время его обработки, а затем восстановить нормальный отклик.
Следующая группа системных вызовов и служит для подобных манипуляций
сигналами. Вызов s i gpending выполняется функцией do_s i gp endi ng (стро­
ка 1 9597), возвращающей битовую карту rnp_s i gp ending, по которой процесс
может определить, имеются ли активные сигналы. Вызов s i gprocrnask, обслу­
живаемый функцией do_s i gp r ocrna sk, возвращает набор блокируемых сигна­
лов. Кроме того, с его помощью можно изменить блокировку либо одного сигна­
ла из набора, либо сразу установить новый набор. В тот момент, когда с сигнала
снимается блокировка, стоит проверить, есть ли задержанные сигналы, для этого
в конце вариантов S I G_S ETМASK и S I G_UNBLOCK вызывается функция che ck_
pending (строки 1 9635 и 1964 1 ). Функция do_s i g s u sp end выполняет систем­
ный вызов s i g su sp end. Этот вызов приостанавливает выполнение процесса до
тех пор, пока не будет получен сигнал. Как и другие обсуждаемые здесь функции,
do_s igsuspend манипулирует битовыми картами. Кроме того, она устанавли­
вает в поле rnp_ f l ags бит S I GSUS PENDED, что и предотвращает выполнение про­
цесса. Опять же, в завершение здесь стоит вызвать функцию check_p end ing.
Наконец, функция do_s igret run обслуживает системный вызов s i gr e turn,
обеспечивающий возврат из пользовательского обработчика сигнала. Эта функ­
ция восстанавливает контекст сигналов, предшествующий входу в обработчик,
а также вызывает функцию check_pending в строке 19682.
Когда пользовательский процесс, такой как команда ki 1 1 , совершает системный
вызов k i l l , происходит обращение к функции do_k i l l (строка 19689) менед­
жера процессов. Один вызов ki l l может привести к доставке сигналов группе
различных процессов, поэтому do_k i l l вызывает функцию check_s ig, кото­
рая проверяет допустимых получателей во всей таблице процессов.
Некоторые сигналы, такие как S I GINT, исходят от самого ядра. Функция k s i g_
pending (строка 1 9699) выполняется, когда ядро посьшает менеджеру процес­
сов сообщение об активных сигналах. Активные сигналы могут иметься у не­
скольких процессов, поэтому цикл в строках 197 14- 1 9722 многократно запра­
шивает активный сигнал у системного задания, передает его процедуре handl e_
s i g и указывает системному заданию на то, что обработка завершена, если про­
цессов с активными сигналами больше нет. В сообщения включается битовая
4. 8 . Уп равление памятью в M I N IX 51 З

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


Следующая функция, hand l e_s i g, побитно обрабатывает битовую карту (стро­
ки 1 9750- 1 9763). Некоторые сигналы ядра требуют особого внимания: иногда
идентификатор процесса изменяется, чтобы сигнал был доставлен группе про­
цессов (строки 19753- 1 9757 ). В противном случае для каждого установленного
бита вызывается функция check_s ig, как в подпрограмме do_k i l l .

Оповещения и таймеры
Системный вызов a l arrn выполняется функцией do_a l a rrn (строка 19769). Она
вызывает следующую функцию, s e t_a l arrn, указав нулевое время оповещения.
Код s e t_a l arrn вынесен в отдельную функцию потому, что он также использу­
ется для отключения таймера, когда процесс завершается, а таймер еще работает.
Функция s i g_a l a rrn работает с таймерами, поддерживаемыми внутри менед­
жера процессов. Сначала она определяет, установлен ли таймер от имени за­
прашивающего процесса, и, если да, истек ли он. Такие проверки позволяют сис­
темному вызову вернуть оставшееся время предыдущего оповещения в секундах
или нуль, если таймер не установлен. Комментарий описывает некоторые про­
блемы, связанные с большими тайм-аутами. В строке 19818 весьма неприятный
на вид код преобразует время в тики, умножая аргумент, переданный при вызове
(время в секундах), на константу нz - число тактов таймера в секунду. Для того
чтобы привести результат к требуемому типу c l o ck_t , нужно трижды преобра­
зовать его тип. В следующей строке тип c l o c k_t переменной t i ck s преобразу­
ется обратно в uns i gn e d l ong. Затем результат сравнивается с преобразова­
нием исходного значения аргумента в uns i gned l ong. Если обнаруживается
несовпадение, то запрошенное время вызывает выход за пределы значений одно­
го из используемых типов данных, и вместо него подставляется значение, указы­
вающее бесконечное время. Наконец, вызывается одна из функций prn_set_
t irner или prn_c anc e l_t irner, соответственно добавляющая и удаляющая тай­
мер из очереди менеджера процессов. Ключевым аргументом последнего вызова
является c au s e_s i g a l arrn
- сторожевая функция, которая должна быть вы­
полнена по истечении таймера.
Взаимодействие с таймерами, поддерживаемое в пространстве ядра, скрыто в вы­
зовах подпрограмм prn_XXX_t irner. Любой запрос оповещения, в конечном сче­
те, вызывающий его генерацию, обычно приводит к запросу установки таймера
в пространстве ядра. Единственным исключением является ситуация, в которой
несколько запросов оповещения возникают одновременно. Однако процессы
могут отменять оповещения или завершать работу до истечения установленных
ими таймеров. Вызов ядра, устанавливающий таймер в пространстве ядра, дол­
жен выполняться лишь при изменении таймера, находящегося во главе очереди
таймеров менеджера процессов.
Когда таймер, стоящий в очереди в пространстве ядра и установленный от имени
менеджера процессов, истекает, системное задание информирует об этом менедже­
ра процессов, посылая ему уведомление, а тот обнаруживает его в своем главном
514 Глава 4. Уп равлен ие памятью

цикле по типу SYN_ALARМ. В результате запускается функция pm_exp i r e_


t ime r s , которая, в конечном счете, вызывает другую функцию, cau s e_s igal arm.
Функция c au s e_s i g a l a rm (строка 1 9935) является упомянутой выше сто­
рожевой функцией. Она получает номер процесса, которому требуется послать
сигнал, проверяет некоторые флаги, сбрасывает флаг ALARM_ON и вызывает
функцию che ck_s i g, отправляющую сигнал S I GALRМ.
По умолчанию сигнал S I GALRМ уничтожает процесс. Если же выполняется его
обработка, то вызов s i ga c t i on должен установить функцию обработки. Вся по­
следовательность событий обработки сигнала S I GALRM показана на рис. 4.37.
Здесь представлены три последовательности сообщений. При помощи сообще­
ния 1 пользователь делает системный вызов a l arm (сообщение менеджеру про­
цессов) , менеджер процессов ставит таймер в очередь таймеров пользователь­
ских процессов и в качестве подтверждения посылает сообщение 2. В течение
какого-то времени новые события могут не происходить. Когда таймер данного
запроса достигнет начала очереди из-за отмены или истечения стоящих впереди
таймеров, системному заданию отправляется сообщение 3, чтобы оно установи­
ло новый таймер для менеджера процессов в пространстве ядра. Сообщение 4
является подтверждением. После этого между событиями снова наступает пе­
рерыв. После того как этот таймер достигает начала очереди таймеров ядра, об­
работчик прерываний от таймера узнает о его истечении. Передача оставшихся
сообщений выполняется быстро. Обработчик прерываний от таймера посылает
таймерному заданию сообщение HARD_INT (5), по нему таймерное задание за­
пускается и обновляет свои таймеры. Сторожевая функция таймера, cau s e_
a la rm, передает уведомление менеджеру процессов (6), который также обновля­
ет свои таймеры. После того как он с помощью собственной таблицы процессов
определяет, что в целевом процессе установлен обработчик сигнала S I GALRМ, он
посьшает системному заданию сообщение 7, чтобы выполнить манипуляции со
стеком, нужные для передачи сигнала пользовательскому процессу. Сообще­
ние 8 является подтверждением. Пользовательский процесс планируется и вы­
полняет обработчик, после чего обращается к менеджеру процессов с вызовом
s i greturn (9). Менеджер процессов передает сообщение 10 системному зада­
нию, чтобы завершить очистку, и получает сообщение 1 1 в качестве подтвержде­
ния. На диаграмме не представлена еще одна пара сообщений между менедже­
ром процессов и системным заданием. С ее помощью передается время работы
системы. Обмен этими сообщениями осуществляется перед сообщением 3.
Следующая функция, do_p au s e , заботится о системном вызове p au s e (стро­
ка 19853). На самом деле она не связана с оповещениями и таймерами, хотя ее
можно использовать для приостановки исполнения программы до получения
оповещения или какого-либо другого сигнала. Все, что ей нужно сделать, - это
установить бит и вернуть код SUS PEND. Это приведет к тому, что главный цикл
менеджера процессов не будет отвечать, сохраняя вызвавший процесс заблоки­
рованным. Не нужно даже информировать ядро, так как оно уже знает о блоки­
ровании процесса.
4. 8 . Уп равление памятью в M I N IX 51 5

.--�����-----,
Уровень

Рис. 4.37. Сообщения при работе таймера. Самые главные из них: 1 пользователь
-

делает вызов alarm; З менеджер процессов обращается к системному заданию,


-

чтобы установить таймер; 6 таймер информирует менеджер процессов


-

об истечении таймера; 7 менеджер процессов запрашивает сигнал


-

для пользователя; 9 обработчик завершается вызовом sigreturп


-

Функции поддержки си г налов


Ранее мы упоминали о нескольких вспомогательных функциях из файла signal . с.
Теперь рассмотрим их подробно. Важнейшей из них является s i g_proc (строка
1 9864), которая и отвечает за отправку сигнала. Сначала она делает несколько
проверок. Попытка послать сигнал завершившемуся или зомбированному про­
цессу является признаком серьезной ошибки и приводит к панике в системе
(строки 1 9889 - 1 9893). Если процесс отслеживается, то при получении сигнала
он останавливается (строки 19894 - 1 9899). Если же сигнал должен игнорировать­
ся, работа функции s i g_proc завершается в строке 1 9902. Это действие приня­
то по умолчанию для ряда сигналов, например для тех, которые задает стандарт
POSIX, но не поддерживает MINIX 3. Если сигнал блокируется, нужно только
установить соответствующий бит в битовой карте mp_s i gpend i ng процесса.
Ключевое значение имеет проверка, отделяющая процессы, которым разрешено
перехватывать сигнал, от тех, которым это не разрешено (строка 199 10). После
этого все особые случаи игнорируются, и процессы, не удостоившиеся права пе­
рехватывать сигнал, завершаются. Единственное исключение составляют сигна­
лы, преобразуемые в сообщения, адресованные системным службам.
Сначала мы рассмотрим обработку сигналов, которые процессу разрешено пе­
рехватывать (строки 1 99 1 1 - 1 9950). Сначала формируется сообщение для ядра,
некоторые части сообщения заполняются копиями информации из принадле­
жащей менеджеру процессов части таблицы процессов. Если ранее процесс бьm
516 Глава 4 . Уп равление памятью

приостановлен сигналом s i gs u s p end, в сообщение включается маска сигналов,


сохраненная в момент остановки, если нет, туда вставляется текущая маска
(строка 199 14). Дополнительно в сообщение включаются некоторые адреса из
адресного пространства процесса-получателя сигнала: адрес обработчика, адрес
библиотечной подпрограммы s i greturn, подлежащей вызову после завершения
обработчика, и текущее значение указателя стека.
Затем выделяется место в стеке процесса. Структура, которая помещается в стек,
показана на рис. 4.38. Часть s i gc ont ext сохраняется в стеке и позднее восста­
навливается из него потому, что ее значение в таблице процессов меняется при
подготовке к запуску обработчика. Часть, названная s i g f rame, содержит адрес
возврата и данные, необходимые вызову s i greturn для восстановления состоя­
ния процесса. Адрес возврата и указатель кадра стека в действительности не ис­
пользуются нигде в MINIX 3. Они нужны для того, чтобы обмануть отладчик
при трассировке обработчика событий .

.._
1
1
1
1
1

1
1
Регистры процессора
1
1
1
t�
(64 байта ) t�

1
1
1
1
1
1
1

Маска
Структура sigcoпtext Флаги
.,,,

....

Указатель на sigпcoпtext
Структура sigframe
Адрес возврата
'
Указатель фрейма
'
Указатель на sigпcoпtext
'
'
'
'
Код (плавающая запятая)
'
'
'

Номер сигнала '


'
'
'

<111
Адрес структуры sigreturп '

Рис . 4.38. Чтобы подготовиться к запуску обработчика, в стек помещаются структуры


sigcoпtext и sigframe. Регистры процессора копируются из кадра стека на момент
переключения контекста
Структура, помещаемая в стек, довольно объемна. Для нее выделяется место
(строки 1 9923 и 19924), а затем, чтобы проверить, достаточно ли байтов в сте ­
ке, вызывается adj u s t . Если места не хватает, происходит переход по метке
dot e rmina t e (здесь вы можете увидеть редко используемую в языке С команду
go t o ), и процесс завершается.
4 . 8 . Уп равление памятью в M I N IX 517

При вызове adj u s t возможна проблема. При обсуждении реализации системного


вызова brk говорилось, что adj u s t начинает сообщать об ошибке тогда, когда
стек приближается к области данных на расстояние SAFETY_BYTES. Дополни­
тельный зазор вводится потому, что программно стек может проверяться лишь
время от времени. В данном случае, вероятно, такой запас прочности излишен,
так как достоверно известно, какая часть стека требуется для сигнала, а допол­
нительное место необходимо только обработчику, который предположительно
является относительно простой функцией. Поэтому возможно, что некоторые
процессы вследствие чересчур строгой проверки в adj u s t будут �заживо похо­
ронены� , но это все же лучше, чем наблюдать таинственные сбои в работе систе­
мы. Таким образом, указанные проверки можно улучшить.
Если в стеке достаточно места, проверяются значения еще двух флагов. Флаг
SA_NODEFER означает, что дальнейшие сигналы такого же типа должны быть бло­
кированы на время выполнения обработчика. Флаг SA_RESETHAND - признак
того, что при получении сигнала обработчик должен быть возвращен в исходное
состояние. ( В результате получается честная имитация устаревшего вызова
s i gna l . Хотя такое поведение обычно рассматривалось как недостаток, при под­
держке устаревших функций приходится поддерживать и их недостатки.) Затем
вызовом sy s_s ends i g ядра отправляется уведомление ядру (строка 1 9940 ) .
В завершение сбрасывается бит-индикатор задержанного сигнала и вызывается
функция unpau s e, которая прерывает любой системный вызов, остановивший
процесс. В следующий раз, когда процесс получит управление, начнет работу об­
работчик. Если по каким-либо причинам все указанные тесты закончатся неудач­
но, менеджер процессов вызовет сбой (строка 1 9949).
Упомянутое ранее исключение (преобразование сигналов в сообщения, адресо­
ванные системным службам) проверяется в строке 1995 1 и выполняется после­
дующим вызовом sy s_k i l l ядра. В результате системное задание посылает уве­
домление получателю сигнала. В отличие от большинства других уведомлений,
уведомление системного задания помимо основной содержит дополнительную
информацию - сведения об источнике и временную метку. Дополнительная
информация представляет собой битовую карту сигналов, с помощью которой
системный процесс, которому адресован сигнал, обнаруживает все активные
сигналы. Если вызов sys_ki l l завершается неудачно, менеджер процессов ге­
нерирует сбой. В противном случае происходит выход из функции s i g_proc
(строка 1 9954). Если проверка в строке 1 995 1 имеет отрицательный результат,
управление передается на метку dot e rminat e.
Теперь давайте взглянем на код завершения процесса под меткой dot erminat e
(строка 1 9957 ). Инструкция got o и метка - самый простой механизм обработки
возможных отказов при вызове adj u s t . Итак, сюда передается управление, если
сигнал по тем или другим причинам не может или не должен быть обработан.
Возможно, перехваченный сигнал необходимо проигнорировать; в этом случае
s i g_p roc просто завершает работу. В противном случае завершиться должен
процесс. Единственный вопрос состоит в том, нужно ли генерировать при этом
дамп памяти. Наконец, процесс завершается вызовом pm_exi t (строка 19967)
так, как будто он делает это сам.
518 Глава 4 . Уп равление памятью

В функции check_s i g (строка 19973) менеджер процессов проверяет, может ли


быть послан сигнал. Например:
ki l l ( O , sig) ;

Этот вызов означает, что указанный сигнал должен быть отправлен всем процессам
в текущей группе (то есть всем процессам, запущенным с того же терминала).
Сигналы, исходящие от ядра и системного вызова rebo o t , также могут затраги­
вать несколько процессов. По этой причине check_s i g в цикле перебирает все
процессы из таблицы процессов, выбирая из них потенциальных получателей
(строки 1 9996-20026). В цикле содержится много тестов, и только если все они
пройдены, при помощи s i g_proc отправляется сигнал (строка 20023 ).
Еще одна функция, которая несколько раз вызывается в рассмотренном нами ко­
де, называется check_p ending (строка 20036). Она перебирает все биты в би­
товой карте mp_s i gp ending процесса, сверяясь с битовыми картами с помо­
щью функций do_s i gma sk, do_s i gr e t run и do_s igsuspend, и смотрит, не
была ли снята блокировка с одного из задержанных сигналов. Обнаружив пер­
вый такой сигнал, она отправляет его. Так как все обработчики событий со вре­
менем вызывают функцию do_s i greturn, все разблокированные активные сиг­
налы в конце концов доставляются.
Процедура unpau s e (строка 20065) необходима для отправки сигналов про­
цессам, приостановленным на одном из вызовов pau s e, wai t , read, wr i t e или
s i gsuspend. Выяснить, обусловлена ли остановка вызовом p au s e , wa i t или
s i gsu spend, можно, сверившись с таблицей процессов менеджера процессов.
Если выясняется, что ни один из этих вызовов не является причиной остановки,
запрос передается файловой системе, которая при помощи собственной функции
do_ unpaus e проверяет, был ли процесс приостановлен для чтения или записи.
В любом случае результат одинаков: незаконченный вызов завершается с ошиб­
кой, а процесс вновь запускается и может обработать сигнал.
Последняя процедура в этом файле называется dump_c or e (строка 20093). Она
записывает на диск дампы памяти. Дамп состоит из заголовка, содержащего ин­
формацию о размере занимаемых процессом сегментов, всей информации состоя­
ния процесса (это копия записи процесса из таблицы процессов ядра) и образов
всех сегментов. Отладчик может прочитать эту информацию, чтобы помочь про­
граммисту определить, что пошло не так в работе программы.
Код записи в файл прост. Здесь снова возможна проблема, упомянутая в предьщу­
щем разделе, но теперь в несколько иной форме. Чтобы гарантировать, что записы­
ваемый в дамп сегмент стека содержит самую последнюю информацию, вызывает­
ся функция adj u s t (строка 200 10). Из-за проверки границы безопасности этот
вызов может �провалиться�. Правда, дамп записывается в любом случае, незави­
симо от успеха вызова, но информация о стеке может оказаться неправильной.

Функции поддержки таймеров


Менеджер процессов в MINIX 3 обрабатывает запросы оповещений от пользовq­
тельских процессов, которым не разрешен непосредственный самостоятельный
контакт с ядром и системным заданием. Такой интерфейс скрывает все подроб-
4 . 8 . Уп равление памятью в M I N IX 51 9

пасти планирования оповещения в системном задании. Возможностью установ­


ки таймера в ядре обладают лишь системные процессы. Соответствующая под­
держка предоставлена в файле t imers . с (строка 20200).
Менеджер процессов управляет списком оповещений и просит системное зада­
ние уведомлять его, когда для оповещения наступает время. Получив оповеще­
ние от ядра, менеджер процессов передает его процессу, сделавшему запрос.
Поддержка таймеров осуществляется тремя функциями. Функция pm_s et_
t ime r устанавливает таймер и добавляет его в список менеджера процессов,
pm_exp i r e_t imer проверяет истекшие таймеры, а pm_c anc e l_t imer удаляет
таймер из списка. Все три функции пользуются подпрограммами библиотеки тай­
меров, объявленными в файле inc l ude / t ime r s . h. Функция pm_s et_t imer
вызывает функцию tmrs_s e t t imer, pm_exp i re_t imer - функцию tmrs_
expt ime r s , а pm_c anc e l_t imer - функцию tmr s_c l r t ime r s . Эти функции
соответственно выполняют поиск в связанном списке, добавление и удаление его
элемента. Вмешательство системного задания с целью корректировки очереди тай­
меров в пространстве ядра требуется лишь при операциях вставки и удаления пер­
вого элемента очереди. В подобных случаях каждая из функций pm_XXX_t imer
использует вызов sys_s e t a l arm ядра, чтобы запросить помощь уровня ядра.

4 . 8 . 7 . Реал изация других систем ных вызовов


Менеджер процессов обрабатывает три системных вызова, связанных со време­
нем - t ime, s t ime и t ime s. Соответствующий код находится в файле t ime . с .
Описания системных вызовов приведены в табл. 4.6.

Таблица 4 . 6 . Три системных вызова, связанных со временем


Вызов Функция
time Считывание текущего реального времени и времени работы системы в секундах
stime Установка часов реального времени
times Считывание учетного времени процесса
Реальное время поддерживается таймерным заданием внутри ядра, однако само
таймерное задание ведет обмен сообщениями лишь с одним процессом - сис­
темным заданием. Таким образом, единственный способ считать и установить
реальное время - послать сообщение системному заданию. Именно этим и зани­
маются функции do_t ime (строка 20320) и do_s t ime (строка 2034 1 ). Реальное
время измеряется в секундах, начиная от 1 января 1 970 года.
Ядро поддерживает учетную информацию для каждого процесса. Каждый такт
часов записывается в счет какого-либо процесса. Ядро не располагает сведениями
о соотношениях «родитель-потомок», поэтому задача накопления информации
об учетном времени потомков процессов возлагается на менеджер процессов. Ко­
гда выполнение дочернего процесса завершается, его учетное время добавляется
в запись родителя в таблице процессов менеджера процессов. Функция do_t imes
( строка 20366) считывает время родительского процесса с помощью вызова
sy s_t imes ядра, адресованного системному заданию, а затем создает ответное
520 Глава 4 . Уп равление памятью

сообщение, помещая в него пользовательское и системное время, начисленное


дочерним процессам.
Файл ge t s et . с содержит только одну процедуру, do_ge t s et (строка 204 15),
выполняющую семь системных вызовов, обязательных согласно стандарту POSIX.
Эти вызовы перечислены в табл. 4.7. Все они настолько просты, что выделять
для них отдельные процедуры не имеет смысла. Вызовы ge t u i d и ge t g i d воз­
вращают как реальные, так и эффективные значения идентификаторов пользо­
вателя (UID) и группы ( GID).

Табл ица 4 . 7 . Системные вызовы, поддерживаемые в файле servers/pm/getset.c


Системный вызов Описание
getuid Возвращает реальный и эффективный идентификаторы пользователя
getgid Возвращает реальный и эффективный идентификаторы группы
getpid Возвращает идентификаторы процесса и его родителя
setuid Устанавливает реальный и эффективный идентификаторы пользователя
setgid Устанавливает реальный и эффективный идентификаторы группы
setsid Создает новый сеанс и возвращает идентификатор процесса
getpgrp Возвращает идентификатор группы процессов
Установить GID или UID несколько сложнее, чем просто прочитать. Сначала
нужно проверить, уполномочен ли процесс изменять эти значения. Если проверка
пройдена, об изменении GID или UID необходимо информировать файловуrо сис­
тему, так как эти значения влияют на защиту файлов. Вызов s et s i d создает новый
сеанс; этого нельзя делать процессу, который уже является лидером группы, и со­
ответствующая проверка делается в строке 20463. После проверки вызов завершает
файловая система, делая процесс лидером группы без управляющего терминала.
В противоположность системным вызовам, рассмотренным к настоящему моменту
в этой главе, вызовы в файле mi s c . с не требуются стандартом POSIX. Их нали­
чие обусловлено необходимостью обеспечить взаимодействие с ядром драйверов
устройств и серверов, находящихся в MINIX 3 в пользовательском пространст­
ве. Подобная необходимость отсутствует в монолитных операционных системах.
Вызовы перечислены в табл. 4.8.

Таблица 4 . 8 . Специальные системные вызовы MINIX З в файле servers/pm/misc.c


Системный вызов Описание
do_allocmem Выделение фрагмента памяти
do_freemem Освобождение фрагмента памяти
do_getsysinfo Получение от ядра информации о менеджере процессов
do_getprocnr Получение индекса таблицы процессов по идентификатору
или по имени пользователя
do_reboot Уничтожение всех процессов, информирование файловой
системы и ядра
do_getsetpriority Считывание или установка системного приоритета
do_svrctrl Преобразование процесса в сервер
4 . 8 . Уп равление памятью в M I N IX 521

Два первых вызова обрабатываются полностью менеджером процессов. Функ­


ция do_a l l o cmem считывает запрос из полученного сообщения, преобразует его
в клики и вызывает функцию a l l o c_mem. Вызов do_a l l ocmem используется,
например, драйвером памяти для выделения памяти под виртуальный диск.
Вызов do_ f r e emem выполняется аналогично, но вместо a l l o c_mem происходит
обращение к функции f r e e_mem.
Остальные вызовы, как правило, требуют поддержки других частей операционной
системы. Их можно рассматривать в качестве интерфейсов с системным заданием.
Вызов do_get sy s in f o (строка 20554) способен выполнять ряд операций в зави­
симости от запроса в полученном сообщении. Он обращается к системному зада­
нию за информацией о ядре, содержащейся в структуре k i n f o (ее определение
находится в файле inc l ude /min i x / type . h), а также предоставляет другому
процессу по запросу адрес части таблицы процессов, принадлежащей менеджеру
процессов, либо копию всей таблицы. Последним действием является вызов функ­
ции sy s_da t a c opy (строка 20582). Функция do_getprocnr самостоятельно
возвращает индекс в таблице процессов по заданному значению PID, а если ей
задано имя целевого процесса, обращается за помощью к системному заданию.
Следующие два вызова, хотя и не требуются стандартом POSIX, в какой-либо
форме поддерживаются в большинстве операционных систем, подобных UNIX.
Процедура do_r eboot посылает сигнал K I L L всем процессам и информирует
файловую систему о том, что нужно подготовиться к перезагрузке. Лишь после
синхронизации файловой системы уведомление передается ядру вызовом sys_
abort (строка 20667). Перезагрузка может быть обусловлена сбоем, а также за­
просом суперпользователя на остановку или перезапуск системы, и ядру необхо­
димо знать, какой из случаев имеет место. Функция do_ge t s etpr i o r i ty под­
держивает известную UNIХ-утилиту ni c e, позволяющую пользователю снизить
приоритет своего процесса, чтобы уступить место другим процессам (возможно,
принадлежащим тому же пользователю). В MINIX 3 эта утилита решает более
важную задачу - она обеспечивает точное управление относительными приори­
тетами системных компонентов. Например, сетевому или дисковому устройству,
получающему быстрый поток данных, можно назначить более высокий приори­
тет, чем устройству, чья скорость приема ниже (скажем, клавиатуре). Если высо­
коприоритетный процесс зациклится и воспрепятствует обслуживанию других
процессов, его приоритет также можно временно снизить. Изменение приоритета
процесса осуществляется при планировании путем помещения процесса в очередь
с более высоким или низким приоритетом. Реализация механизма планирования
описана в главе 2. Если изменение приоритета инициируется планировщиком
в ядре, вовлекать в операцию менеджер процессов не требуется. Пользовательско­
му же процессу в подобной ситуации придется выполнить системный вызов. Все,
что требуется сделать на уровне менеджера процессов, - считать текущее значе­
ние, возвращенное в сообщении, либо сгенерировать сообщение с новым значе­
нием. Новое значение передается системному заданию вызовом sy s_ni c e ядра.
Последняя подпрограмма в файле mi s c . с - do_svrc t l . В настоящее время она
используется для включения и отключения подкачки. Другие функции, обслу­
живаемые этим вызовом, предполагается реализовать в сервере реинкарнации.
522 Глава 4. Уп равление памят ью

Последний системный вызов, который мы рассмотрим в этой главе, - это вызов


pt r a c e из файла t ra c e . с. Код этого файла вы можете найти на компакт-диске
книги и веб-сайте MINIX 3. Вызов p t r a c e используется для отладки программ.
В качестве параметра он принимает одну из 1 1 команд, перечисленных в табл. 4.9.
В менеджере процессов функция do_t r a c e обрабатывает четыре из них: т_ок,
т_RESUМE, Т_ТЕХТ и т_S ТЕР. Она выполняет запросы на включение режима
трассировки и выход из режима. Все остальные команды передаются системному
заданию, которое имеет доступ к части таблицы процессов, принадлежащей
ядру. Для этого вызывается библиотечная функция sy s_t r a c e . Трассировка
поддерживается двумя функциями: f i n d_p r o c ищет трассируемый процесс
в таблице процессов, а s t op_p r oc останавливает трассируемый процесс при по­
лучении сигнала.

Табл ица 4 . 9 . Команды отладки, поддерживаемые в файле servers/pm/trace.c


Команда Описание
Т_SТОР Остановка процесса
т_ок Включение трассировки родительским процессом для данного процесса
T_GEТINS Возврат значения из пространства кода
T_GEТDATA Возврат значения из пространства данных
T_GEТUSER Возврат значения из таблицы пользовательских процессов
T_SEТINS Установка значения в пространстве кода
T_SEТDATA Установка значения в пространстве данных
T_SEТUSER Установка значения в таблице пользовательских процессов
T_RESUME Возобновление исполнения
T_EXIT Выход
T_STEP Установка бита трассировки

4 . 8 . 8 . Утил иты управления памятью


В завершение этой главы мы кратко рассмотрим два файла, содержащие функ­
ции поддержки менеджера процессов: a l l o c . с и ut i l i ty . с . Вы можете найти
эти файлы на компакт-диске и веб-сайте MINIX 3.
Файл a l l oc . с позволяет системе отслеживать, какие области памяти заняты,
а какие свободны. В этом файле определены три точки входа:
+ a l l o c_rnern - запрос блока памяти заданного размера;
+ f r ee_rnern - возврат блока памяти;
+ rnern_ini t - инициализация списка свободных блоков при запуске менедже-
ра процессов.
Как уже отмечалось, функция a l l o c_rnern просто ищет в списке первый блок
подходящего размера. Если она обнаруживает слишком большой блок, она от­
резает от него нужную часть, а лишнее оставляет в списке. Если требуется
весь блок, вызывается функция de l_s l o t , которая полностью удаляет блок из
списка.
4 . 8 . Уп равление памят ью в M I N IX 523

Функция f r e e_rnern должна проверять, можно ли объединить возвращенный


блок с соседними. Если да, вызывается функция rnerge, которая объединяет ука­
занные блоки и обновляет список.
Начальный список, охватывающий всю доступную память, строится функцией
rnern_ini t .
Процедуры из последнего файла, u t i l i t y . с , используются в различных частях
менеджера процессов.
Процедура get_ f r e e_p i d ищет свободный идентификатор для дочернего про­
цесса. Она предотвращает возникновение возможной проблемы. Максимальное
значение идентификатора процесса равно 30 ООО. На самом деле, оно должно оп­
ределяться верхней границей диапазона P I D_t , однако указанное значение было
выбрано потому, что некоторые устаревшие программы используют более узкий
тип. К примеру, если процессу-«долгожителю� был назначен идентификатор 20,
то за время его жизни 30 ООО процессов могут быть созданы и уничтожены. Каж­
дый раз при необходимости в новом идентификаторе переменная инкрементиру­
ется; в конечном счете она достигнет порогового значения, обнулится, а затем
увеличится до 20. Повторное назначение уже используемого идентификатора
приведет к катастрофе (представьте, что будет, если кто-нибудь пошлет процес­
су 20 сигнал). Переменная , хранящая последний назначенный идентификатор
процесса, инкрементируется, и если ее значение выходит за установленный мак­
симум, отсчет начинается заново с PID 2 (идентификатор PID 1 всегда назначен
процессу ini t ). Затем выполняется поиск по всей таблице процессов с тем, что­
бы убедиться, что назначаемый идентификатор процесса не используется. Если
идентификатор процесса занят, процедура повторяется до тех пор, пока не будет
обнаружен свободный идентификатор процесса.
Процедура a l l owed проверяет, предоставлен ли доступ к файлу. Эта функция не­
обходима, например, do_exec, чтобы проверить, является ли файл исполняемым.
Процедура no_sy s не должна вызываться никогда. Она здесь только на тот
случай, если менеджер процессов получит системный вызов с недопустимым но­
мером или вызов, не обрабатываемый им.
Когда менеджер процессов обнаруживает ошибку, после которой невозможно
восстановление, он вызывает функцию p ani c . Она сообщает об ошибке систем­
ному заданию, которое «со скрежетом� останавливает «несущуюся в пропасты
систему MINIX. Просто так эту функцию лучше не вызывать.
Следующая функция в файле ut i l i ty . с называется t e l l_ f s . Она подготав­
ливает сообщение и отправляет его файловой системе, когда последнюю необхо­
димо информировать об обработанных менеджером процессов событиях.
Функция f i nd_p ararn применяется для разбора параметров монитора. В на­
стоящее время с ее помощью извлекается информация об использовании памяти
перед загрузкой MINIX 3, однако при необходимости она может находить и дру­
гую информацию.
Следующие две функции файла u t i l i ty . с предоставляют интерфейсы к биб­
лиотечной функции sys_g e t p r o c , которая вызывает системное задание для
524 Глава 4. Уп равление памятью

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


ция sys_getproc является макросом, определенным в файле inc lude /minix/
sys l ib . h и передающим параметры вызову sy s_get i n f o ядра. Функция get_
mem_map получает карту памяти процесса, а get_s t ac k_pt r - указатель стека.
Обеим функциям нужен номер процесса (индекс в таблице процессов), отличаю­
щийся от идентификатора процесса. Последняя функция в файле ut i l i ty . c ,
которая называется proc_ f rom_p i d, обеспечивает такую поддержку - при вы­
зове она принимает PID, а возвращает индекс таблицы процессов.

Рез ю ме
В этой главе были проанализированы как общие принципы управления памя­
тью, так и их реализация в M INIX 3. Мы увидели, что в простейших системах
вообще не поддерживается подкачка или замещение страниц. Программа, загру­
женная в память, остается там до своего завершения. Некоторые операционные
системы позволяют находиться в памяти одновременно только одному процессу,
в то время как другие поддерживают многозадачность.
Следующим шагом вперед является подкачка (то есть загрузка и выгрузка целых
процессов). Благодаря подкачке система может обрабатывать большее количест­
во процессов, чем то, для которого достаточно пространства в памяти. Процессы,
для которых в ней нет места, целиком выгружаются на диск. Свободные области
в памяти и на диске можно отслеживать с помощью битового массива или спи­
ска свободных участков.
Современные компьютеры часто поддерживают некоторую форму виртуальной
памяти. В простейшем виде адресное пространство каждого процесса делится на
части постоянного размера, называемые страницами, которые могут размещаться
в любом доступном страничном блоке в памяти. Существует множество алгорит­
мов замещения страниц, два наилучших - это алгоритмы •старения� и •второго
шанса� . Для хорошей работы систем замещения страниц выбрать хороший алго­
ритм недостаточно; необходимо также обратить внимание на такие вопросы, как
определение рабочего набора, стратегию предоставления памяти и размер страниц.
Сегментация помогает в управлении структурами данных, изменяющими свой
размер во время выполнения, и упрощает процессы компоновки и совместного
доступа. Она также облегчает предоставление различных видов защиты разным
сегментам. Иногда механизмы сегментации и замещения страниц комбинируют­
ся, что позволяет организовать двухмерную виртуальную память. Системы Intel
Pentium поддерживают сегментацию и замещение страниц памяти.
Управление памятью в MINIX 3 реализовано просто. Процессу выделяется па­
мять, когда он делает системный вызов f or k или ехе с . После того как область
памяти выделена, она никогда не меняет своих размеров, пока работает процесс.
На машинах с процессорами Intel система MINIX 3 поддерживает для процессов
две модели памяти. У маленьких программ инструкции и данные могут разме­
щаться в одном сегменте. В более сложных программах адресные пространства
данных и кода могут быть разными. Такие процессы могут иметь общий код,
Во п росы и задани я 525

поэтому при вызове f or k им выделяется память лишь под данные и стек. Такое
не исключено и при вызове ехе с , если окажется, что в памяти уже находится
процесс, использующий тот же код, который нужен новой программе.
По большей части работа менеджера процессов связана не с отслеживанием памя­
ти, что делается при помощи списка свободных блоков и алгоритма выбора первого
подходящего блока, а с обслуживанием системных вызовов, относящихся к управ­
лению памятью. Некоторые из системных вызовов обеспечивают работу сигналов
в стиле POSIX, а поскольку по умолчанию большинство таких сигналов завершают
процесс, имеет смысл обрабатывать их в менеджере процессов, инициирующем за­
вершение всех процессов. Отдельные системные вызовы напрямую к работе с па­
мятью не относятся, но также обрабатываются менеджером процессов в основном
из-за того, что он меньше файловой системы по объему и поместить их здесь удобнее.

В опросы и задания
1 . Компьютерная система имеет достаточно места для того, чтобы содержать в опе­
ративной памяти четыре программы. Эти программы простаивают в ожида­
нии ввода-вывода половину времени. Какая часть времени работы централь­
ного процессора пропадает?
2. Рассмотрим систему подкачки, когда в памяти содержатся свободные участки
следующих размеров и в следующем порядке: 1 0 Кбайт, 4 Кбайт, 20 Кбайт,
18 Кбайт, 7 Кбайт, 9 Кбайт, 1 2 Кбайт и 15 Кбайт. Какой из них будет выбран
по алгоритму первого соответствия для успешного удовлетворения запроса
сегмента следующего размера:
1 ) 12 Кбайт;
2) 10 Кбайт;
3) 9 Кбайт.
Ответьте на тот же самый вопрос для алгоритмов наилучшего соответствия,
наихудшего соответствия и следующего соответствия.
3. У компьютера есть 1 Гбайт оперативной памяти, выделяемой блоками по
64 Кбайт. Сколько килобайтов занимает битовая карта, хранящая данные о сво­
бодных блоках?
4. Рассмотрите предыдущий вопрос для случая, когда вместо битовой карты ис­
пользуется список свободных блоков. Сколько памяти займет список в луч­
шем и худшем случаях? Считайте, что операционная система занимает пер­
вые 5 1 2 Кбайт памяти.
5. В чем разница между физическим адресом и виртуальным?
6. Опираясь на таблицу страниц на рис. 4.8, сосчитайте физический адрес, соот­
ветствующий каждому из следующих виртуальных адресов:
1) 20;
2 ) 4 1 00;
3) 8300.
526 Глава 4 . Уп р авление памятью

7. На рис. 4.9 поле страницы виртуального адреса имеет разрядность 4 бита, а по­
ле страницы физического адреса - 3 бита. Может ли число страничных битов
виртуального адреса в общем случае быть больше, меньше и равно числу битов
физического адреса? Объясните ответ.
8. Процессор Intel 8086 не поддерживает виртуальную память. Тем не менее не­
которые компании ранее продавали системы, содержащие стандартный про­
цессор 8086 и выполняющие замещение страниц. Предположите, как они это
делали. Подсказка: подумайте о логическом расположении блока управления
памятью (MMU).
9. Считая, что команда выполняется за 1 мкс, а ошибка отсутствия страницы
требует дополнительно п мкс, напишите выражение для фактического време­
ни выполнения команды с учетом того, что ошибки происходят через каждые
k инструкций.
10. Компьютер имеет 32-разрядное адресное пространство и страницы размером
8 Кбайт. Таблица страниц целиком поддерживается аппаратно, на запись в ней
отводится одно 32-разрядное слово. При запуске процесса таблица страниц
копируется из памяти в аппаратуру, одно слово требует 1 00 нс. Если каждый
процесс работает в течение 1 00 мс (включая время загрузки таблицы страниц),
какая доля времени процессора жертвуется на загрузку таблицы страниц?
1 1 . Компьютер с 32-разрядным адресом использует двухуровневую таблицу стра­
ниц. Виртуальные адреса расщепляются на 9-разрядное поле таблицы верх­
него уровня, 1 1 -разрядное поле таблицы страниц второго уровня и смещение.
Чему равен размер страниц и сколько их в адресном пространстве?
12. Далее представлен алгоритм фрагмента программы для компьютера с разме­
ром страницы 5 1 2 байт. Программа расположена по адресу 1 020, указатель
стека равен 8 1 92 (стек увеличивается по направлению к нулю). Напишите
последовательность страничных обращений, создаваемую этой программой.
Каждая инструкция занимает 4 байта (1 слово), включая непосредственные
константы. В последовательности обращений учитываются обращения как
к инструкциям, так и к данным.
Загрузить слово 6 1 44 в регистр О.
Поместить содержимое регистра О в стек.
Вызвать процедуру по адресу 5 1 20, поместив в стек адрес возврата.
Вычесть константу 16 из указателя стека.
Сравнить полученный результат с константой 4.
При равенстве перейти на адрес 5 152.
13. Предположим, что 32-разрядный виртуальный адрес разбивается на четыре
поля. Первые три используются для трехуровневой системы таблиц страниц.
Четвертое поле - это смещение. Зависит ли количество страниц от размера
всех четырех полей? Если нет, какие из полей имеют значение, а какие нет?
14. Компьютер, процессы которого имеют 1 024 страницы в своем адресном про­
странстве, хранит таблицы страниц в памяти. На чтение слова из таблицы
Во п росы и задания 527

страниц требуется 5 нс. Чтобы уменьшить издержки, в компьютере присутст­


вует буфер быстрого преобразования адреса (TLB), который содержит 32 пары
(виртуальная страница, физический страничный блок) и может выполнить
поиск за 1 00 нс. При какой частоте обращений к памяти, успешно реализуе­
мых в TLB, потери в среднем будут меньше 200 нс?
15. Буфер быстрого преобразования адреса на VАХ-машинах не содержит би­
та R. Почему?
16. Машина поддерживает 48-разрядные виртуальные адреса и 32-разрядные фи­
зические адреса. Размер страницы равен 8 Кбайт. Сколько требуется записей
в таблице страниц?
1 7 . RISK-пpoцeccop с 64-разрядными виртуальными адресами и 8 Гбайт опера­
тивной памяти использует инвертированную таблицу страниц с 8-килобайт­
ными страницами. Каков минимальный размер TLB?
18. Компьютер имеет четыре страничных блока. Время загрузки, время послед­
него доступа и биты R и М для каждой страницы показаны в следующей таб­
лице (время считается в тактах системных часов).

Страница Загружена П оследнее обращение R М


о 1 26 279 о о
1 230 260 о
2 1 20 272
3 1 60 280
1) Какую страницу выгрузит алгоритм NR U?
2) Какую страницу выгрузит алгоритм FIFO?
3) Какую страницу выгрузит алгоритм LRU?
4) Какую страницу выгрузит алгоритм второго шанса?
19. Если используется алгоритм замещения страниц FIFO в системе с четырьмя
страничными блоками и восемью страницами, сколько ошибок отсутствия
страниц произойдет для последовательности обращений 0 1 72327 1 03 при ус­
ловии, что четыре страничных блока изначально пусты? Решите эту задачу
для алгоритма LR U.
20. Небольшой компьютер имеет 8 страничных кадров, в каждом из которых содер­
жится по странице. Страничные кадры включают виртуальные адреса А, С, G,
Н, В, L, N, D и F в перечисленном порядке, а относительные значения времени
загрузки равны соответственно 18, 23, 5, 7, 32, 19, 3 и 8. Соответствующие би­
ты R равны 1, О, 1, 1, О, 1, 1 и О, а биты М 1, 1, 1, О, О, О, 1 и 1. В каком порядке
-

алгоритм второго шанса рассматривает страницы и какую из них выбирает?


2 1 . Имеются ли какие-либо обстоятельства, в которых алгоритмы часов и второ­
го шанса выбирают для замещения различные страницы? Если да, то какие?
22. Предположим, что компьютер использует алгоритм замещения страниц PFF,
однако имеющейся памяти достаточно для того, чтобы обслуживать все про­
цессы без ошибок отсутствия страниц. Что происходит?
528 Глава 4. Уп равление памятью

23. У маленького компьютера четыре страничных блока. Во время первого такта


часов биты R равны 0 1 1 1 (у страницы О бит R равен О, у остальных - 1 ) . Во
время последующих тактов часов биты R принимают значения 1 0 1 1 , 1 0 1 0,
1 1 0 1 , 00 1 0 , 1 0 1 0, 1 1 00 и 000 1 . Считая, что используется алгоритм старения
с 8-разрядным счетчиком, напишите четыре значения, которые примет счет­
чик после последнего такта.
24. Сколько времени займет загрузка с диска программы размером 64 Кбайт, ес­
ли среднее время поиска равно 1 О мс, время оборота - 8 мс, каждая дорожка
содержит 1 Мбайт:
1) для страницы размером 2 Кбайт;
2) для страницы размером 4 Кбайт;
3) для страницы размером 64 Кбайт.
Распределение страниц на диске является случайным.
25. Используя результаты предыдущего задания, объясните, почему страницы
имеют малые объемы. Укажите два недостатка 64-килобайтных страниц по
сравнению со страницами размером 4 Кбайт.
26. Одна из первых машин с системой разделения времени PDP- 1 имела память
из 4 К 18-разрядных слов. В каждый конкретный момент времени она содер­
жала в памяти один процесс. Когда планировщик решал запустить другой
процесс, процесс в памяти записывался на страничный барабан из 4 К 1 8-раз­
рядных слов по окружности барабана. Барабан мог начать запись (или чте­
ние) с любого слова, а не только с нулевого. Как вы полагаете, почему была
выбрана эта конструкция?
27. Встроенный компьютер обеспечивает для каждого процесса 65 536 байт ад­
ресного пространства, разделенного на страницы по 4096 байт. Некая про­
грамма имеет размер текста 32 768 байт, размер данных 16 386 байт и размер
стека 15 870 байт. Поместится ли эта программа в адресном пространстве?
А если бы размер страницы был 512 байт, она поместилась бы? Помните, что
страница не может вмещать части двух разных сегментов.
28. Было замечено, что количество инструкций, выполненных между ошибками
отсутствия страниц, прямо пропорционально количеству страничных блоков,
предоставленных программе. Если доступная память увеличивается вдвое, то
средний интервал между ошибками отсутствия страниц также увеличивается
вдвое. Предположим, что нормальная инструкция занимает 1 мкс, но если про­
исходит ошибка отсутствия страницы, она выполняется за 200 1 мкс (то есть
2 мс идут на обработку прерывания). Если программа требует для работы 60 с
и в ходе выполнения она вызывает 15 ООО ошибок отсутствия страниц, сколько
времени она работала бы в условиях удвоенного количества доступной памяти?
29. Разработчики из компании 4 Экономные операционные системы• размышля­
ют о способе сокращения объема резервного пространства для хранения дан­
ных, необходимого их операционной системе. Ведущий специалист пред­
ложил вообще не беспокоиться о сохранении текста программы в области
подкачки, а просто загружать его страницами прямиком из двоичного файла
всякий раз, когда он требуется. Имеются ли проблемы при таком подходе?
Вопросы и задания 529

30. Объясните разницу между внутренней и внешней фрагментацией. Какая из


них происходит в системах замещения страниц? А в системах с <1:чистой� сег­
ментацией?
3 1 . Если поддерживаются и сегментация, и замещение страниц (как в Pentium),
сначала должен быть найден дескриптор сегмента, затем - идентификатор
страницы. Может ли при таком двухуровневом поиске работать также буфер
быстрого преобразования адреса (TLB)?
32. Почему при принятой в MINIX 3 системе управления памятью необходимы
программы типа chmern?
33. На рис. 4.34 показано начальное потребление памяти первыми четырьмя ком­
понентами системы MINIX 3. Каковым будет значение cs для следующего
компонента, загруженного после r s ?
34. IВМ-совместимые компьютеры оснащены областью памяти, недоступной для
программ. Эта область находится в диапазоне адресов от 640 Кбайт до 1 Мбайт.
После того как монитор загрузки M INIX 3 перемещается в область, располо­
женную ниже 640 Кбайт, объем памяти, доступной для загрузки программ,
становится еще меньше. Глядя на рис. 4.34, ответьте, сколько памяти доступ­
но для загрузки программ в области между ядром и недоступным фрагмен­
том, если под монитор загрузки выделено 52 256 байт?
35. Имеет ли в предыдущем задание значение то, сколько в точности байтов за­
нимает монитор загрузки? Или же происходит округление до целого числа
кликов?
36. В пункте 4.7.5 при описании системного вызова е х е с было упомянуто, что
реализация алгоритма поиска подходящего участка свободной памяти перед
освобождением памяти текущего процесса не является оптимальной. Усовер­
шенствуйте данный алгоритм.
37. В пункте 4.8.4 было отмечено, что предпочтительнее искать свободные участ­
ки для сегментов данных и кода раздельно. Реализуйте эту рекомендацию.
38. Измените код функции adj u s t так, чтобы избежать ситуаций, когда полу­
чивший сигнал процесс из-за слишком строгой проверки доступности стека
завершается зря.
39. Чтобы определить текущий объем памяти процесса в MINIX 3, вы можете
воспользоваться командой
c hmem + О a . ou t

Однако эта команда имеет неприятный побочный эффект - файл перезапи­


сывается, а следовательно, изменяются дата и время его создания. Измените
команду chmern, создав на ее основе команду showrnern, которая отображает
память, выделенную в текущий момент под переданный ей аргумент.
Гл ава 5
Ф а й л о вы е с и ст е м ы

Всем компьютерным приложениям нужно хранить и получать информацию.


В собственном адресном пространстве работающий процесс может хранить не­
которые объемы данных, но вместимость такого хранилища ограничена разме­
ром виртуального адресного пространства. Для некоторых приложений этого
размера вполне достаточно, но для других, например систем резервирования
авиабилетов, систем банковского или корпоративного учета, одного только вир­
туального адресного пространства недостаточно.
Вторая проблема - потеря информации, хранящейся в адресном пространстве
процесса, после завершения его работы. Для большинства приложений (на­
пример, баз данных) эта информация должна храниться неделями, месяцами
или даже вечно. Потеря данных после завершения работы процесса для таких
программ неприемлема. Информация должна продолжать существовать даже
при аварийном завершении процесса в случае сбоя компьютера.
Третья проблема состоит в том, что часто возникает необходимость нескольким
процессам одновременно получить доступ к одним и тем же данным (или части
данных) . Если интерактивный телефонный справочник хранится в адресном
пространстве одного процесса, то доступ к нему есть только у этого процесса.
Для решения проблемы необходимо отделить информацию от процесса.
Таким образом, к устройствам долговременного хранения информации предъяв­
ляются три важных требования:
+ устройства должны обеспечивать хранение очень больших объемов данных;
+ информация должна оставаться в сохранности после прекращения работы ис-
пользующего ее процесса;
+ несколько процессов должны иметь возможность параллельного доступа к ин-
формации.
Обычное решение всех этих проблем состоит в размещении информации на
дисках и других внешних хранителях в модулях, называемых файлами. Процес­
сы могут по мере надобности читать их и создавать новые файлы. Информа­
ция, хранящаяся в файлах, должна быть устойчивой (persistent), то есть на нее
не должно оказывать влияния создание или завершение какого-либо процес­
са. Файл должен исчезать только тогда, когда его владелец выполняет команду
его удаления.
5 . 1 . Файлы 531

Файлами управляет операционная система. Их структура, именование, исполь­


зование, защита и реализация, а также доступ к ним являются важными аспекта­
ми устройства операционной системы. Часть операционной системы, работаю­
щая с файлами, называется файловой системой. Ей и посвящена данная глава.
С точки зрения пользователя, самое важное в файловой системе - это механизм
взаимодействия с ней, то есть пользователю важно знать, как обеспечить имено­
вание и защиту файлов, как выполнять операции с файлами и т. д. Детали внут­
реннего устройства, крайне важные для разработчиков файловой системы, пред­
ставляют для пользователя гораздо меньший интерес. Например, пользователю
не важно, что применяется для отслеживания свободных и занятых блоков дис­
ка, списки или битовые карты, также ему совершенно не интересно, сколько фи­
зических секторов в логическом блоке. По этой причине мы разбили главу на не­
сколько частей. Первые два раздела посвящены пользовательскому интерфейсу
файлов и каталогов. В следующих разделах мы рассмотрим способы реализации
файловой системы. После ознакомления с механизмами безопасности и защиты
мы завершим главу описанием файловой системы MINIX 3.

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

5 . 1 . 1 . И м енование файлов
Файлы являются объектами абстрактного механизма. Они предоставляют сред­
ство сохранения информация на диске и ее последующего считывания. При этом
от пользователя должны скрываться такие подробности, как способ и место хра­
нения информации, а также детали работы дисков.
Вероятно, наиболее важной характеристикой любого абстрактного механизма
является то, как именуются управляемые объекты, поэтому мы начнем изучение
файловой системы с именования файлов. При создании файла процесс дает фай­
лу имя. Когда процесс завершает работу, файл продолжает свое существование,
и по его имени к нему могут получить доступ другие процессы.
Правила именования файлов варьируются от системы к системе, но все совре­
менные операционные системы поддерживают использование в качестве имен
файлов 8-символьных текстовых строк. То есть идентификаторы andrea, bruce
и c a thy являются допустимыми именами файлов. Часто в именах файлов также
разрешается употребление цифр и специальных символов, поэтому допустимы
и такие имена, как 2 , urgent ! и F i g . 2 - 1 4 . Многие файловые системы поддер­
живают имена файлов длиной до 255 символов.
В некоторых файловых системах, например UNI X, различаются прописные
и строчные буквы, тогда как в других, таких как MS- DOS, нет. Таким образом,
имена mar i a , Mar i a и MARI A в системе UNIX указывают на три различных
файла, и в то же время в MS- DOS все три имени относятся к одному файлу.
532 Глава 5 . Файловые систем ы

Операционная система Windows находится где-то посередине между этими край­


ностями. В основе файловых систем Windows 95 и Windows 98 лежала файловая
система MS- DOS; по этой причине были унаследованы многие свойства послед­
ней, в частности, принцип именования файлов. С каждой версией в файловую
систему добавлялись усовершенствования, однако мы в основном ограничимся
изучением возможностей, общих у MS-D OS и �классических� версий Windows.
Операционные системы Windows NT, Windows 2000 и Windows ХР предостав­
ляют поддержку файловой системы MS-DOS, однако обладают и собственной
файловой системой NTFS (New Technology File System - файловая система
новой технологии) , имеющей другие свойства (такие как именование файлов
в кодировке Unicode). В перечисленных версиях Windows система NTFS также
изменялась. В данной главе рассматривается файловая система более старых
версий Windows, таких как Windows 98. Если какая-либо описываемая черта от­
сутствует в MS-DOS или Windows 95, мы особо отметим это. Говоря об NTFS,
мы имеем в виду файловую систему Windows ХР, а если рассматриваемый ас­
пект неприменим к файловым системам Windows NT и Windows 2000, это также
оговаривается. Термин �Windows� означает все операционные системы Win­
dows, начиная с Windows 95.
Во многих операционных системах имя файла может состоять из двух частей,
разделенных точкой, например prog . с. Часть имени файла после точки называ­
ется расширением и.м,ени файла и обычно характеризует его тип. Так, в MS- DOS
имя файла может содержать от 1 до 8 символов плюс расширение от 1 до 3 сим­
волов. В UNIX величина расширения файла определяется пользователем. Кроме
того, файл может иметь несколько расширений, например prog . с . b z 2 , где рас­
ширение b z 2 означает, что файл p r o g . с сжат при помощи алгоритма bzip2.
Некоторые часто встречающиеся расширения имен файлов и их смысловая на­
грузка представлены в табл. 5 . 1 .

Таблица 5 . 1 . Некоторые типичные расширения имен файлов


Расш ирение Значение
file.bak Резервная копия файла
file.c Исходный текст программы на языке С
file.gif Изображение в формате GIF
file.hlp Файл справки
file.html Документ в формате HTML (веб-страница)
file.jpg Статическое изображение стандарта JPEG
file.iso Образ компакт-диска, предназначенный для его записи
file.mpЗ Музыка в аудиоформате MPEG, уровень З
file.mpg Фильм в формате MPEG
file.o Объектный файл (еще не скомпонованный выходной файл компилятора)
file.pdf Документ формата PDF (программы Adobe Acrobat)
file.ps Документ формата PostScript
file.tex Входной файл для программы форматирования ТЕХ
file.txt Текстовый файл общего назначения
file.zip Сжатый архив
5. 1 . Файлы 533

В отдельных системах (например, в UNIX) расширения являются просто согла­


шениями, и операционная система не принуждает пользователя строго этих со­
глашений придерживаться. Файл f i l e . txt может быть текстовым, но это ско­
рее напоминание пользователю, а не руководство к действию для операционной
системы. С другой стороны, не исключено, что компилятор языка С откажется
компилировать файлы с расширениями, отличными от с .
Подобные соглашения особенно полезны, когда одна и та же программа должна
управлять различными типами файлов. Например, интегрированному компиля­
тору языка С может быть предоставлен список файлов, которые он должен пре­
образовать в объектный код и затем скомпоновать, причем некоторые из файлов
могут содержать программы на языке С (к примеру, f o o . с ) , тогда как другие
могут быть ассемблерными (к примеру, bar . s ) или объектными (к примеру,
other . о ) . В этом случае именно по расширениям компилятор отличает одни
файлы от других.
Windows, напротив, принимает расширения в расчет и придает им смысл. Поль­
зователи и процессы могут регистрировать расширения в операционной системе
и указывать, какая программа «берет шефство• над тем или иным расширением.
Когда пользователь совершает двойной щелчок мышью на файле, программа, со­
ответствующая его расширению, запускается, а имя файла передается ей в каче­
стве параметра. Например, двойной щелчок на файле с именем f i l e . doc запус­
кает Microsoft Word, открывая указанный файл для редактирования.
Некоторым пользователям кажется странным, что компания Microsoft по умол­
чанию скрывает известные расширения файлов, если уж они играют такую важ­
ную роль. К счастью, большинство параметров Windows, по умолчанию «непра­
вильных• , можно изменить, хотя для этого нужно быть опытным пользователем
и знать, где найти их.

5 . 1 . 2 . Структура файла
Файлы могут быть структурированы несколькими способами. Три типа струк­
тур показаны на рис. 5 . 1 . Файл на рис. 5. 1 , а представляет собой неструктуриро­
ванную последовательность байтов. В данном случае операционная система не
интересуется содержимым файла. Все, что она видит, - это байты. Значения этим
байтам присваиваются программами пользовательского уровня. Такой подход
характерен как для UNIX, так и для Windows 98.
Трактовка операционной системой файлов как просто последовательностей бай­
тов обеспечивает максимальную гибкость. Пользовательские программы могут
помещать в файлы все что угодно и именовать их любым удобным для них спо­
собом. Операционная система не вмешивается в э.тот процесс, что особенно цен­
но для пользователей, собирающихся сделать что-либо необычное.
Первый шаг по направлению к структуризации иллюстрирует рис. 5 . 1 , б. В дан­
ной модели файл представляет собой последовательность · эаписей фиксирован­
ной длины, каждая со своей внутренней структурой. Для файлов, состоящих
534 Глава 5. Файловые систем ы

и з записей, важным является то, что операция чтения возвращает одну запись,
а операция записи обновляет или дополняет одну запись. Несколько десяти­
летий назад, когда вовсю применялись перфокарты, состоящие из 80 колонок
отверстий, многие операционные системы (на мэйнфреймах) оперировали фай­
лами, состоящими из 80-символьных записей - образов перфокарт. Этими опе­
рационными системами поддерживались также файлы, составленные из 1 3 2 -
символьных записей, предназначенные для линейных принтеров (которые в те
дни печатали по 132 символа в строке). В результате программы читали из вход­
ных файлов 80-символьные блоки и тут же расширяли их до 132 -символьных
блоков. Ни одна современная универсальная система не работает подобным
образом.

1 Байт 1 Запись

Курица Ибис Ягненок


а б в

Три типа файлов: - последовательность байтов;


Рис. 5 . 1 . а
б- последовательность записей; в - дерево
Третий вариант файловой структуры показан на рис. 5. 1 , в. При такой органи­
зации файл представляет собой дерево записей, не обязательно одной и той
же длины. Каждая запись содержит ключевое поле в фиксированной позиции.
Дерево упорядочено по ключевому полю с целью быстрого поиска по заданному
ключу.
Основной файловой операцией здесь является не получение следующей записи,
хотя это также возможно, а получение записи с указанным значением ключа.
Для файла, показанного на рис. 5. 1 , в, можно, например, запросить у системы
запись с ключом •пони �, не беспокоясь о точном положении записи в файле
(вольера в зоопарке). При добавлении новой записи операционная система, а не
пользователь должна решать, куда ее поместить. Такой тип файлов принципи­
ально отличается от неструктурированных потоков байтов, применяемых в UNIX
и Windows 98. Подобные файловые системы характерны для больших мэйнфрей­
мов, еще используемых для коммерческой обработки данных.
5. 1 . Файлы 535

5 . 1 . З . Тип ы файлов
Многие операционные системы поддерживают различные типы файлов. Напри­
мер, в системах UNIX и Windows проводится различие между обычными файла­
ми и каталогами. Кроме того, в UNIX различают символьные и блочные специ­
альные файлы. Windows ХР также использует файлы метаданных, которые мы
рассмотрим позднее. К обычным (regular) файлам относятся все файлы, содер­
жащие пользовательскую информацию. Все файлы на рис. 5. 1 являются просто
файлами. Каталоzи - это системные файлы, обеспечивающие структуризацию
файловой системы. Позже мы рассмотрим их подробнее. Символьные специаль­
ные файл ы имеют отношение к вводу-выводу и используются для моделирова­
ния последовательных устройств ввода-вывода, таких как терминалы, принтеры
и сети. Блочные специальные файлы находят применение при моделировании
дисков. В данной главе в основном рассматриваются обычные файлы.
Обычные файлы, как правило, являются либо АSСП-файлами, либо двоичны­
ми файлами. ASCII -файлы содержат текстовые строки. В некоторых системах
каждая АSСП -строка завершается символом возврата каретки. В других (на­
пример, UNIX) используется символ перевода строки. Есть системы (напри­
мер, Windows), где требуются оба символа. Строки не обязаны иметь одну и ту
же длину.
Главным преимуществом АSСП-файлов является то, что они могут отображать­
ся на экране и выводиться на печать «как есты" без какого-либо преобразования,
и редактироваться любым текстовым редактором. Более того, если несколько
программ использует АS С П - файлы для ввода и вывода, несложно соединить
вход одной программы с выходом другой, как это делается в конвейерах оболоч­
ки. ( Обмен данными между процессами не становится проще, но интерпретация
информации облегчается, если ее представление стандартизировано.)
Помимо АSСП-файлов существуют двоичные файлы. При выводе их на принтер
получается невразумительный набор символов. Но в действительности у них
есть некая внутренняя структура, известная использующей их программе.
Например, на рис. 5.2, а показан простой исполняемый двоичный файл, входя­
щий в одну из версий UNIX. Хотя технически любой файл представляет собой
всего лишь последовательность байтов, операционная система станет исполнять
его только в том случае, если он имеет соответствующий формат. Файл состоит
из пяти разделов: заголовка, текста, данных, данных переадресации и символь­
ной таблицы. Заголовок начинается с так называемого маzическоzо числа, и:ден­
тифицирующего файл как исполняемый (чтобы предотвратить случайный «За­
пуск• файла другого формата). Следом за сигнатурой в заголовке располагаются
поля с размерами различных частей файла, адресом точки входа файла и некото­
рые флаги. За заголовком следуют текст программы и данные. Они загружаются
в оперативную память и настраиваются на работу по адресу загрузки при помо­
щи битов переадресации. Символьная таблица используется для отладки.
Второй пример двоичного файла представляет собой архив в системе UNIX. Он
состоит из набора библиотечных процедур (модулей), откомпилированных, но
536 Глава 5. Файловые системы

не скомпонованных. Каждой процедуре предшествует заголовок, содержащ:ий ее


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

«Магическое» число
/ Имя модуля

т
Заголовки
Размер текста
Размер данных Дата
s
" Размер
111
о релокационного блока Владелец
еС\1 Размер таблицы Объектный
С') символов модуль Защита

1
Точка входа Размер
Флаги Заголовки
Текст

l l
Объектный
модуль
Данные Заголовки

I Биты релокации I
I Таблица
символов
I Объектный
модуль

1 J
а б
Рис. 5 . 2 . Структура двоичного файла: а - исполняемый файл; б архив
-

Все операционные системы должны распознавать, по крайней мере, один тип


файлов - собственные исполняемые файлы, но некоторые операционные систе­
мы различают и другие типы файлов. Старая система TOPS-20 (для компьютера
DECsystem 20) даже анализировала время создания каждого предоставляемого
ей на исполнение файла. Затем она находила исходный файл и проверяла, не
был ли он изменен, после того как был создан исполняемый файл. Если оказыва­
лось, что исполняемый файл уже устарел, операционная система автоматически
перекомпилировала исходный файл. В переводе на язык UNIX - программа
rnake была встроена в оболочку. Расширения имен файлов были обязательными,
5 . 1 . Файлы 537

чтобы операционная система могла определить, какая двоичная программа от


какого исходного файла произошла.
Однако такая жесткая привязка типов файлов к содержимому может оказаться
неудобной для пользователя, пытающегося сделать что-нибудь, не предусмот­
ренное разработчиками операционной системы. Представьте, например, систему,
в которой файлы программного вывода автоматически получают расширение
dat (файлы данных). Пусть пользователь написал программу, форматирующую
исходные тексты программ на языке С. Программа читает файл с расширением с ,
обрабатывает его и затем сохраняет результат в файле со стандартным расшире­
нием dat . Если пользователь затем попытается предложить этот файл компиля­
тору С, операционная система не позволит его скомпилировать, так как у файла
для данного действия неверное расширение. Попытка скопировать файл f i le . da t
в f i l e . с также будет отвергнута операционной системой, чтобы защитить поль­
зователя от ошибки.
Хотя такая «дружественность� по отношению к пользователю ( «защита от ду­
рака� ) может быть полезна для новичков, она ставит опытных пользователей
в безвыходное положение, заставляя их тратить массу усилий на то, чтобы пере�
хитрить операционную систему.

5 . 1 4 Доступ к файлам
. .

В старых операционных системах к файлам предоставлялся только один тип


доступа последовательный. В этих системах процесс мог читать байты или за­
-

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


тельных файлов поддерживалась «перемотка� , что позволяло считывать их так
часто, как это требовалось. Последовательные файлы были удобны во времена,
когда в качестве устройства хранения информации использовался не диск, а маг­
нитная лента.
С появлением дисков стало возможным читать байты или записи файла в произ­
вольном порядке или получать доступ к записям по ключу. Файлы, байты кото­
рых могут быть прочитаны в произвольном порядке, называются файлами про­
изволъноzо доступа. Такие файлы используются многими приложениями.
Файлы произвольного доступа очень важны для ряда приложений, например для
баз данных. Если клиент звонит в авиакомпанию с целью зарезервировать место
на конкретный рейс, программа резервирования авиабилетов должна иметь воз­
можность получить доступ к нужной записи, не читая все тысячи предшествую­
щих записей с информацией о других рейсах.
Место начала чтения указывается двумя способами. В первом случае каждая
операция read неявно устанавливает позицию в файле. Во втором варианте ис­
пользуется специальная операция s e e k, устанавливающая новую текущую по­
зицию. После выполнения операции s e ek файл можно читать последовательно
с текущей позиции.
В некоторых старых операционных системах, работающих на мэйнфреймах, способ
доступа к файлу (последовательный или произвольный) указывался в момент
538 Глава 5. Файловые систем ы

создания файла. Это позволяло операционной системе применять различные ме­


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

5 . 1 . 5 . Атрибуты файлов
У каждого файла есть имя и данные. Помимо этого, все операционные системы
связывают с каждым файлом другую информацию, например дату и время со­
здания файла, а также его размер. Мы будем называть эти дополнительные све­
дения атрибутами файлов, хотя их иногда обозначают термином метаданные.
Список атрибутов значительно варьируется от системы к системе. В табл. 5.2
показаны некоторые возможные атрибуты, однако существуют и другие. На
практике ни в одной операционной системе не используются сразу все приве­
денные в таблице атрибуты файлов, но каждый из них можно встретить в той
или иной системе.

Таблица 5 . 2 . Некоторые возможные атрибуты файлов


Атрибут Значение
Защита Кто и каким образом может получить доступ к файлу
Пароль Пароль для получения доступа к файлу
Создатель Идентификатор пользователя, создавшего файл
Владелец Текущий владелец
Флаг только чтения О для чтения/записи; 1 только для чтения
- -

Флаг скрытия О нормальный; 1 не показывать в перечне файлов каталога


- -

Флаг «Системный» О нормальный; 1 системный


- -

Флаг архивации О файл помещен в резервное хранилище; 1 требуется


- -

резервирование
Флаг АSСll/двоичный О ASCll; 1 двоичный
- -

Флаг произвольного доступа О только последовательный доступ; 1 произвольный доступ


- -

Флаг «временный» О нормальный; 1 удаление файла по окончании процесса


- -

Флаги блокировки О неблокированный; ненулевой в случае блокировки


-

Длина записи Количество байтов в записи


Позиция ключа Смещение до ключа в записи
Длина ключа Количество байтов в поле ключа
Время создания Дата и время создания файла
Время последнего доступа Дата и время последнего доступа к файлу
Время последнего Дата и время последнего изменения файла
изменения
Текущий размер Количество байтов в файле
Максимальный размер Количество байтов, до которого можно увеличивать размер файла
Первые четыре атрибута относятся к защите файла и содержат информацию о том,
кто вправе получить доступ к файлу, а кто нет. Возможны различные схемы за­
щиты файла, несколько из них мы рассмотрим позже. В некоторых системах
5 . 1 . Файл ы 539

пользователь должен для получения доступа к файлу указать пароль. В этом слу­
чае пароль должен входить в атрибуты файла.
Флаги представляют собой отдельные биты или короткие битовые поля, управляю­
щие некоторыми специфическими свойствами. Например, скрытые файлы не по­
являются в перечне файлов при распечатке каталога. Флаг архивации представляет
собой бит, следящий за тем, была ли создана для файла резервная копия. Этот
флаг очищается программой архивирования и устанавливается операционной
системой при изменении файла. Таким образом, программа архивирования мо­
жет определить, какие файлы следует архивировать. Временный файл можно ав­
томатически удалить по окончании работы создавшего файл процесса.
Атрибуты длины записи, позиции ключа и длины ключа присутствуют только
у тех файлов, записи которых могут искаться по ключу.
Различные атрибуты, хранящие значения времени, позволяют следить за тем,
когда файл был создан, когда в последний раз он был изменен, когда к нему в по­
следний раз предоставлялся доступ. Эти сведения можно использовать в различ­
ных целях. Например, если исходный файл программы был модифицирован по­
сле создания соответствующего ему объектного файла, данный исходный файл
должен быть перекомпилирован.
В качестве текущего размера файла указывается количество байтов в файле в на­
стоящий момент. В некоторых старых операционных системах мэйнфреймов
при создании файла требовалось указать также максимальную длину файла, что
позволяло операционной системе зарезервировать достаточно места для его по­
следующего увеличения. Современные операционные системы, работающие на
персональных компьютерах, умеют обходиться без подобного резервирования.

5 . 1 6 Операци и с файлами
. .

Файлы позволяют сохранять информацию и получать е е позднее. В различных


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

Создание файла. Файл создается без данных. Этот системный вызов объявля­
ет о появлении нового файла и позволяет установить некоторые его атрибуты.
de l e t e

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

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

close

Закрытие файла. Когда все операции с файлом закончены, атрибуты и дисковые


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

Чтение данных из файла. Обычно байты поступают с текущей позиции в фай­


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

Запись в файл. Запись данных в файл также происходит в текущую позицию


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

Добавление данных в конец файла. Этот системный вызов представляет со­


бой усеченную версию вызова wr i t e. Он позволяет только добавлять данные
к концу файла. Данный вызов может не поддерживаться в операционных сис­
темах с минимальным набором системных вызовов.
seek

Позиционирование в файле. Для файлов произвольного доступа требуется


способ указать, где располагаются данные в файле. Эта операция устанавли­
вает указатель текущей позиции на определенное место файла. Последующие
данные будут считаны из этой позиции и записаны в нее.
get a t t r i but e s

Получение атрибутов файла. Процессам часто требуются атрибуты интересую­


щих их файлов. Например, для сборки программ, состоящих из большого числа
отдельных исходных модулей, в UNIX часто используется программа make.
Эта программа исследует время изменения всех исходных и объектных файлов,
благодаря чему система обходится обработкой минимального их количества.
Для выполнения своей работы программе требуется знать атрибуты файлов.
s e t a t t r i but e s

Установка атрибутов файла. Некоторые атрибуты файла могут устанавливать­


ся пользователем после создания файла. Этот системный вызов предоставля­
ет такую возможность. Например, для файла может быть установлен код за­
щиты доступа и большинство других флагов.
rename

Переименование файла. Этот вызов позволяет изменить имя файла. Поддерж­


ка операционной системой вызова r ename не является необходимой, так как
обычно файл можно скопировать с новым именем, а старый экземпляр удалить.
5. 2 . Катал оги 541

lock

Блокирование файла. Блокирование файла или его фрагмента предотвращает


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

5 . 2 . Каталоги
В файловых системах файлы обычно организуются в каталоzи, или папки, ко­
торые, в свою очередь, в большинстве операционных систем также являются
файлами. В данном разделе мы рассмотрим каталоги, их организацию, свойства
и действия, которые могут быть выполнены с ними.

5 . 2 . 1 . П ростые каталоги
Обычно каталог содержит некоторое число записей, по одной записи на файл.
Один из вариантов показан на рис. 5.3, а, где каждая запись каталога включает
в себя имя файла, его атрибуты и адрес данных файла на диске. Другой вариант
представлен на рис. 5.3, б. Здесь запись каталога хранит имя файла и указатель
на структуру данных с атрибутами и дисковыми адресами. Широко применяют­
ся оба этих варианта.

games : Атрибуты games


mail : Атрибуты mai1
news :1 Атрибуты news
work 1 Атрибуты work

а б
Структура данных,
содержащая атрибуты
Рис. 5 . 3 . Организация каталогов:
а - атрибуты хранятся в каталоге;
б - атрибуты хранятся отдельно

Когда открывается файл, операционная система ищет его запись в каталоге. Затем
она извлекает и загружает в память атрибуты и дисковые адреса либо из самой
записи, либо из структуры, на которую запись ссылается. При всех последую­
щих обращениях к файлу используется информация из памяти.
Количество каталогов меняется от системы к системе. В простейшем варианте име­
ется один каталог, в котором хранятся все файлы всех пользователей (рис. 5.4, а ) .
Подобные системы бьши распространены в ранних персональных компьютерах,
отчасти потому, что компьютеры были однопользовательскими.
542 Глава 5. Файл овые с и стемы

О Каталог Q Файл

а б в

Ри с . 5.4.Три варианта устройства файловой системы : а один каталог на всех пользователей;


-

б- отдельный каталог для каждого пользователя; в дерево каталогов для каждого


-

из пользователей. Буквами обозначены владельцы каталога или файла


Системам с одним каталогом присуща проблема: если пользователей много, то
некоторые из них могут непреднамеренно создавать файлы с одинаковыми име­
нами. Например, если пользователь А создает файл rna i lbox, а затем то же дела­
ет пользователь В, то файл пользователя А будет перезаписан. По этой причине
описанная схема больше не применяется в многопользовательских системах, од­
нако ее можно встретить в малых встраиваемых системах, к примеру, в персо­
нальных электронных секретарях и сотовых телефонах.
Чтобы избежать конфликтов, связанных с присвоением различными пользо­
вателями одинаковых имен своим файлам, был сделан шаг к выделению персо­
нального каталога каждому пользователю. В этом случае имена, выбираемые
пользователями, не влияют друг на друга, и применение одного и того же имени
в нескольких каталогах не создает проблем. Подобный подход приводит к сис­
теме, изображенной на рис. 5.4, б. Такие системы могут применяться, к примеру,
в многопользовательском компьютере или несложной локальной сети персо­
нальных компьютеров, имеющих общий файловый сервер.
В описанной схеме операционная система должна знать, какой пользователь пы­
тается открыть тот или иной файл, чтобы выполнить поиск в соответствующем
каталоге. Следовательно, возникает необходимость процедуры входа в систему,
где пользователь должен указать имя входа или идентификационные данные.
В системе с одноуровневой структурой каталогов такая процедура не нужна.
Если описанная система реализована в простейшей форме, пользователи имеют
доступ только к файлам, находящимся в их собственных каталогах.

5 . 2 . 2 . Иерархические системы каталогов


Двухуровневая иерархия исключает конфликты имен между файлами различ­
ных пользователей. Тем не менее существует другая проблема: пользователям,
работающим с большим числом файлов, необходимо группировать их. К примеру,
5 . 2 . Катал оги 543

профессору было бы удобно хранить рабочие материалы для студентов отдельно


от черновиков книги, которую он пишет. Следовательно, нужна некая общая иерар­
хия (то есть дерево каталогов). При таком подходе каждый пользователь может
сам создать себе столько каталогов, сколько ему нужно, группируя свои файлы
естественным образом. Этот подход иллюстрирует рис. 5.4, в. Здесь каталоги А,
В и С, вложенные в корневой каталог, принадлежат различным пользователям,
два из которых создали подкаталоги для проектов, над которыми они работают.
Возможность создавать произвольное количество подкаталогов является мощ­
ным стимулом к структуризации файлов, позволяя пользователям лучше орга­
низовать свою работу. По этой причине почти все современные файловые систе­
мы персональных компьютеров и серверов реализуются подобным образом.
Тем не менее, как мы отмечали ранее, в отношении технологий история часто
повторяется. Цифровым камерам необходимо записывать снимки на какой-либо
носитель, как правило, на карту флэш-памяти. Самые первые камеры имели
единственный каталог, а файлы именовались DSC О О О 1 JPG, DSC О О О 2 JPG и т. д.
• •

Однако вскоре производители создали файловые системы с несколькими ката­


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

5 . 2 . 3 . Пути
При организации файловой системы в виде дерева каталогов требуется некий
способ указания файла. Для этого обычно используют два метода. В первом слу­
чае обращение к файлу выполняется по абсолютному пути, составленному из
имен всех каталогов от корневого до того, в котором содержится файл, и имени
самого файла. Например, путь / u s r / a s t / m a i lbox означает, что корневой ка­
талог содержит подкаталог u s r, в который, в свою очередь, вложен подкаталог
a s t , где находится файл mai lbox. Абсолютные пути всегда начинаются от кор­
невого каталога и являются уникальными. В UNI X компоненты пути разделяют­
ся косой чертой ( / ). В Windows в качестве разделителя принята обратная косая
черта ( \ ) . Таким образом, одно и то же имя пути в этих операционных системах
будет выглядеть следующим образом:
+ Windows:
\ u s r \ a s t \mai lbox

+ UNIX:
/ u s r / a s t / ma i lbox

Если первой буквой имени пути бьm разделитель, независимо от используемого


в качестве разделителя символа, это означало, что путь абсолютный.
544 Глава 5. Файловые системы

Помимо абсолютного применяют и относительный путь. Относительный путь


непосредственно связан с концепцией рабочеzо, или текущеzо, каталоzа. Поль­
зователь может назначить один из каталогов текущим (рабочим) каталогом.
В этом случае все имена путей без начального символа разделителя считаются
относительными и отсчитываются относительно текущего каталога. Например,
если текущим каталогом является / u s r / as t , тогда к файлу с абсолютным путем
/u s r / as t /rnai l box можно обратиться просто как к rnai lbox. Другими слова­
ми, если рабочим каталогом является / u s r / a s t , следующие UNIХ-команды вы­
полнят ОДНО и то же:
ер / u s r / a s t /ma i l box / u s r / a s t /mai lbox . ba k
ер ma i lbox ma i l box . bak

Относительная форма задания пути часто оказывается более удобной, хотя она
подразумевает то же, что и абсолютная.
Некоторым программам требуется доступ к файлам независимо от того, какой
каталог является в данный момент текущим. В этом случае они всегда должны
указJ>Iвать абсолютные имена. Например, программе проверки правописания может
понадобиться для выполнения работы прочитать файл / u s r / l i Ь / di c t i onary.
В этом случае она должна использовать абсолютное имя файла, поскольку за­
ранее неизвестно, каким будет рабочий каталог при ее вызове. Абсолютное
имя файла будет работать всегда, независимо от того, какой каталог является
текущим.
Если программе проверки правописания понадобится большое количество фай­
лов из каталога /usr / l ib, она может, обратившись к операционной системе, поме­
нять рабочий каталог на / us r / l ib, после чего указывать просто имя di c t i onary
в качестве первого аргумента системного вызова op en. Явно указав свой рабо­
чий каталог, программа может использовать в дальнейшем относительные име­
на, поскольку точно знает, где она находится в дереве каталогов.
У каждого процесса есть свой рабочий каталог, поэтому, когда процесс меняет
свой рабочий каталог и потом завершает работу, это не влияет на работу других
процессов, и в файловой системе не остается никаких следов от подобных изме­
нений. Таким образом, процесс может без опасений менять свой рабочий ката­
лог, когда это ему удобно. С другой стороны, если библиотечная процедура по­
меняет свой рабочий каталог и не восстановит его при возврате управления,
программа, вызвавшая такую процедуру, может оказаться не в состоянии про­
должать свою работу, так как ее предположения о текущем каталоге окажутся
неверными. По этой причине библиотечные процедуры редко меняют рабочие
каталоги, а когда все-таки меняют, обязательно восстанавливают старое имя пе­
ред возвратом управления.
Большинство операционных систем, поддерживающих иерархические катало­
ги, имеют специальные записи в каждом каталоге, означающие текущий ( ) .

и родительский ( ) каталог. Чтобы продемонстрировать, как работает по­


. .

добная запись, обратимся к дереву каталогов системы UNIX, показанному на


рис. 5.5.
5. 2 . Каталоги 545

Корневой каталог

Ьin etc tmp

ast lib jim


dict. /usr/jim

Рис. 5 . 5 . Дерево каталогов системы UNIX


Для некоторого процесса каталог / u s r / as t является рабочим. Тогда процесс
может использовать две точки ( . . ), чтобы указать на необходимость переместиться
вверх по дереву. Например, он может копировать файл / u s r / l i Ь / d i c t i onary
в собственный каталог при помощи команды
е р . . / l i b / d i e t i onary

Две точки являются инструкцией системе подняться вверх (в каталог u s r) . По­


сле этого нужно открыть каталог l ib и найти в нем файл di ct i onary.
Второй вариант обозначения ( . ) указывает на текущий каталог. Когда команда
ер получает имя каталога (включая точку) в качестве последнего аргумента, она
копирует в него все файлы. Разумеется, более адекватным способом копирова­
ния является команда
ер u s r / l iЬ / d i e t i onary .

Тем не менее с тем же успехом вы можете набрать любую из двух следующих


команд:
ер / u s r / l i b / di e t i onary di e t i onary
ер / u s r / l i b / di e t i onary / u s r / a s t / di e t i onary

Все три команды делают в точности одно и то же.


546 Глава 5. Файловые системы

5 . 2 . 4 . Операции с каталогами
Системные вызовы, управляющие каталогами, значительно отличаются о т сис­
темы к системе (в отличие от системных вызовов для работы с файлами). Чтобы
дать представление о том, что они собой представляют и как выполняются, при­
ведем следующий пример (взятый из UNI X).
c re a t e

Создание каталога. Только что созданный каталог пуст и не содержит дру­


гих записей, кроме точки ( ) и двух точек ( ), помещаемых в каталог опе­
. . .

рационной системой автоматически или в некоторых случаях программой


rnkdi r.
de l e t e

Удаление каталога. Может бьпь удален только пустой каталог. Записи точка ( )
.

и две точки ( ) файлами не являются и удалены быть не могут.


. .

opendi r

Открытие каталога. После этой операции каталог может быть прочитан. На­
пример, для распечатки всех файлов каталога программа, создающая список,
открывает каталог, чтобы прочитать имена всех содержащихся в нем файлов.
Прежде чем каталог может быть прочитан, его следует открыть, подобно от­
крытию и чтению файла.
c l o sedir

Закрытие каталога. Когда каталог прочитан, его следует закрыть, чтобы осво­
бодить место во внутренней таблице системы.
readd i r

Чтение следующей записи открытого каталога. В прежние времена можно бы­


ло читать каталоги с помощью обычного системного вызова r e ad, но такой
подход был небезопасен, так как требовал от программиста умения работать
с внутренней структурой каталогов. Поэтому был создан отдельный систем­
ный вызов readdi r, всегда возвращающий одну запись каталога стандартного
формата независимо от текущей структуры каталогов.
rename

Переименование каталога. Во многих отношениях каталоги аналогичны фай­


лам и могут переименовываться так же, как и файлы.
l ink

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


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

un l i nk

Удаление связи с файлом. Если файл присутствует только в одном каталоге,


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

5 . 3 . Реал изаци я файловой системы


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

5 . 3 . 1 . Структура файловой системы


Как правило, файловые системы хранятся н а дисках. Базовая структура диска
была рассмотрена нами в главе 2. Кратко повторим изложенное: большинство
дисков можно разбить на разделы, каждый из которых имеет независимую фай­
ловую систему. Сектор О диска называется главной заzрузочной записью (Master
Boot Record, MBR) и используется для загрузки компьютера. В конце главной
загрузочной записи находится таблица разделов, содержащая начальные и ко­
нечные адреса всех разделов. Один из разделов таблицы может быть помечен
как активный. При загрузке компьютера BIOS считывает и исполняет код, со­
держащийся в MBR. Первое, что делает программа MBR, определяет актив­
-

ный раздел, считывает его первый блок, называемый заzрузочным, и исполняет


его. Программа загрузочного блока загружает операционную систему раздела.
Для единообразия все разделы начинаются с загрузочного блока, в том числе
и те, которые не содержат загружаемую операционную систему.
Н езависимо от используемой операционной системы, приведенное описание
справедливо для любой аппаратной платформы, система BIOS которой способна
загружать более одной операционной системы.
Терминология в разных операционных системах может быть разной. Например,
главная загрузочная запись иногда называется начальным заzрузчиком проzрамм
(Initial Program Loader, IPL), кодом загрузки тома (volume boot code), или про­
сто главным загрузчиком (masterboot). Некоторые операционные системы не тре­
буют, чтобы для их загрузки раздел был помечен как активный, и предоставляют
548 Глава 5. Файловые систем ы

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


выбора пользователя может длиться в течение какого-то интервала, по истече­
нии которого загружается система, предлагаемая по умолчанию. После того как
BIOS загружает MBR или загрузочный сектор, дальнейшие действия могут раз ­
личаться. Например, программа, загружающая операционную систему, может за­
нимать несколько блоков раздела. Система BIOS способна загрузить лишь один
блок, который загружает дополнительные блоки, если разработчики операцион­
ной системы запрограммировали его соответствующим образом. Разработчик
также может предоставить собственную главную загрузочную запись, однако она
должна уметь работать со стандартной таблицей разделов, чтобы поддерживать
загрузку нескольких операционных систем.
Системы, совместимые с персональными компьютерами, могут иметь не более
четырех главных разделов, поскольку между главной загрузочной записью и гра­
ницей первого 5 1 2-байтового сектора есть место под массив, включающий лишь
4 элемента. Некоторые операционные системы позволяют выделять в таблице
разделов один расширенный раздел, указывающий на связанный список логиче­
ских разделов. Такая структура позволяет иметь неограниченное количество до­
полнительных разделов. BIOS не может запустить операционную систему с ло­
гического раздела, поэтому начальную загрузку требуется проводить с главного
раздела, чтобы загрузить код, управляющий логическими разделами.
В MINIX 3 применяется альтернативный подход к расширенным разделам. Раз­
дел может содержать таблицу подразделов. Преимущество этого подхода заклю­
чается в том, что таблица подразделов имеет ту же структуру, что и главная таб­
лица разделов, а, следовательно, обеими таблицами может управлять один и тот
же код. Потенциально можно выделить собственные подразделы под корневое
устройство, подкачку, двоичные системные файлы и пользовательские файлы.
В этом случае проблемы в одном подразделе не затронут другой, а новую версию
операционной системы можно будет установить, изменив содержимое лишь не­
которых, а не всех подразделов.
Существуют диски, разбиение которых на разделы невозможно. Гибкие диски,
как правило, содержат в первом секторе загрузочный блок. BIOS считывает его
и ищет магическое число, идентифицирующее корректный исполняемый код.
Магическое число предотвращает попытку выполнения кода для неформатиро­
ванного или поврежденного диска. Главная загрузочная запись и загрузочный
блок используют одно и то же магическое число, поэтому с равным успехом
могут применяться в качестве загрузочного кода. Сказанное относится не только
к электромеханическим дисковым устройствам. В устройствах, подобных каме­
рам и персональным электронным секретарям, где используется энергонезави­
симая память (к примеру, флэш-память), часть памяти обычно имитирует диск.
Помимо неизменного загрузочного блока, расположенного в начале раздела, ос­
тальная структура дискового раздела может меняться в зависимости от файло­
вой системы. UNIХ-подобная файловая система содержит некоторые элементы,
представленные на рис. 5.6. П ервый из них, суперблок, включает все базовые
параметры файловой системы и считывается в память при загрузке компьютера
или первом использовании файловой системы.
5. 3. Реализация файл о в о й системы 549

..----- Диск целиком -------1-.

Таблица разделов Раздел диска


� � · �
Главная загрузочная
запись

Суперблок Управление свободным l-узлы Корневой Файлы и каталоги


пространством каталог
Рис. 5 . 6 . Вариант организации файловой системы

Далее представлена информация о свободных блоках в файловой системе. Затем


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

5 . 3 . 2 . Реал изация файлов


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

Н еразрывные файлы
Простейшей схемой выделения файлам определенных блоков на диске являет­
ся система, в которой файлы представляют собой наборы смежных блоков диска.
Тогда на диске с блоками по 1 Кбайт файл размером в 50 Кбайт будет занимать
50 последовательных блоков. У неразрывных файлов есть два существенных пре­
имущества. Во-первых, такую модель легко реализовать, так как системе, чтобы
определить, какие блоки принадлежат тому или иному файлу, нужно следить всего
лишь за двумя числами: номером первого блока файла и числом блоков в файле.
Зная первый блок файла, любой другой его блок легко получить при помощи
простой операции сложения.
Во-вторых, при работе с неразрывными файлами производительность просто
превосходна, так как весь файл может быть прочитан с диска за одну операцию.
Требуется только одна операция позиционирования (для первого блока). После
этого более не нужно искать цилиндры и тратить время на ожидание поворота
диска, поэтому данные могут считываться с максимальной скоростью, на какую
способен диск. Таким образом, непрерывные файлы легко реализуются и для
них характерна высокая производительность.
К сожалению, неразрывные файлы имеют и серьезный недостаток: их использо­
вание ведет к фрагментации дисков. Поначалу фрагментация не является про­
блемой, поскольку каждый новый файл можно записать сразу следом за преды­
дущим. Однако в конечном счете диск заполнится, и возникнет необходимость
550 Глава 5 . Файловые системы

сжать его (что непозволительно дорого) либо реорганизовать свободные фраг­


менты. Последнее требует ведения списка свободных фрагментов, что вполне ре­
ально. К тому же, при создании нового файла необходимо знать его конечный
размер, чтобы разместить в блоке подходящего размера.
Как было отмечено в главе 1, по мере появления новых поколений технологий
в вычислительной технике история может повторяться. Много лет назад вы­
деление памяти непрерывными блоками благодаря простоте и высокой произ ­
водительности применялось в файловых системах магнитных лент (в то время
удобству пользователя не придавалось большого значения). Затем от него отка­
зались, сочтя недопустимой необходимость указывать размер файла в момент
его создания. Однако с изобретением CD-ROM, DVD и прочих видов оптиче­
ских носителей с однократной записью неразрывные файлы снова оказались
в фаворе. Подобные носители допускают выделение памяти непрерывными бло­
ками, более того, оно широко распространено. Размеры всех файлов известны за­
ранее и неизменны на протяжении всего срока использования файловой систе­
мы CD-ROM. Вывод: важно изучать старые системы и удачные концептуально
простые идеи, поскольку в будущем они могут получить совершенно неожи­
данное применение.

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

Файл А

Блок а Блок 1 Блок 2 Блок З Блок 4


файла файла файла файла файла
Физический 4 7 2 1 а 12
блок Файл В

Блок а Блок 1 Блок 2 Блок З


файла файла файла файла
Физический 14
блок 6 з 11

Рис. 5 . 7. Размещение файла в виде связанного списка блоков диска

В отличие от систем с неразрывными файлами, связанный список позволяет ис­


пользовать каждый блок диска. Нет потерь дискового пространства на фрагмен­
тацию (если не считать потери в последних блоках файла). Кроме того, в каталоге
нужно хранить только адрес первого блока файла. Всю остальную информацию
можно найти по указанному адресу.
5.3 . Реализаци я файловой системы 551

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


вольный доступ оказывается очень медленным. Чтобы получить доступ к бло­
ку п, операционная система должна сначала прочитать первые п 1 блоков по-

очереди. Очевидно, такая схема оказывается медленной.


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

Связанные списки с индексацией


Оба недостатка предыдущей схемы организации файлов в виде списков могут
быть устранены, если указатели на следующие блоки хранить не прямо в бло­
ках, а в отдельной таблице, загружаемой в память. На рис. 5.8 показан внеш­
ний вид такой таблицы для файлов с рис. 5.7. На обоих рисунках присутствуют
два файла. Файл А занимает блоки диска 4, 7, 2, 10 и 12, а файл В блоки 6, 3, -

1 1 и 14. С помощью таблицы мы можем начать с блока 4 и следовать по цепочке


до конца файла. Те же действия применимы для второго файла, если начать с бло­
ка 6. Обе цепочки завершаются специальным маркером, не являющимся допусти­
мым номером блока (например, - 1 ). Такая таблица, загружаемая в оперативную
память, называется таблицей размещения файлов ( File Allocation ТаЫе, F АТ).

Физический
блок
о
1
2 10
3 11
4 7 ..__ Файл А начинается здесь
5
6 3 ..__ Файл В начинается здесь
7 2
8
9
10 12
11 14
12 -1
13
14 -1
15 � Неиспользуемый блок
Рис . 5 . 8 . Таблица размещения файлов
552 Глава 5. Файловые систем ы

При такой организации все блоки доступны для данных. Кроме того, значитель­
но упрощается произвольный доступ. Хотя для обращения к какому-либо блоку
файла все равно понадобится проследовать по цепочке всех ссылок вплоть до
требуемого блока, в данном случае вся цепочка ссылок уже хранится в памяти и
ее прохождение не требует дополнительных дисковых операций. Как и в преды­
дущем случае, для доступа ко всем частям файла в каталоге достаточно хранить
один целый индекс (номер начального блока файла).
Основной недостаток этого метода состоит в том, что вся таблица должна по­
стоянно находиться в памяти. Для 20-гигабайтного диска с блоками размером
1 Кбайт потребовалась бы таблица из 20 ООО ООО записей, по одной для каждого
из 20 ООО ООО блоков диска. Каждая запись должна состоять как минимум из
3 байт. Для ускорения поиска размер записей должен быть увеличен до 4 байт.
Таким образом, резидентная таблица будет занимать 60 или 80 Мбайт опера­
тивной памяти. Таблица, конечно, может быть размещена в виртуальной памяти,
но и в этом случае она продолжит занимать большой объем виртуальной па­
мяти и дискового пространства, а кроме того, приведет к генерации страничного
трафика. Операционные системы M S - D O S и Windows 98 используют только
файловые системы F АТ; более поздние версии Windows также предоставляют
их поддержку.

И ндексные узл ы
Последний метод соотнесения блоков диска файлам заключается в связывании
с каждым файлом структуры данных, называемой индексным узлом (index node),
или i-узлом (i-node), содержащей атрибуты файла и адреса блоков файла. Про­
стой пример индексного узла показан на рис. 5.9. При наличии индексного узла
можно найти все блоки файла. Большое преимущество такой схемы перед хра­
нящейся в памяти таблицей из списков состоит в том, что индексный узел ока­
зывается в памяти только тогда, когда открыт соответствующий ему файл. Если
каждый индексный узел занимает п байт, а одновременно открыть можно k фай­
лов, для массива индексных узлов в памяти потребуется всего kn байт.
Обычно эта величина значительно меньше, чем размер таблицы F АТ. Это легко
объясняется. Размер таблицы, хранящей список всех блоков диска, пропорцио­
нален емкости самого диска. Для диска из п блоков потребуется п записей в таб­
лице. Таким образом, размер таблицы линейно растет с ростом размера диска.
Для схемы индексных узлов, напротив , требуется массив в памяти с разме­
ром, пропорциональным максимальному количеству файлов, которые можно
открыть одновременно. При этом не важно, какой именно размер диска, 1, 1 0
или 1 0 0 Гбайт.
С такой схемой связана проблема, суть которой в том, что при выделении каждо­
му файлу фиксированного количества дисковых адресов этого количества может
не хватить. Одно из решений заключается в резервировании последнего дис ­
кового адреса не для блока данных, а для адреса косвенноzо блока, содержащего
адреса блоков диска. Этот принцип можно расширить и ввести блоки с двойным
и тройным уровнем косвенности, как показано на рис. 5.9.
5. 3 . Реализация файловой системы 553

1-узел
Атрибуты Однократный
Адреса
блоков

Рис. 5 . 9 . Индексный узел с тремя уровнями косвенных блоков

5 . 3 . З . Реал изация каталогов


Прежде че м прочитать файл, его следует открыть. П ри открытии файла опера­
ционная система оперирует указанным пользователем путем, чтобы найти за­
пись в каталоге. Разумеется, чтобы найти запись в каталоге, сначала требуется
найти корневой каталог. Корневой каталог может иметь фиксированное место­
положение относительно начала раздела или определяться на основе другой ин­
формации. Например, в классической файловой системе UNIX суперблок содер­
жит сведения о размерах структур данных файловой системы, предшествующих
области данных. С помощью суперблока можно определить местоположение
индексных узлов. Первый индексный узел указывает на корневой каталог, созда­
ваемый одновременно с файловой системой UNIX. В Windows ХР информация
загрузочного сектора (который, на самом деле, занимает значительно больше,
чем один сектор) задает расположение главной таблицы фай;лов (Master File ТаЫе,
MFT), с помощью которой определяется местоположение других объектов фай­
ловой системы.
После обнаружения корневого каталога выполняется поиск нужной записи в де­
реве каталогов. Запись каталога предоставляет информацию, с помощью кото­
рой можно найти дисковые блоки. В зависимости от системы это может быть
дисковый адрес всего файла (для неразрывных файлов), номер первого блока
файла (обе схемы со связанными списками) или номер индексного узла. Во всех
случаях основная функция системы каталогов состоит в преобразовании АSСП­
имени в информацию, необходимую для поиска данных.
554 Глава 5. Файловые систем ы

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

Общие файл ы
В главе 1 мы кратко упомянули о связях между файлами, например, одного проекта,
упрощающих совместную работу с ними нескольких пользователей. На рис. 5. 1 О
показана файловая система с рис. 5.4, в, однако теперь один из файлов пользова­
теля С присутствует также в одном из каталогов пользователя В.

Общий файл
Ри с . 5. 1 О. Файловая система, содержащая общий файл

В UNIX атрибуты файлов хранятся в индексных узлах, что упрощает совместное


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

на индексный узел другой файловой системы. Кроме того, файл может иметь
только одного владельца и один набор разрешений. Если владелец совместно ис­
пользуемого файла удалит его запись из своего каталога, не исключено, что дру­
гой пользователь лишится возможности удалить этот файл из своего каталога
(при отсутствии соответствующего разрешения).
Альтернативным способом совместного использования файлов является созда­
ние нового типа файла, содержимое которое представляет собой путь к другому
файлу. Такой вид связи работает для монтируемых файловых систем. Более то­
го, если в путях существует возможность указывать сетевые адреса, можно орга­
низовать связь с файлом, расположенным на другом компьютере. В UNIX этот
вид связи называется символьной связью, в Windows - ярлыком, а в Мае OS фир­
мы Apple - псевдонимом. Символьные связи могут применяться в системах, где
атрибуты хранятся в записях каталогов. Несложно понять, что синхронизация
множества записей каталогов, которые содержат атрибуты, - непростая задача.
Любое изменение, внесенное в файл, затрагивает все соответствующие ему за­
писи каталогов. Недостатком символьных связей является то, что при удалении
и даже при переименовании целевого файла они становятся недействительными.

Катало г и в Wi ndows 98
Файловая система в начальной редакции Windows 95 была идентична файловой
системе MS-DOS, однако уже во второй редакции была организована поддержка
длинных имен файлов и файлов большего объема. Мы будем ссылаться на вторую
версию файловой системы как на файловую систему Windows 98, хотя ее можно
найти и на некоторых компьютерах, работающих под управлением Windows 95 .
В Windows 98 поддерживаются два типа записей каталогов; первую из них
(рис. 5. 1 1 ) мы будем называть базовой записью.

Байты 8 3 1 1 1 4 2 2 4 2 4
Базовое имя Расш. NT Дата/время Последний Дата/время Размер
создания доступ последней записи файла
Атрибуты�
Десятые доли t t
Старшие 1 6 бит t
Младшие 1 6 бит
времени создания начального блока начального блока
Рис. 5 . 1 1 . Базовая запись каталога в Windows 98

Базовая запись каталога содержит всю информацию, которая имелась в запи­


сях каталогов более ранних версий Windows, а также дополнительные данные.
10 байт, начинающиеся с поля NТ, являются добавлениями к предшествующей
структуре Windows 95, которая, к счастью (или, вероятнее, сознательно, с пла­
нами усовершенствования в будущем) , не использовалась. Наиболее важным
обновлением является поле, увеличивающее число битов для указания на на­
чальный блок с 16 до 32. В результате максимальный размер файловой системы
увеличен с 2 1 6 до 232 блоков.
Эта структура рассчитана только на старые имена файлов в формате 8 + 3, уна­
следованном от MS-DOS и СР /М. А что же делать с длинными именами? Чтобы
556 Глава 5. Файловые систем ы

обеспечить возможность использования более длинных имен файлов, одновремен­


но сохранив совместимость с более ранними системами, было решено ввести до­
полнительные записи каталогов. На рис. 5. 1 2 показана альтернативная форма
записи каталога, позволяющая задавать имя файла длиной до 13 символов. Для
файлов с длинными именами автоматически генерируется сокращенная форма
имени и помещается в поля базового имени и расширения базовой записи каталога
(см. рис. 5 . 1 1 ) . Перед базовой записью размещается столько записей, изображен­
ных на рис. 5 . 1 2, сколько нужно для хранения длинного имени файла. Записи
располагаются в обратном порядке. Поле атрибутов всех записей длинного имени
содержит значение OxOF, недопустимое в предшествующих файловых системах
(MS-DOS и Windows 95 ). Таким образом, эти записи будут проигнорированы
при чтении каталога старой системой (например, если каталог находится на гибком
диске). Бит в поле последовательности указывает системе последнюю запись.

Байты 1 10 1 1 1 12 2 4
11 5 символов 1111 6 символов 1 1 2 символа

Последовательность
1 Атрибуты
1 \Контрольная
сумма
Рис. 5 . 1 2 . Запись (частичная) для длинного имени файла в Windows 98
Если все это кажется сложным, мы, пожалуй, согласимся с вами. Обеспечение
обратной совместимости с более ранними и простыми системами в сочетании с
новыми возможностями приводит к хаосу. Несомненно, борцы за чистоту идей
выступят против хаоса, но едва ли смогут разбогатеть на новых версиях операци­
онных систем.

Катало г и в U N IX
В UNIX применяется исключительно простая структура каталогов (рис. 5. 13).
Здесь каждая запись состоит из имени файла и номера индексного узла. Вся ос­
тальная информация, о размере файла, его типе, владельцах, времени изменения
и занимаемых им дисковых блоках, хранится в индексном узле. В некоторых
UNIХ-подобных системах применяется другая схема, но, в любом случае, за­
пись каталога состоит исключительно из АSСП-строки и номера индексного узла.

Байты 2 14
Имя файла

Номер
i-узла
Рис. 5 . 1 З. Запись каталога в UNIX версии 7
Когда открывается файл, файловая система должна найти на диске указанное ей
имя файла. Рассмотрим, как будет происходить поиск файла / u s r / a s t / rnbox.
5. 3 . Реализаци я файл о в о й системы 557

В качестве примера мы взяли файловую систему UNIX, но все сказанное отно­


сится и к другим иерархическим файловым системам. Сначала файловая систе­
ма обнаруживает корневой каталог. Индексные узлы образуют простой массив,
расположение которого устанавливается по информации суперблока. Первая за­
пись в этом массиве - индексный узел корневого каталога.
Сначала файловая система ищет первый компонент пути, u s r, в корневом ката­
логе, чтобы определить номер индексного узла для файла / u s r / . Обнаружить
индексный узел по его номеру несложно, так как расположение узлов фиксиро­
вано. Далее система продолжает поиск с этого индексного узла и находит сле­
дующий компонент, a s t . Обнаружив его, система получает номер индексного
узла для каталога / u s r / a s t . Наконец, в этом каталоге ищется сам файл mЬох.
Затем индексный узел файла считывается в память и остается там до тех пор,
пока файл не будет закрыт. Процесс поиска файла иллюстрирует рис. 5. 14.

1-узел 26
1-узел 6 содержит
содержит Блок 1 32 данные Блок 406
данные содержит о каталоге содержит
Корневой каталог о каталоге /usг каталог 1 32 /usг/ast каталог /usr/ast
1 .

Режимный код 6 .

Режимный код 26 .

1 . . Размер 1 . . Размер 6 . .

Времена Времена 64 graпts


4 Ьiп 1 9 dick
7 dev 1 32 30 eгik 406 92 books
14 lib 51 jim 60 mbox
9 ect 26 ast 81 miпix
6 usг 45 bal 1 7 src
8 tmp
В результате В i-узле 6 Запись /usг/ast В i-узле 26 Запись
поиска указывается, ссылается указывается, /usr/asUmbox
элемента usг что /usг находится на i-узел 26 что /usг/ast ссылается
получаем i-узел 6 в блоке 1 32 находится на i-узел 60
в блоке 406
Рис. 5 . 1 4. Поиск файла /usr/ast/mbox

Относительные пути обрабатываются точно так же, как абсолютные, за исключе­


нием того, что поиск начинается не с корневого каталога, а с текущего. В каждом
каталоге есть записи с именами точка ( . ) и две точки ( . . ), добавляемые при
создании каталога. Записи точка ( . ) соответствует индексный узел самого ка­
талога, а записи две точки ( . . ) - индексный узел каталога верхнего уровня.
Таким образом, если дано имя файла . . / d i c k / prog . с, система найдет две точ­
ки ( . . ) в текущем каталоге, получит индексный узел родительского каталога
и будет искать в нем имя di ck. Для обработки таких имен не применяется ника­
ких специальных алгоритмов. С точки зрения системы каталогов, это обычные
АSСП-строки, ничем не отличающиеся от прочих имен.
558 Глава 5. Файловые системы

Катало г и в NTFS
Текущей файловой системой для продуктов Microsoft на сегодняшний день явля­
ется NТFS (New Technology File System - файловая система новой технологии).
Рамки этой книги не предусматривают ее детального описания, однако некото­
рые проблемы, с которыми сталкивается NТFS, и их решениями мы ознакомимся.
Одна из проблем - длинные имена файлов и путей. NТFS поддерживает длин­
ные имена файлов (до 255 символов) и путей (до 32 767 символов). Поскольку
предшествующие версии Windows в любом случае не способны читать файло­
вую систему NТFS, сложная структура каталогов с обратной совместимостью не
нужна, и поле имени имеет переменную длину. Также предоставляется поддерж­
ка второго имени в формате 8 + 3, позволяющая устаревшим системам получать
доступ к NТFS-файлам по сети.
NТFS предусматривает использование в именах файлов различных алфавитов
с помощью кодировки Unicode. В Unicode каждый символ занимает 16 бит; это­
го достаточно для представления множества языков с очень большими алфави­
тами (например, японского языка). Однако помимо представления алфавитов,
многоязычности присущи и другие проблемы. Даже среди языков, использующих
латиницу, имеются свои тонкости. Так, в некоторых языках (к примеру, в испан­
ском) определенные комбинации двух символов при сортировке считаются од­
ним символом. Слова, начинающиеся с префиксов �ch� и �н�, должны следовать
после слов, начинающихся соответственно с префиксов �cz� и �1z� . Еще сложнее
проблема чувствительности к регистру. Если по умолчанию имена файлов чув­
ствительны к регистру, иногда может возникать необходимость в организации
нечувствительного к регистру поиска. Для языков на основе латиницы решение
проблемы очевидно, по крайней мере, их носителям. Если поддерживается только
один язык, правила очевидны, однако Unicode позволяет смешивать различные
языки. В многонациональной организации один и тот же каталог может содер­
жать имена файлов на греческом, русском и японском языках. В качестве реше­
ния проблемы в NТFS введен атрибут файла, определяющий соглашения о реги­
стре для языка, на котором написано его имя файла.
С помощью дополнительных атрибутов в NТFS решено много задач. Если в UNIX
файл представляет собой последовательность байтов, то в NТFS - коллекцию
атрибутов, где каждый атрибут является потоком байтов. Базовая структура дан­
ных NТFS - главная таблица файлов (Master File ТаЫе, MFT). Она поддержи­
вает 16 атрибутов, каждый из которых может иметь длину до 1 Кбайт. Если этого
недостаточно, атрибут можно использовать в качестве заголовка, указывающего
на дополнительный файл с расширенными значениями атрибута. Такой атрибут
называется нерезидентным. Сама таблица MFT представляет собой файл и со­
держит запись для каждого файла и каталога файловой системы. Поскольку ее
объем может значительно вырасти, при создании NТFS около 1 2,5 % пространст­
ва раздела резервируется под MFT. Благодаря резервированию MFT не фраг­
ментируется как минимум до тех пор, пока все зарезервированное пространст­
во не будет исчерпано. В последнем случае для MFT резервируется еще одна
5.3 . Реализация файловой системы 559

область. Таким образом, даже если таблица MFT фрагментирована, она состоит
из очень небольшого числа крупных блоков.
Как же в NTFS обстоит дело с данными? Данные попросту представляют собой
один из атрибутов файла. На самом деле, NТFS-файл может содержать несколь­
ко потоков данных. Изначально эта возможность позволяла Windows-cepвepaм
обслуживать файлы клиентов Apple Maclntosh. В исходной операционной систе­
ме Maclntosh (до Мае OS 9) все файлы имели два потока данных. Эти потоки
назывались <1:ветвь ресурсов• и <1:ветвь данных• . Множественные потоки данных
имеют и другие применения; например, для большого графического файла можно
хранить его уменьшенный эскиз. Максимальный объем потока составляет 264 бай­
та. В то же время система NTFS способна хранить содержимое небольших файлов
(до нескольких сотен байтов) в заголовке атрибута. Такие файлы называются
непосредственными [9 1 ) .
М ы лишь слегка затронули несколько подходов, позволяющих NTFS решать про­
блемы, не решенные более старыми и простыми файловыми системами. NTFS
также предоставляет и другие возможности: сложную систему защиты, шифро­
вание и сжатие данных. Их описание, как и описание их реализации, занимает
гораздо больше места, чем мы можем позволить себе в этой книге. Более деталь­
ное рассмотрение NTFS вы найдете в [ 1 1 5 ) . Кроме того, дополнительную инфор­
мацию можно поискать в Интернете.

5 . 3 . 4 . Организация дискового п ространства


Обычно файлы хранятся на диске, поэтому организация дискового пространства
является основной заботой разработчиков файловых систем. Для хранения фай­
ла из п байт можно использовать две стратегии: выделение на диске п последова­
тельных байтов или разбиение файла на несколько непрерывных блоков. Та же
дилемма характерна и для систем управления памятью, где имеется выбор меж­
ду <1:чистой• сегментацией и замещением страниц.
Как уже отмечалось, при хранении файла в виде непрерывной последовательно­
сти байтов возникает проблема, связанная с увеличением его размеров. Един­
ственный способ увеличить неразрывный файл состоит в перемещении его на
новое место на диске. Проблема существенна и для управления сегментами па­
мяти, с той разницей, что перемещение сегмента в памяти является более быст­
рой операцией по сравнению с перемещением файла на диске. По этой причине
почти все файловые системы хранят файлы в виде блоков фиксированного раз­
мера, но не обязательно смежных.

Размер блока
После принятия решения о хранении файлов блоками фиксированного размера
возникает вопрос о размере блоков. Учитывая организацию дисков, очевидными
кандидатами на роль блоков являются сектор, дорожка и цилиндр диска (недос­
татком такого выбора является зависимость этих параметров от устройств). В сис­
теме управления страницами памяти страницы также входят в число основных
560 Глава 5. Файловые системы

кандидатов. Если выбрать большую единицу хранения, такую как цилиндр, это
будет означать, что любой файл, даже состоящий из одного байта, займет как ми­
нимум целый цилиндр.
В то же время при маленьких единицах хранения каждый файл будет состоять
из большого числа блоков. Для чтения каждого блока файла обычно требуется
операция поиска нужного цилиндра и ожидание поворота диска, поэтому чтение
файла, состоящего из большого числа блоков, окажется медленным.
Например, представьте себе диск, в котором каждая дорожка содержит 131 072 байт
( 128 Кбайт), период вращения составляет 8,33 мс, а среднее время поиска - 1 0 мс.
При этом время, требующееся для чтения блока из k байт, равно сумме времен
поиска, поворота и переноса данных:
10 + 4 , 1 65 + (k/13 1 072) х 8,33.
Сплошная кривая на рис. 5 . 1 5 показывает зависимость скорости передачи дан­
ных от размера блока.

1 000 · - - - - - - - -+ - - - - - - - · - - - - - - - .... - - - - - - � 1 00 :.е.


о

Использование \
\ cli
�Ф � 800 дискового пространства \
\
\ 80 ф
111
!3
>S \ :s; :i::
� со :i:: со
,_ ' со а.
'8 � 600 ' 60 111
!3

.11 - '
'
t3 �:Jj ' 8.
о •, ' § i::

g- � 400 ,
,
40 i:: е
U o
:s: 111
с) � �
200 20 u
:s;
i::[

O L_,�
�d::==�====I...�--1.��J_�_L��l___J O
о 128 256
1К 2К 51 2
4К 8К 1 6К О
Размер блока, в байтах
Рис. 5 . 1 5 . Зависимость скорости чтения/записи данных диска (сплошная линия, левая
шкала) и эффективности использования дискового пространства (штриховая линия,
правая шкала) от размера блоков. Все файлы по 2 Кбайт
Чтобы вычислить эффективность использования дискового пространства, нам
необходимо сделать предположение о среднем размере файла. Одно из старых
исследований показало, что средний размер файла в системе UNIX составляет
около 1 Кбайт [9 1 ) . В 2005 году в отделе, где работает один из авторов, был про­
изведен подсчет для более чем 1 млн дисковых UNIX -файлов и 1 ООО пользова­
телей. Медианный размер составил 2475 байт; это означает, что половина фай­
лов имеет меньший размер, а половина - больший. Медианный размер является
лучшим показателем, чем средний, поскольку на среднее значение могут оказы­
вать влияние всего лишь несколько файлов (например, руководства для аппа­
ратных устройств размером по 100 Мбайт или демонстрационные видеоролики),
а на медианное - нет.
5.3. Реал изация файловой системы 561

В [ 1 25) показано, что в Windows NT работа с файлами происходит значительно


сложнее, чем в UNIX. Вот короткий фрагмент из этого издания:
Когда мы вводим несколько символов в текстовом редакторе Блокнот (Note­
pad ), сохранение их в файле приводит к 26 системным вызовам, включая 3 не­
удачные попытки открыть файл, одну перезаписать файл и 4 дополнительных
последовательности открытия и закрытия файла.
Тем не менее в [ 1 25) приводятся следующие медианные размеры файлов (взве­
шенные): только прочитанные файлы - 1 Кбайт, только записанные файлы -
2,3 Кбайт, прочитанные и записанные файлы - 4,2 Кбайт. Учитывая различия
в технике измерений (статическая и динамическая), результаты достаточно близ­
ки к 2-килобайтному медианному размеру файла.
Для простоты предположим, что все файлы имеют размер 2 Кбайт. В этом слу­
чае эффективность использования дискового пространства изображается пунк­
тирной кривой на рис. 5 . 1 5.
Проанализируем две полученные кривые. Время доступа к блоку полностью за­
висит от времен поиска и поворота; если известно, что для доступа к блоку тре­
буется 14 мс, то чем больше данных будет считано из блока, тем лучше. Таким
образом, скорость обмена данными растет с ростом размера блока до тех пор, пока
время передачи не начинает преобладать. Если блоки имеют небольшой размер,
являющийся степенью двойки, а файлы занимают 2 Кбайт, потерь дискового про­
странства не происходит. Однако если размер блока составляет 4 Кбайт, часть
памяти диска тратится впустую. На практике размер файла очень редко кратен
размеру блока, поэтому в последнем блоке файла всегда теряется часть места.
Кривые показывают, что эффективность использования диска и производитель­
ность находятся в обратной зависимости. Маленькие блоки экономят дисковое
пространство, но снижают производительность. Необходимо выбрать компромисс­
ный размер блока. Для рассматриваемого случая хорошим решением является
значение 4 Кбайт, однако в некоторых операционных системах выбор делался
тогда, когда параметры дисков и размеры файлов были иными. Размер блока
в MS-DOS может быть любой степенью двойки от 5 1 2 байт до 32 Кбайт, однако
по другой причине зависит от объема диска: максимальное число блоков диско­
вого раздела составляет 2 1 6 , что заставляет использовать большие блоки на дис­
ках большого объема.

Учет свободных блоков


После того как мы выбрали размер блоков, следует определиться, как учитывать
свободные и занятые блоки. Широкое распространение получили два метода,
представленные на рис. 5. 1 6. Первый метод - не что иное, как использование
связанного списка блоков диска. При этом в каждом блоке списка содержится
столько номеров свободных блоков, сколько может поместиться в один блок. При
размере блока, равном 1 Кбайт, и 32-разрядных номерах блоков каждый блок
списка свободных блоков может содержать номера 255 свободных блоков. ( Од­
но 32-разрядное слово нужно для указателя на следующий блок списка. ) Для
256-гигабайтного диска потребуется список свободных блоков, состоящий из
562 Глава 5. Файловые системы

1 052 689 блоков, чтобы охватить все 2 2 8 дисковых блока. Часто список свобод­
ных блоков хранится в самих свободных блоках.

Свободные дисковые блоки 1 6, 1 7, 18


42 � 230 � 86 1 0011 01 1 011 011 00
1 36 1 62 234 011 01 1 011111 0111
21 0 612 897 1 01 01 1 011 011 01 1 0
97 342 422 011 01 1 011 0111 011
41 214 140 111 0111 0111 01 1 1 1
63 1 60 223 1 1 01 1 01 01 0001111
21 664 223 0000111 011 01 0111
48 21 6 1 60 1 0111 01 1 011 01111

( 262 320 1 26 1 1 001 000111 01111


, :� :� :� :� :�

31 0 180 142 0111 0111 0111 0111


516 _/ 482 _/ 141 1 1 011111 0111 0111
Дисковый блок Битовый массив
размером 1 Кбайт
содержит 256 32-битных
номеров дисковых блоков
а б
Рис. 5 . 1 6. Учет свободных блоков: хранение информации о свободных блоках в виде
а -
связанного списка; б битовая карта
-

Другой метод учета свободного дискового пространства заключается в хранении


этой информации в виде битового массива (битовой карты). Здесь на каждый
блок приходится всего по одному биту. Свободные блоки обозначаются в массиве
единицами, а занятые - нулями (или наоборот). 256-гигабайтный диск состоит
из 2 28 1 -килобайтных блоков, таким образом, для него требуется массив размером
228 бит, то есть 32 768 блоков. Нет ничего удивительного в том, что битовая карта
требует меньше пространства, поскольку в ней одному блоку соответствует 1 бит,
а не 32, как в модели со связанным списком. Схема на основе списка более эффек­
тивна только тогда, когда диск практически полон (то есть свободных блоков
очень мало). В то же время, если число свободных блоков велико, то часть из них
можно отвести под хранение списка без ощутимых потерь дискового пространства.
При использовании списка свободных блоков достаточно хранить в памяти все­
го один блок указателей. Когда создается файл, нужные блоки извлекаются из
блока указателей. Когда текущий блок заканчивается, следующий блок считыва­
ется с диска. Аналогично, при удалении файла его блоки освобождаются и до­
бавляются к блоку указателей в основной памяти. Заполненный блок сбрасыва­
ется на диск.
5.3. Реализаци я файловой системы 563

5 . 3 . 5 . Н адежность файловой системы


Разрушение файловой системы часто приносит больше бед, чем поломка компь­
ютера. Если компьютер приходит в негодность вследствие пожара, удара молнии
или пролитой на клавиатуру чашки кофе, это неприятно, ремонт требует денег,
но обычно не причиняет много хлопот. Недорогие персональные компьютеры
можно заменить в течение часа, обратившись в ближайший компьютерный мага­
зин. (Исключение составляют университеты, где для приобретения персональ­
ных компьютеров требуется согласование этого вопроса в трех инстанциях, по­
лучение пяти подписей и 90 дней ожидания. )
В случае ж е краха файловой системы п о вине аппаратного или программного
обеспечения (либо крыс, посчитавших, что одного отверстия на гибком диске
недостаточно), восстановление всей информации оказывается делом трудным,
длительным, а часто и невыполнимым. Для пользователей, чьи программы, доку­
менты, файлы клиентов, счета, базы данных, маркетинговые планы или другие
данные теряются навсегда, последствия могут оказаться катастрофическими.
Хотя операционная система не в силах защитить от физического уничтоже­
ния оборудование или носитель, она в состоянии помочь сберечь информацию.
В данном разделе мы рассмотрим некоторые вопросы, касающиеся защиты фай­
ловой системы от уничтожения.
Когда гибкие диски покидают фабрику, их качество, как правило, превосходно,
но со временем на них могут появиться дефектные блоки. Можно утверждать,
что сегодня это более вероятно, чем во времена более широкого распространения
дискет. Благодаря сетям и съемным устройствам большой емкости, таким как
компакт-диски с возможностью перезаписи, дискеты стали использовать редко.
Кулеры вместе с воздухом гонят в дисководы пыль, и если дисковод не исполь­
зуется в течение долгого времени, попытка поработать с гибким диском может
привести к его поломке. Вероятность повреждения диска часто используемым
дисководом меньше.
У жестких дисков дефектные блоки часто бывают врожденными, поскольку про­
изводство жестких дисков абсолютно без дефектов обходится чересчур дорого.
Как мы видели в главе 3, дефектные блоки обычно заменяются исправными при
помощи контроллера. У таких дисков каждая дорожка имеет как минимум один
лишний сектор, поэтому как минимум один поврежденный сектор можно про­
пустить, создав зазор между двумя последовательными секторами. На каждом
цилиндре имеется несколько запасных секторов, что позволяет контроллеру ав­
томатически переназначать секторы, если чтение или запись какого-либо секто­
ра требует больше установленного числа попыток. Таким образом, пользователь
обычно ничего не подозревает о поврежденных блоках и их учете. Однако если
происходит отказ современного I D E - или S С S I -диска, обычно это - признак
серьезной проблемы, когда у диска заканчиваются запасные секторы. SСSI-диски,
заменяя сбойный блок запасным, сообщают о восстановлении после сбоя ( гecovered
error). Заметив это, драйвер может вывести на экран сообщение. Если такие со­
общения появляются на экране часто, пришло время менять диски.
564 Глава 5. Файловые системы

Существует простое и элегантное программное решение проблемы сбойных бло­


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

Резервные копии
Большинство пользователей считают создание резервных копий файлов просто
потерей времени. Однако когда в один прекрасный день диск внезапно отказы­
вается работать, они диаметрально меняют свои привычки. Компании, напротив,
обычно хорошо осознают ценность своей информации и выполняют резервное
копирование один раз в сутки, чаще всего на магнитную ленту. Современные
магнитные ленты вмещают десятки и иногда даже сотни гигабайтов при стоимо­
сти в несколько центов за гигабайт. Однако создание резервных копий является
далеко не столь тривиальным делом, как это может показаться, поэтому мы кос­
немся некоторых аспектов данной темы.
Как правило, резервирование на магнитную ленту осуществляется для решения
одной из двух потенциальных проблем: восстановления после аварии и вос­
становления после глупой ошибки. В первом случае требуется вернуть компью­
тер в работоспособное состояние после отказа диска, пожара, потопа или другой
природной катастрофы. На практике такие вещи происходят довольно редко,
и именно поэтому многие пользователи не заботятся о резервировании. Как
правило, в силу той же причины они не страхуют свое имущество от пожара.
Второй случай наступает тогда, когда пользователи случайно удаляют нужные
им файлы. Это происходит настолько часто, что разработчики Windows придума­
ли специальный каталог, называемый корзиной (гесусlе Ьin), куда на самом деле
перемещается удаляемый файл и где он впоследствии может быть легко обнаружен
и восстановлен. Резервные копии являются развитием этого принципа и позво­
ляют восстанавливать файлы, удаленные несколько дней или даже недель назад.
Создание резервной копии отнимает много времени и требует большого про­
странства, поэтому важно, чтобы этот процесс был эффективным и удобным.
Возникает ряд вопросов, и первый из них - следует резервировать файловую
систему целиком или лишь ее часть? Зачастую исполняемые (двоичные) про­
граммы содержатся в ограниченной части дерева файловой системы; резервиро­
вать их не обязательно, поскольку такие файлы можно восстановить с компакт­
дисков производителей. Кроме того, большинство систем имеют каталог времен­
ных файлов, и резервировать его также не имеет смысла. В UNIX все специаль­
ные файлы (устройства ввода-вывода) содержатся в каталоге / dev / . Резерви­
рование этого каталога не только не обязательно, а попросту опасно, поскольку
программа резервирования при попытке считывания файлов зависнет. Короче
говоря, чаще всего предпочтительнее создавать резервные копии содержимого
отдельных каталогов, нежели копировать всю файловую систему.
5.3 . Реализация файловой системы 565

Во-вторых, резервировать файлы, которые не менялись с момента предыдущего


резервирования, бессмысленно. Это приводит к идее инкрементных резервных
копий. Простейшая форма инкрементной архивации состоит в том, что полная
резервная копия создается, скажем, раз в неделю или раз в месяц, а ежедневно
сохраняются только те файлы, которые изменились с момента последней полной
архивации. Еще лучше архивировать только те файлы, которые изменились со
времени последней инкрементной архивации. Несмотря на то что подобная схема
минимизирует время резервирования, процесс восстановления усложняется, по­
скольку сначала требуется восстановить самую свежую архивную копию, а затем
все остальные копии в обратном порядке. С целью упрощения восстановления,
как правило, используются более сложные методы инкрементного резервирования.
В-третьих, объемы резервируемых данных, чаще всего, огромны, а, следователь­
но, перед записью на магнитную ленту данные предпочтительно сжать. Однако
для большинства алгоритмов сжатия единственное повреждение на ленте делает
восстановление невозможным и не позволяет считать содержимое файла или всей
ленты. Таким образом, решение о сжатии резервной копии следует принимать
с осторожностью.
В-четвертых, трудно создать резервную копию работающей файловой системы.
В процессе резервирования файлы и каталоги создаются, удаляются и изменя­
ются, что может привести к некорректности резервной копии. Поскольку архи­
вация длится до нескольких часов, систему необходимо освободить от работы на
большую часть ночи, что не всегда приемлемо. По этой причине бьmи разработа­
ны алгоритмы, делающие <1:моментальный снимок• состояния файловой системы
путем копирования наиболее важных структур данных. После этого изменения,
вносимые в файлы и каталоги, приводят к копированию блоков, а не их обновле­
нию [63]. Таким образом, при создании <1:моментального снимка• файловая систе­
ма <1:замораживается•, и ее архивацию можно без проблем выполнить в будущем.
Наконец, в-пятых, резервные копии являются для организаций источниками про­
блем нетехнического характера. Лучшая в мире интерактивная система безопасно­
сти может оказаться бесполезной, если системный администратор держит архив­
ные магнитные ленты в своем офисе и не закрывает его, выходя на пару минут,
чтобы забрать документы с корпоративного принтера. Все, что нужно сделать
злоумышленнику, - зайти на секунду, положить одну крохотную ленту себе в кар­
ман и с беспечным видом отправиться дальше. Прощай, безопасность! Кроме то­
го, ежедневная архивация файлов бесполезна, если пожар, который уничтожит
компьютеры, уничтожит и все магнитные ленты с резервными копиями. Таким
образом, архивные ленты следует хранить в другом месте, хотя это и более риско­
ванно с точки зрения безопасности. Подробно эти и другие вопросы практическо­
го администрирования рассмотрены в [93 ] . Далее мы займемся изучением лишь
технических аспектов, связанных с резервированием файловой системы.
Существует две стратегии резервирования диска на магнитную ленту: получе­
ние физического или логического дампа. При получении физическою дампа ко­
пирование начинается с нулевого блока диска, все блоки записываются на ленту
в порядке следования, а заканчивается дамп последним блоком диска. Программа
566 Глава 5. Файловые системы

получения физического дампа настолько проста, что ее можно написать абсолют­


но безошибочно, что практически недостижимо для других полезных программ.
Тем не менее относительно получения физического дампа следует сделать не­
сколько комментариев. Во-первых, копирование неиспользуемых дисковых бло­
ков не имеет смысла. Этого можно избежать, если программа получения дампа
имеет доступ к структуре данных, описывающей свободные блоки. Однако в слу­
чае, если свободные блоки пропускаются, перед каждым архивируемым блоком
нужно помещать его номер, так как теперь блок k на ленте не обязательно явля­
ется блоком k на диске.
Вторая проблема - резервирование дефектных блоков. Если все такие блоки
заменены контроллером диска и скрыты от операционной системы так, как опи­
сано в пункте 5.4.4, программа получения физического дампа работает хорошо.
Однако, если поврежденные блоки видны операционной системе и перечислены
в одном или нескольких файлах или битовых массивах, было бы совершенно
логично, если бы программа получения физического дампа имела доступ к этой
информации, чтобы избежать копирования дефектных блоков. В противном слу­
чае программа зависнет из-за бесконечных попыток чтения с диска.
Основными преимуществами получения физического дампа являются простота
и высокая скорость: резервирование может выполняться со скоростью работы
диска. Главные недостатки - невозможность пропуска выбранных каталогов,
создания инкрементных дампов и восстановления отдельных файлов по запросу.
В силу этих причин чаще используются логические дампы.
Программа получения логического дампа начинает копирование с одного или не­
скольких указанных каталогов и рекурсивно копирует все находящиеся в них
файлы и каталоги, изменившиеся с заданной базовой даты (например, с послед­
ней архивации для инкрементной резервной копии или со времени установки
системы в случае полной резервной копии). Таким образом, в логическом дампе
присутствует последовательность тщательно идентифицируемых каталогов и фай­
лов, что позволяет без труда восстановить нужный файл или каталог по запросу.
Для того чтобы корректно восстановить даже один файл, на архивный носитель
необходимо записать всю информацию его пути. Таким образом, первый шаг по­
лучения логического дампа - анализ дерева каталогов. Очевидно, что архивации
подлежат все модифицированные файлы и каталоги. Однако для правильного
восстановления требуется сохранить все каталоги, находящиеся на пути к изменен­
ному файлу, даже если они не менялись. Это означает, что необходимо сохранить
не только данные (имена файлов и указателей на индексные узлы), но и атрибу­
ты всех каталогов, чтобы восстановить их с соответствующими разрешениями.
Сначала на ленту записываются каталоги и их атрибуты, а затем - модифици­
рованные файлы также со своими атрибутами. Это позволяет восстановить ар­
хивированные файлы и каталоги в качестве новой файловой системы другого
компьютера. Таким образом, дамп и программы восстановления дают возмож­
ность межмашинного переноса целых файловых систем.
Вторая причина получения дампа немодифицированных каталогов, находящихся
на пути к измененным файлам, - возможность инкрементного восстановления
5.3. Реализаци я файловой системы 567

отдельного файла (возможно, после его случайного удаления). Предположим, что


полный дамп файловой системы создан вечером в воскресенье, а инкрементный
дамп - вечером в понедельник. Во вторник каталог /usr / j hs / pro j / nrЗ / удали­
ли вместе со всеми вложенными файлами и каталогами. Утром в среду пользова­
тель хочет на свежую голову восстановить файл / u s r / j hs / p r o j / nr З / p l ans /
summary, однако это невозможно, поскольку его некуда поместить. Сначала тре­
буется восстановить каталоги nrЗ / и p l an s / . То есть нужно получить коррект­
ную информацию об их владельцах и режимах, временн Ые данные и т. д. А для
этого они должны присутствовать на архивной магнитной ленте, даже если не
бьmи изменены со времени получения полного дампа.
Восстановить файловую систему с архивной магнитной ленты несложно. Снача­
ла на диске создается пустая файловая система, а затем в нее восстанавливается
самый свежий полный дамп. Поскольку каталоги расположены в начале ленты,
они восстанавливаются первыми, формируя каркас файловой системы. Затем вос­
станавливаются сами файлы. Этот процесс повторяется сначала для первого ин­
крементного дампа, созданного после полного дампа, затем - для второго и т. д.
Хотя в получении логического дампа нет ничего сложного, без нескольких тон­
костей не обойтись. Во-первых, список свободных блоков не является файлом,
а следовательно, не резервируется. Таким образом, его потребуется воссоздать
�с нуляэ- после того, как все lJ;ампы восстановлены. Это всегда возможно, так как
список свободных блоков - всего лишь дополнение по отношению ко всей сово­
купности блоков, занятых файлами.
Отдельный аспект касается связей. Если файл связан с двумя или более катало­
гами, важно, чтобы его восстановление было однократным, а все каталоги, с ко­
торыми он связан, указывали на него.
Еще одна проблема заключается в том, что файлы в системе UNIX могут содер­
жать дыры. Вполне допустимо сначала открыть файл и записать в него несколь­
ко байтов, а затем совершить дальний переход и записать еще несколько байтов.
Блоки, расположенные между двумя последовательностями байтов, не являются
частью файла, а значит, их не нужно ни резервировать, ни восстанавливать.
Файлы дампов памяти зачастую имеют большие дыры между сегментами дан­
ных и стека. Если не обеспечить надлежащую обработку таких файлов, при вос­
становлении пустая область будет заполнена нулями, а размер восстановленного
файла дампа оказывается равным размеру виртуального адресного пространства
(2 32 , а может быть, и 2 64 байт).
Наконец, специальные файлы, именованные каналы и тому подобное не должно
записываться в дамп, независимо от того, в каком каталоге все это находится
(специальные файлы не обязательно расположены в каталоге / dev / ). Дополни­
тельную информацию об архивировании файловой системы вы найдете в [ 19, 132].

Н епротиворечивость файловой системы


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

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


заться в противоречивом состоянии. Эта проблема становится особенно важной
в случае, если одним из модифицированных и не сохраненных блоков оказыва­
ется блок индексного узла, каталога или списка свободных блоков.
Для обеспечения непротиворечивости файловой системы большинство компью­
теров оснащаются спец;иальной обслуживающей программой, проверяющей
состояние файловой системы. Например, в UNIX используется утилита f s c k,
в Windows - chkdsk (в ранних версиях - s c andi s k). Данную программу мож­
но запустить при загрузке системы, обычно после аварии. В приведенном далее
описании рассмотрена работа утилиты f s c k. Несколько отличается от нее утилита
chkdsk, поскольку предназначена для другой файловой системы, однако прин­
цип избыточности при восстановлении остается общим. Все программы проверки
обрабатывают каждую файловую систему (раздел диска) независимо от других.
Обычно контролируется непротиворечивость объектов двух типов: блоков и фай­
лов. При проверке непротиворечивости блоков программа создает две таблицы,
каждая из которых содержит счетчик для каждого блока, изначально установ­
ленный в О. Счетчики в первой таблице фиксируют, в каком количестве каждый
блок присутствует в файле. Счетчики во второй таблице показывает, сколько раз
каждый блок учитывается в списке (или в битовой карте) свободных блоков.
Затем программа считывает все индексные узлы. Начиная с индексного узла,
можно построить список всех номеров блоков, занятых соответствующим фай­
лом. При считывании каждого номера блока счетчик этого блока увеличивается
на единицу. Затем программа анализирует список или битовую карту свободных
блоков, чтобы обнаружить все неиспользуемые блоки. Каждый раз, встречая но­
мер блока в списке свободных блоков, программа инкрементирует соответствую­
щий счетчик во второй таблице.
Если файловая система непротиворечива, каждый блок будет встречаться только
один раз либо в первой, либо во второй таблице (рис. 5 . 1 7 , а). Однако в резуль­
тате сбоя эти таблицы могут принять вид, соответствующий рис. 5 . 1 7 , б. В этом
случае блок 2 отсутствует в каждой таблице. О таком блоке программа сообщит
как о недостающем. Хотя недостающие блоки не причиняют вреда, они зани­
мают место на диске, снижая его емкость. Учесть же недостающие блоки очень
просто: программа проверки файловой системы просто добавляет их к списку
свободных.
Другая возможная ситуация показана на рис. 5. 17, в. Здесь мы видим блок но­
мер 4, дважды появляющийся в списке свободных. (Дубликаты свободных блоков
могут появиться лишь тогда, когда в файловой системе реально используются
списки свободных блоков; в случае битовой карты это невозможно. ) Решить
проблему также несложно - надо построить список свободных блоков заново.
Гораздо хуже, если один и тот же блок оказывается сразу в двух файлах, как
показано на рис. 5 . 1 7 , z для блока 5. При удалении любого из этих файлов блок 5
окажется в списке свободных блоков, что приведет к ситуации, в которой один
и тот же блок одновременно является и свободным, и занятым. Если удалить оба
файла, блок будет помещен в список свободных блоков дважды.
5 . 3 . Реали з ация файловой системы 569

Номер блока
о 1 2 3 4 5 6 7 8 9 1 0 11 121 3141 5
Занятые
l 1 l 1 l o l 1 l o l 1 l 1 l 1 l 1 l o l o l 1 l 1 l 1 l o l o l блоки
Свободные
l o l o l 1 l o l 1 l o l o l o l o l 1 l 1 l o l o l o l 1 l 1 I блоки
а

Номер блока
о 1 2 3 4 5 6 7 8 9 1 0 11 1 21 3141 5
Занятые
l 1 l 1 l o l 1 l o l 1 l 1 l 1 l 1 l o l o l 1 l 1 l 1 l o l o l блоки
Свободные
l o l o l o l o l 1 l o l 0 J o l o l 1 l 1 l o l o l o l 1 l 1 I блоки
б

о 1 2 3 4 5 6 7 8 9 1 0 1 1 121 3141 5
Занятые
1l l 1 l o l 1 l o l 1 l 1 l 1 l 1 l o l o l 1 l 1 l 1 l o l o J блоки
Свободные
l o l o l 1 l o l 2 l o l o l o l o l 1 l 1 l o l o l o l 1 l 1 I блоки
в

о 1 2 3 4 5 6 7 8 9 1 0 11 121 3141 5

l 1 l 1 l o l 1 l o l 2 l 1 l 1 l 1 l o l o l 1 l 1 l 1 l o l o l Занятые
блоки
l o l o l 1 l o l 1 l o l o l o l o l 1 l 1 l o l o l o l 1 l 1 I Свободные
блоки
г

Рис. 5 . 1 7 . Состояния файловой системы: непротиворечивое; б недостающий блок;


а - -

в - дубликат блока в списке свободных блоков; дубликат блока данных


г -

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


ный блок, скопировать в него содержимое блока 5 и вставить эту копию в один
из файлов. Таким образом, содержимое файлов останется неизменным (хотя
почти наверняка один из файлов уже испорчен), но, по крайней мере, струк­
тура фай л овой системы после этой операции становится непротиворечивой.
Программа также должна выдать сообщение об ошибке, чтобы пользователь мог
изучить проблему.
Помимо контроля принадлежности блоков, программа проверки также анализи­
рует структуру каталогов. Для этого используется таблица счетчиков, но уже не
для блоков, а для файлов. Проверка начинается с корневого каталога с рекурсив­
ным заходом в каждый каталог. Для каждого файла в каждом каталоге програм­
ма увеличивает на единицу счетчик использования файла. Благодаря жестким
связям файл может присутствовать сразу в нескольких каталогах. Символьные
связи не учитываются и не оказывают влияния на счетчик.
570 Глава 5. Файловые системы

Когда сканирование дерева каталогов завершается, программа получает список,


индексированный по номерам индексных узлов, с информацией о том, в сколь­
ких каталогах присутствует каждый файл. Затем программа сравнивает полу­
ченные числа со счетчиками связей, хранящимися в самих индексных узлах. Эти
счетчики содержат единицу при создании файла и инкрементируются всякий
раз, когда создается связь (жесткая) с данным файлом. В непротиворечивой
файловой системе оба счетчика должны совпадать. Однако возможны два типа
ошибок: значение счетчика связи в индексном узле может оказаться слишком
велико или слишком мало.
Если счетчик связи больше, чем количество записей в каталоге, тогда даже при
удалении всех файлов из каталогов счетчик все равно не уменьшится до нуля,
и индексный узел удален не будет. Эта ошибка несерьезная, но она приводит
к расходованию дискового пространства файлом, находящимся вне всех катало­
гов. Чтобы исправить ее, следует установить значение счетчика равным числу
существующих записей каталога.
Вторая ошибка таит в себе катастрофические последствия. Если у файла есть
две связанные с ним записи каталогов, согласно информации индексного узла
такая запись только одна, тогда при удалении записи каталогов этого файла в лю­
бом каталоге счетчик индексного узла уменьшится до нуля. При этом файловая
система освободит все блоки, занимаемые файлом, в том числе и блок, в котором
помещается сам индексный узел. Таким образом, в одном из каталогов сохра­
нится дескриптор файла, указывающий на неиспользуемый индексный узел, чьи
блоки могут быть вскоре выделены другим файлам. Решение здесь также заклю­
чается в присваивании счетчику связей индексного узла фактического количест­
ва записей каталога.
Часто эти две операции, проверки блоков и проверки каталогов, для увеличения
эффективности выполняют за один проход. Возможно также проведение и других
проверок. Например, формат каталогов должен соответствовать определенным
требованиям относительно индексных узлов и АSСП-имен. Если номер индекс­
ного узла оказывается больше числа индексных узлов на диске, это означает, что
каталог поврежден.
Более того, у каждого индексного узла могут оказаться значения режима досту­
па, являющиеся допустимыми, но странными, например 0007. Такое значение
совсем отказывает в доступе владельцу и его группе, но зато разрешает всем по­
сторонним читать, писать и исполнять файл. Программа должна хотя бы сооб­
щать обо всех файлах, предоставляющих сторонним пользователям больше прав,
чем владельцам. Каталоги, содержащие, скажем, более 1000 записей, также подоз­
рительны. Расположенные в каталогах пользователей файлы, владельцем которых
является суперпользователь и у которых установлен бит SETU I D, представляют
собой потенциальную угрозу безопасности, так как такие файлы при запуске
любым пользователем дают полномочия суперпользователя. Список технически
возможных, но необычных ситуаций, о которых программа должна информиро­
вать, можно продолжать довольно долго.
5 .3 . Реализация файловой системы 57 1

До сих пор мы обсуждали проблему защиты пользователя от сбоев. Некоторые


файловые системы также пытаются защитить пользователя от самого себя. На­
пример, пусть пользователь решает удалить все файлы с расширением о (создан­
ные компилятором объектные файлы) и вводит такую команду:
rm * . о

Однако случайно вместо этого вводит следующее (обратите внимание на пробел


после звездочки):
rm * .о

Тогда программа rrn удалит все файлы в текущем каталоге, после чего сообщит,
что не может найти файл с расширением о. В системе MS-D O S и некоторых дру­
гих системах при удалении файла устанавливается всего лишь один бит в ката­
логе или индексном узле, отмечая, что файл удален. Блоки диска не возвращаются
в список свободных блоков до тех пор, пока они не понадобятся. Таким образом,
если пользователь быстро обнаружит ошибку, он сможет восстановить удален­
ные файлы. В Windows удаленные файлы обычно помещаются в корзину, откуда
их можно при необходимости извлечь. При этом свободное пространство на дис­
ке не увеличивается до тех пор, пока корзина не будет очищена.
Подобные механизмы не являются безопасными. В безопасной системе при уда­
лении с диска блоки данных заполняются нулями или случайными числами,
чтобы не дать возможность другому пользователю восстановить информацию.
Многие пользователи даже не подозревают, насколько �живучими• могут быть
данные. Конфиденциальные или важные данные зачастую удается восстановить
даже с утилизированных дисков [48] .

5 . 3 . 6 . П роизводител ьность файловой системы


Доступ к диску значительно медленнее, чем к оперативной памяти. Чтение слова
из памяти может занять около 10 нс. Чтение с жесткого диска может выполнять­
ся со скоростью 10 Мбайт/с, что в сорок раз медленнее для 32-разрядного слова,
но к этому следует добавить 5-10 мс на поиск нужного цилиндра и поворот диска.
Если требуется прочитать или записать всего одно слово, то оперативная память
оказывается примерно в миллион раз быстрее жесткого диска. Поэтому во многих
файловых системах применяются различные методы оптимизации, повышающие
производительность. В данном разделе мы рассмотрим три из них.

Кэширование
Для минимизации количества обращений к диску применяется блочный, или бу­
ферный, кэш. (Термин �кэш• происходит от французского слова cacher, что зна­
чит �скрывать•.) В данном контексте кэшем называется набор блоков, логически
принадлежащих диску, но хранящихся в оперативной памяти по соображениям
производительности.
Существуют различные алгоритмы кэширования. Обычная практика заключается
в перехвате всех запросов чтения к диску и поиске требующихся блоков в кэше.
Если блок присутствует в кэше, то запрос чтения блока может быть удовлетворен
572 Глава 5. Файловые систем ы

без обращения к диску. В противном случае блок сначала считывается с диска


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

Хеш-таблица Конец (MRU)

Рис. 5. 1 8. Структуры данных буферного кэша


Когда требуется загрузить блок в заполненный до предела кэш, какой-либо дру­
гой блок должен быть из него удален (и записан на диск, если он был модифици­
рован в кэше ). Эта ситуация очень похожа на замещение страниц, и к ней приме­
нимы все обычные для этого алгоритмы, описанные в главе 4, такие как FIFO,
LRU, второго шанса и т. д. Одно приятное отличие кэширования от страничной
организации памяти состоит в том, что обращения к кэшу производятся относи­
тельно нечасто, что позволяет хранить все блоки в точном LRU-порядке с одно­
направленными списками.
На рис. 5. 18 мы видим, что в дополнение к цепочке соответствий, начинающейся
от хеш-таблицы, имеется двунаправленный список всех блоков в порядке ис­
пользования. Блок с наиболее давним обслуживанием находится во главе списка,
а последний использованный блок - в его конце. При обращении к блоку он
удаляется из текущей позиции в списке и перемещается в его конец. Таким обра­
зом обеспечивается точное соответствие LRU-порядку.
К сожалению, здесь есть одна загвоздка. Хотя мы можем реализ овать точное соблю­
дение алгоритма LRU, оказывается, что алгоритм LRU является нежелательным.
Вызвано это тем, что его буквальное применение снижает надежность файловой
системы и угрожает ее непротиворечивости (обсуждавшейся в предыдущем разде­
ле). Если критически важный блок, например блок индексного узла, считывается
в кэш и далее модифицируется, но не записывается сразу же на диск, то компью­
терный сбой может привести к тому, что файловая система окажется в некор­
ректном состоянии. Если блок индексного узла поместить в конец LRU-цепочки,
может пройти довольно много времени, прежде чем этот блок попадет в ее нача­
ло и будет записан на диск.
5 . 3 . Реализация файловой системы 573

Более того, к некоторым блокам, таким как блоки индексных узлов, программы
редко обращаются дважды в течение короткого интервала времени. Исходя из
этих соображений, мы приходим к модифицированной схеме LRU, принимая во
внимание два вопроса.
1. Насколько велика вероятность того, что данный блок скоро снова понадобится?
2. Важен ли данный блок для непротиворечивости файловой системы?
Для ответа на каждый из поставленных вопросов блоки можно разделить на ка­
тегории, такие как блоки индексных узлов, �косвенные• блоки (блоки косвен­
ной адресации), блоки каталогов, блоки, целиком заполненные данными, и бло­
ки, частично заполненные данными. Блоки, которые, вероятно, не потребуются
снова в ближайшее время, помещаются в начало LR U-списка, чтобы занимаемые
ими буферы могли вскоре освободиться. Блоки, вероятность повторного исполь­
зования которых в ближайшее время высока (например, частично заполненные
записываемые блоки), заносятся в конец LRU-списка, что позволяет им дольше
оставаться в кэше.
Второй вопрос не связан с первым. Если блок представляет важность для непро­
тиворечивости файловой системы (обычно это все блоки, кроме блоков данных)
и такой блок модифицируется, то его следует немедленно сохранить на диске,
независимо от его положения в LRU-списке. Своевременно записывая критиче­
ски важные блоки, мы значительно снижаем вероятность того, что сбой компью­
тера повредит файловую систему. Пользователь вряд ли будет рад потере одного
из своих файлов из-за какого-то сбоя. Еще сильнее он огорчится, если при этом
испорченной окажется вся файловая система.
Даже при принятии всех перечисленных мер предосторожности по поддержанию
в рабочем состоянии файловой системы слишком долгое хранение в кэше блоков
с данными является нежелательным. Представьте себе автора будущей книги,
подготавливаемой на персональном компьютере. Даже если наш писатель перио­
дически велит текстовому редактору сохранять редактируемый файл на диске,
есть большая вероятность, что все блоки останутся в кэше. Если произойдет сбой,
структура файловой системы не пострадает, но труд целого дня будет потерян.
Эта ситуация случается не слишком часто, и если случается, то только с очень
невезучими пользователями. Для решения данной проблемы обычно применяет­
ся два метода. В системе UNIX есть вызов sync , принуждающий сохранить все
модифицированные блоки кэша на диске. При загрузке операционной системы
запускается фоновая программа, обычно под названием upda t e, вся работа
которой заключается в периодическом (обычно через каждые 30 с) обращении
к системному вызову sync . В результате при любом сбое будет потеряно не бо­
лее полминуты работы.
В Windows практикуется другой подход, состоящий в том, что каждый модифи­
цированный блок записывается на диск сразу же. Кэш, в котором все модифици­
рованные блоки немедленно записываются на диск, называется сквозным кэшем ,
или кэшем со сквозной записью. При использовании сквозного кэша количество
обращений ввода-вывода к диску больше, чем при применении обычного кэша.
Чтобы лучше понять разницу в этих двух подходах, представьте себе программу,
574 Глава 5. Файловые системы

сохраняющую блок размером в 1 Кбайт по одному символу. Система UNIX будет


собирать все символы в кэше и записывать этот блок на диск каждые 30 с, а так­
же при удалении блока из кэша. Windows будет обращаться к диску для каждого
символа. Конечно, в большинстве программ применяется внутренняя буфериза­
ция, поэтому обычно эти программы обращаются к системному вызову wr i t е не
с одним символом, а с целыми строками или большими единицами данных.
Результатом различия стратегий кэширования оказывается то, что простое удале­
ние (гибкого) диска из системы UNIX без выполнения системного вызова sync
почти всегда ведет к потере данных и часто также к повреждению файловой сис­
темы. В Windows такой проблемы не возникает. Подобное различие в стратегиях
связано с тем, что система UNIX разрабатывалась в среде, в которой все диски
были жесткими и постоянными, тогда как система Windows изначально предна­
значалась для работы со сменными носителями. Когда жесткие диски стали нор­
мой, более эффективный метод, присущий UNIX, также стал нормой и теперь
используется в Windows для жестких дисков.

Упреждающее чтение блоков


Второй метод повышения производительности файловой системы - помещение
блоков в кэш до того, как они оказываются нужными. Это увеличивает процент
кэш-попаданий. В частности, многие файлы считываются последовательно. Когда
файловой системе поступает запрос на блок k файла, она удовлетворяет его, одна­
ко затем скрытно проверяет в кэше наличие блока k + 1 . Если блок отсутствует,
он считывается в кэш в надежде на то, что, когда он понадобится, его присутствие
будет обеспечено. По крайней мере, файловая система заранее себя подготовит.
Разумеется, стратегия упреждающего чтения применима лишь к последователь­
но считываемым файлам. Если доступ к файлу носит случайный характер, упре­
ждающее чтение не поможет. Более того, оно будет мешать дисковому доступу,
считывая ненужные блоки и удаляя из кэша потенциально полезные (а возмож­
но, еще и сбрасывая блоки на диск, если они были изменены). Чтобы опреде­
лить, стоит ли выполнять упреждающее чтение, файловая система может отсле­
живать способ доступа к каждому открытому файлу. Например, каждому файлу
сопоставляется бит, значение которого определяет режим доступа: последователь­
ный и случайный. В начальный момент выдвигается сомнение в пользу файла:
считается, что режим доступа к нему последовательный. При попытке поиска по
файлу бит сбрасывается, однако при последовательном чтении устанавливается
вновь. Таким способом файловая система делает обоснованный вывод о том,
следует ли читать блоки заранее. Если иногда этот вывод оказывается неверным,
результатом является не катастрофа, а всего лишь небольшая потеря пропуск­
ной способности диска.

Уменьшение количества перемещен ий г оловки диска


Кэширование и упреждающее чтение не являются единственными способами по­
вышения производительности системы. Другой важный метод состоит в уменьше­
нии затрат времени на перемещение блока головок. Достигается это помещением
5 . 3 . Реализаци я файловой системы 575

блоков, к которым высока вероятность доступа в течение короткого интервала


времени, близко друг к другу, желательно на одном цилиндре. Когда записывает­
ся выходной файл, файловая система должна зарезервировать место для чтения
таких блоков за одну операцию. Если свободные блоки учитываются в битовой
карте, а вся битовая карта помещается в оперативной памяти, то довольно легко
выбрать свободный блок как можно ближе к предыдущему блоку. Когда свобод­
ные блоки хранятся в списке, часть которого находится в оперативной памяти,
а часть на диске, сделать это значительно труднее.
Однако даже при использовании списка свободных блоков может быть выполне­
на определенная кластеризация данных. Хитрость заключается в том, чтобы учи­
тывать место на диске не в блоках, а в группах последовательных блоков. Если
сектор состоит из 5 1 2 байт, система может использовать блоки размером в 1 Кбайт
(два сектора), но выделять пространство на диске в единицах по два блока (че­
тыре сектора). Это не то же самое, что использовать 2-килобайтные дисковые
блоки, так как кэш рассчитан на килобайтные блоки, и дисковые операции чтения
и записи будут по-прежнему требовать килобайтных блоков. Однако при после­
довательном чтении файла количество операций поиска цилиндра уменьшится
вдвое, что значительно повысит производительность. Вариацией этой темы яв­
ляется попытка системы учесть позицию блока в цилиндре.
Производительность файловых систем снижается еще в силу того, что при опе­
рировании индексными узлами или чем-либо эквивалентным им, особенно при
чтении коротких файлов, требуется два обращения к диску вместо одного: одно
для индексного узла и одно для блока данных. Обычный вариант размещения
индексных узлов на диске показан на рис. 5 . 1 9 , а. Здесь все индексные узлы рас­
полагаются в начале диска, значит, среднее расстояние между индексным узлом
и его блоками составляет около половины количества цилиндров, то есть при
доступе практически к каждому файлу потребуются значительные перемещения
блока головок.

Диск разделен на группы


цилиндров, каждая со своими
1-узлы располагаются собственными блоками и i-узлами

а б
Уменьшение количества перемещений головки диска:
Рис. 5 . 1 9 . индексные узлы,
а -

размещенные в начале диска; б диск, разделенный на группы цилиндров, каждая


-

с собственными блоками и индексными узлами


576 Глава 5. Файловые систем ы

Один из способов подъема производительности заключается в помещении ин­


дексных узлов в середину диска. Таким образом уменьшается средний путь бло­
ков головок в два раза. Другая идея (рис. 5 . 1 9 , б) заключается в разбиении диска
на группы цилиндров, каждая со своими индексными узлами, блоками и спи­
ском свободных блоков [85 ] . Когда создается новый файл, может быть выбран
любой индексный узел, но предпринимается попытка найти блок в той же груп­
пе цилиндров, что и индексный узел. Если эта попытка заканчивается неудачей,
используется блок в соседней группе цилиндров.

5 . 3 . 7. Файловые системы
с жу рнал ьной структурой
Изменения в технологии оказывают влияние на современные файловые систе­
мы. В частности, центральные процессоры становятся все быстрее, диски - все
вместительнее и дешевле (при этом скорость доступа хотя и растет, но мед­
ленно), а размеры оперативной памяти растут экспоненциально. Единственным
параметром, не меняющимся столь стремительно, является время поиска цилин­
дра диска. В результате это становится узким местом многих файловых систем.
В университете Беркли были проведены исследования, направленные на сни­
жение остроты проблемы. В результате родилась совершенно новая файловая
система с журнальной структурой ( Log-structured File System, LFS). В этом
разделе мы кратко опишем, как она работает, а дополнительные сведения можно
получить в [99 ] .
В основе файловой системы с журнальной структурой лежит идея, что п о мере
ускорения центральных процессоров и экстенсивного расширения оперативной
памяти растет и выгода от кэширования дисков. Поэтому становится возмож­
ным удовлетворить весьма существенную часть всех дисковых запросов прямо
из кэша файловой системы без обращения к диску. Из этого следует, что в буду­
щем большинство обращений к диску будут составлять обращения записи, по­
этому алгоритм опережающего чтения, применявшийся в некоторых файловых
системах, уже не дает большого выигрыша производительности.
Ситуация усложняется тем, что в большинстве файловых систем операции запи­
си выполняются над очень маленькими блоками данных. Такие операции оказы­
ваются крайне неэффективными, поскольку самой физической записи, занимаю­
щей 50 мкс, часто предшествует поиск цилиндра в течение 1 О мс и его поворот
в течение 4 мс. При таких параметрах эффективность диска падает до 1 %.
Чтобы понять, из чего складываются все эти мелкие операции записи, рассмот­
рим создание файла в операционной системе UNIX. Здесь необходимо произ­
вести запись в индексный узел каталога, блок каталога, индексный узел файла
и, наконец, блок самого файла. В принципе, эти операции записи могут быть от­
ложены на некоторое время, но тем самым не исключаются серьезные проблемы
непротиворечивости файловой системы в случае сбоя компьютера прежде, чем
запись на диск будет выполнена. По этой причине индексные узлы обычно со­
хранятся на диск без промедления.
5 . 3 . Реализация файловой системы 577

Учитывая все это, разработчики файловой системы с журнальной структурой


решили реализовать файловую систему UNIX таким образом, чтобы добиться
максимально эффективного обращения к диску, несмотря на то, что в рабочем
режиме выполняется множество случайных мелких операций записи. Идея ос­
нована на использовании диска как журнала. Периодически, когда возникает не­
обходимость, все буферизированные в памяти блоки, подлежащие записи, соби­
раются вместе в единый сегмент, и он сбрасывается на диск одним непрерывным
фрагментом в конец журнала. Записываемый сегмент может содержать индекс­
ные узлы, блоки каталогов и блоки данных, перемешанные друг с другом. В на­
чале каждого сегмента создается его оглавление. Если довести средний размер
сегмента до 1 Мбайт, то можно задействовать почти всю пропускную способ­
ность диска.
При такой организации индексные узлы существуют и имеют ту же структуру,
что и в UNIX, но теперь они не располагаются в фиксированной позиции на дис­
ке, а рассредоточены по всему журналу. Тем не менее, когда программа находит
индексный узел, определение расположения блоков выполняется обычным спо­
собом. Конечно, здесь обнаружить индексный узел намного сложнее, так как
его адрес не определяется по его номеру, как это было в UNIX. Чтобы можно
было найти индексный узел, создается массив индексных узлов, индексирован­
ный по номерам узлов. Элемент i массива указывает на узел i на диске. Массив
хранится на диске, но также содержится и в кэше, а это означает, что наиболее
часто востребованные части этого массива постоянно находятся в оператив­
ной памяти.
Таким образом, все операции записи буферизируются в памяти и периодически
данные из буфера записываются на диск в виде единых сегментов в конец жур­
нала. Чтобы открыть файл, используется массив, позволяющий обнаружить ин­
дексный узел в файле. Как только индексный узел обнаружен, могут быть опре­
делены номера блоков файла. Все эти блоки также располагаются в сегментах
где-то в журнале.
Если бы диски были бесконечного размера, на этом все бы и заканчивалось. Одна­
ко существующие диски имеют ограниченный размер, поэтому рано или поздно
журнал разрастается на весь диск. К счастью, многие сегменты могут содержать
уже ненужные блоки. Например, если файл бьш перезаписан, его индексный узел
будет указывать на новые блоки, хотя старые блоки будут все также занимать
место в записанных ранее сегментах.
Для решения проблемы повторного использования блоков в старых сегментах
в файловой системе с журнальной структурой выполняется программный поток
чистwtъщика, в обязанности которого входит постоянное сканирование журнала
с целью сделать последний более компактным. Чистильщик начинает с того, что
считывает содержимое самого первого сегмента журнала, выясняя, какие индекс­
ные узлы и файлы в нем находятся. Затем он смотрит в текущий массив ин­
дексных узлов, проверяя, являются ли индексные узлы все еще текущими и ис­
пользуются ли все еще блоки файлов. Если нет, эта информация отбрасывается,
578 Глава 5. Файловые систем ы

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


в следующий сегмент. Исходный сегмент помечается как свободный, поэтому
журнал может помещать в него новые данные. Таким образом, чистильщик дви­
гается по журналу, удаляя старые сегменты с диска и помещая всю имеющую
ценность информацию в память для перезаписи в следующий сегмент. В резуль­
тате диск превращается в большой кольцевой буфер, в котором пишущий поток
добавляет новые сегменты с одного конца, а чистящий процесс удаляет старые
сегменты с другого.
Учет расположения блоков здесь весьма нетривиален, поскольку, когда блок фай­
ла записывается в новый сегмент, индексный узел файла (где-то в журнале) дол­
жен быть найден, обновлен и помещен в буфер для записи в следующий сегмент.
При этом массив индексных узлов также должен быть обновлен, чтобы элемент
массива указывал на новую копию. Тем не менее администрирование такой сис­
темы вполне возможно, а рост производительности показывает, что все эти
сложности не напрасны. Результаты измерений показали, что файловая система
с журнальной структурой ( LFS) превосходит файловую систему UNIX при мно­
жестве небольших записей на порядок, а при чтении и больших записях облада­
ет близкой или более высокой производительностью.

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

5 . 4 . 1 . Безопасное окружение
Термины «безопасность• и «защита• иногда смешивают. Однако часто полез­
но провести границу между техническими, административными, юридическими
и политическими аспектами безопасности, с одной стороны (то есть с гарантией
того, что файлы не читаются и не модифицируются неавторизованными лица­
ми), и специфическими механизмами операционной системы, привлекаемыми
для обеспечения безопасности, с другой стороны. Чтобы избежать путаницы, мы
будем применять термин безопасность для обозначения общей проблемы и тер­
мин защита при описании специфических механизмов операционной системы,
используемых для обеспечения информационной безопасности в компьютерных
системах, хотя в реальности граница между этими двумя терминами достаточно
размыта. Сначала мы познакомимся с вопросами безопасности, чтобы понять
природу проблемы. Затем мы рассмотрим механизмы защиты и модели, способ­
ствующие обеспечению безопасности.
5. 4 . Безо п асность 579

Проблема безопасности многогранна. Тремя ее наиболее важными аспектами яв­


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

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

Т абл и ца 5 . 3 . Цели и угрозы безопасн ости


Цель Угроза
Конфиденциальность данных Несанкционированный доступ к данным
Целостность данных Искажение данных
Доступность системы Отказ в обслуживании
Вторая цель, целостность данных, означает, что неавторизованные пользователи
не могут изменять какие-либо данные без разрешения владельца. Под изменени­
ем данных в таком контексте понимается не только их редактирование, но также
удаление и добавление. Если компьютер не может гарантировать, что никто, кро­
ме владельца, не способен изменить данные, то его ценность как информацион­
ной системы становится ничтожной. Целостность данных, как правило, еще важ­
нее, чем конфиденциальность.
Третья цель, доступность системы, означает, что никто не может вывести систе­
му из строя. Атаки типа отказа в обслуживании становятся все более и более
частыми. Например, если компьютер представляет собой интернет-сервер, его
можно вывести из строя, послав столько запросов, что все процессорное время
будет тратиться на их анализ и отклонение. Если, скажем, обработка входящего
запроса на чтение веб-страницы занимает 100 мкс, то каждый, кто способен послать
1 0 ООО запросов в секунду, может вызвать отказ системы. Для борьбы с атаками
на конфиденциальность и целостность существуют продуманные модели и техно­
логии; противостоять атакам типа отказа в обслуживании значительно сложнее.
Еще одним аспектом безопасности является приватность, то есть защита поль­
зователей от ненадлежащего использования сведений о них. Это приводит к мно­
жеству вопросов юридического и морального характера. Могут ли органы управ­
ления собирать досье на кого угодно с целью поимки граждан, скрывающихся от
уплаты налогов? Может ли полиция собирать любые сведения о пользователях,
чтобы остановить организованную преступность? Имеют ли какие-либо права
работодатели и страховые компании? Если да, то как быть в случае их противо­
речия частным правам? Все эти вопросы исключительно важны, однако выходят
за рамки темы этой книги.
580 Глава 5. Файловые систем ы

Злоумышленники
Большинство людей совершенно безвредно и законопослушно. Так зачем же бес­
покоиться о безопасности? Затем, что есть небольшая группа людей, которая от­
нюдь не безвредна и жаждет причинять неприятности другим (возможно, для
собственной коммерческой выгоды). В литературе по безопасности злоумышленни­
ком называют человека, сующего свой нос в чужие дела. Злоумышленники подраз­

деляются на два вида. Пассивные злоумышленники просто пытаются прочитать


файлы, которые им не разрешено читать. Активные злоумышленники пытаются
незаконно изменить данные. При разработке системы защиты от злоумышлен­
ников важно знать врага, против которого нужно возводить укрепления, в лицо.
Рассмотрим наиболее распространенные категории злоумышленников.
+ Случайные любопытные пользователи, не применяющие специальных тех­
нических средств. У многих людей есть компьютеры, соединенные с общим
файловым сервером. И если не установить специальной защиты, благодаря
естественному любопытству многие начнут читать чужую электронную почту
и другие файлы. Например, во многих системах UNIX новые только что со­
зданные файлы по умолчанию доступны для чтения всем желающим.
+ Любители совать нос в чужие дела. Студенты, системные программисты, опе­
раторы и другой технический персонал часто считают взлом системы без­
опасности локальной компьютерной системы святым долгом. Как правило,
они имеют высокую квалификацию и готовы посвящать достижению постав­
ленной перед собой цели массу времени.
+ Желающие делать деньги любыми средствами. Некоторые программисты, рабо­
тающие в банках, предпринимали попытки украсть деньги у банка, в котором
они работали. Их схемы варьировались от изменения способов округления
сумм в программах и сбора, таким образом, <(<С миру по нитке• , до шантажа
( <(<Заплатите мне, или я уничтожу всю банковскую информацию• ) .
+ Лица, занимающиеся коммерческим и военным шпионажем. Шпионаж пред­
ставляет собой серьезную и хорошо финансированную попытку конкурента
или другой страны украсть программы, коммерческие тайны, ценные идеи
и технологии, чертежи микросхем, бизнес-планы и т. д. Часто такие попытки
подразумевают подключение к линиям связи или установку антенн, направ­
ленных на компьютер для улавливания его электромагнитного излучения.
Очевидно, что попытка предотвратить кражу военных секретов враждебным
иностранным государством отличается от противостояния шалостям студентов,
встраивающих забавные сообщения в систему. Необходимые для поддержания
безопасности и защиты усилия зависят от предполагаемого противника.

Вредоносные программы
Еще одну угрозу безопасности представляют собой вредоносные программы.
В определенном смысле автора программы следует считать злоумышленником,
зачастую обладающим хорошо развитыми техническими навыками. Различие ме­
жду традиционным злоумышленником и вредоносной программой заключается
5. 4 . Безо пасность 581

в том, что злоумышленник - это человек, своими силами пытающийся взломать


систему с целью нанесения ущерба, а вредоносная программа - это программа,
написанная и выведенная злоумышленником в жизнь. Одни вредоносные про­
граммы предназначены для повреждения систем, в то время как другие имеют
более узкие цели. Злоумышленники представляют собой серьезную проблему,
о которой выпущено множество публикаций [3, 15, 74, 84, 1 20, 127].
Наиболее известным типом вредоносных программ являются вирусы. Обычно
вирус представляет собой фрагмент кода, способный воспроизводить себя, при­
крепляя собственные копии к другим программам (такое поведение аналогично
размножению биологических вирусов). Помимо размножения, вирусы способны
выполнять и другие действия. Например, они могут делать что-нибудь без­
вредное: отображать сообщения или изображения на экране, проигрывать музы­
ку и т. д. К сожалению, они также способны изменять, уничтожать и похищать
файлы, отправляя их по электронной почте неизвестному адресату.
Еще один вариант воздействия вируса на компьютер - выведение его из работо­
способного состояния, известное как атака типа отказа в обслуживании (Denial
Of Service, DOS). Как правило, такой результат достигается за счет сверхинтен­
сивного потребления ресурсов, например, центрального процессора либо захлам­
ления жесткого диска. Вирусы, а также прочие виды вредоносных программ,
рассматриваемые здесь, могут быть использованы и для атак типа распределен­
ного отказа в обслуживании ( Distributed Denial Of Service, DDOS). В этом слу­
чае вирус не начинает свою деятельность на зараженном компьютере немедлен­
но. В установленные заранее дату и время тысячи копий вируса на компьютерах,
расположенных по всему миру, начинают запрашивать веб-страницы или другие
сетевые ресурсы у цели атаки, например, у сайта политической партии или кор­
порации. В результате сервер (или обслуживающая его сеть) может оказаться
перегруженным.
Зачастую вредоносные программы создаются ради выгоды. Многие нежелатель­
ные сообщения электронной почты (если не большинство), известные как спа.м,
передаются адресатам через сети компьютеров, зараженных вирусами или други­
ми видами вредоносных программ. Зараженный компьютер оказывается в под­
чинении главного компьютера, расположенного где-то в Интернете, и передает
ему информацию о своем состоянии. Затем главный компьютер рассылает спам
по всем адресам электронной почты, которые удалось извлечь из адресных книг
и прочих файлов подчиненного компьютера. Еще один вид вредоносных программ,
создаваемых с целью наживы, - перехватчик клавиатурного ввода, фиксирующий
все, что вводится с клавиатуры. Полученные данные несложно проанализировать
и получить такую информацию, как сочетания имен пользователей и паролей,
номера кредитных карт и дат истечения. Информация затем передается главному
компьютеру, после чего может быть использована или продана в преступных целях.
Сходной вирусу программой является червь. Если вирус распространяется, при­
крепляя себя к другой программе, и исполняется вместе с ней, то червь является
самостоятельным. Он распространяется на другие компьютеры путем рассыл­
ки своих копий по сети. Компьютеры, оснащенные операционной системой
582 Глава 5. Файловые систем ы

Windows, имеют каталог автозагрузки для каждого пользователя. Любая поме­


щенная в него программа исполняется при входе пользователя в систему. Таким
образом, все, что нужно сделать червю, - это поместить себя или ярлык, ука­
зывающий на себя, в папку автозагрузки удаленного компьютера. Существуют
и другие способы запуска программы, скопированной в файловую систему уда­
ленной машины; зачастую обнаружить их значительно труднее. Последствия дей­
ствий червя могут быть такими же, как и вируса. На самом деле, четкой границы
между этими видами программ нет; нередко вредоносные программы использу­
ют оба метода для своего распространения.
Еще одна категория вредоносных программ - троянские проzра.мм:ы , или троя­
ны. Внешне они кажутся полезными; например, троянская программа может
быть замаскирована под игру или усовершенствованную версию утилиты. Одна­
ко во время исполнения троянская программа ведет совсем иную деятельность:
запускает червя, вирус или выполняет другие вредоносные действия. Как прави­
ло, подобная работа отличается тонкостью и скрытостью. В отличие от червей
и вирусов, троянские программы загружаются с согласия пользователей. Как
только правда о них выходит наружу, троянские программы удаляют с загрузоч­
ных сайтов, на которых они размещены.
Следующий вид вредоносной программы - лоzическая бомба. Это фрагмент ко­
да, который написан программистом, работающим в компании, и тайно внедрен
в рабочую операционную систему. Бомба не делает ничего до тех пор, пока про­
граммист ежедневно вводит свой пароль. Как только его увольняют и физически
отстраняют от оборудования без предупреждения, бомба не получает пароль
и взрывается.
Результатом взрыва может быть удаление информации с жесткого диска, случай­
ное стирание файлов, аккуратная и труднообнаруживаемая модификация ключе­
вых программ или шифрование важных файлов. В последнем случае компания
стоит перед сложной дилеммой: обратиться в полицию (при этом разбирательство
затянется на месяцы с неясным результатом) или нанять бывшего программиста
в качестве (<консультанта• для решения проблемы за астрономическую сумму
в надежде, что при этом он не заложит следующую бомбу.
Еще одной формой вредоносной программы является шпионская проzрамма . Как
правило, заражение ей происходит при посещении веб-сайтов. В простейшем слу­
чае шпионская программа представляет собой просто cookie - небольшой файл,
используемый для обмена между веб-браузерами и веб-серверами. У сооkiе-фай­
лов вполне легальное предназначение: они содержат информацию, с помощью
которой веб-сайт может вас идентифицировать при следующем посещении. Они
подобны квитанщщ, которую сервисный центр выдает вам, когда вы сдаете неис­
правный велосипед в ремонт. По выполнении ремонта вы приносите свою часть
квитанции, данные которой сравниваются с маркировкой велосипеда и номером
выставленного счета. Веб-соединения непостоянны, поэтому, если вы захотите
приобрести книгу в интерактивном магазине, веб-сайт последнего попросит ваш
браузер предоставить сооkiе-файл. Когда вы закончите свою виртуальную про­
гулку и будете готовы оплатить сделанные заказы, сервер попросит вернуть
5.4. Безо п асност ь 583

сооkiе-файлы, сохраненные в текущем сеансе. Информация, содержащаяся в них,


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

Случайная потеря данных


Важные данные могут быть потеряны не только из-за злоумышленников, но и слу­
чайно. Вот наиболее распространенные причины случайной потери данных:
+ <1: Форс-мажор» : пожары, наводнения, землетрясения, войны, восстания, кры­
сы, пожирающие магнитные ленты или гибкие диски.
+ Аппаратные и программные ошибки: сбои центрального процессора, нечи­
таемые диски или ленты, ошибки при передаче данных, ошибки в программах.
+ Человеческий фактор: неправильный ввод данных, неверные вставленный
диск или заправленная лента, запуск не той программы, потерянные диск или
лента и т. д.
Большая часть этих проблем может быть разрешена при условии своевременного
создания соответствующих резервных копий и хранения их вдали от оригиналь­
ных данных. Несмотря на то что защита данных от случайного удаления может
показаться несерьезной по сравнению с защитой от интеллектуальных злоумыш­
ленников, на практике случайное удаление приносит больше вреда, чем атаки.
584 Глава 5. Файловые системы

5 . 4 . 2 . Общие виды атак


на систему безопасности
Поиск уязвимостей в системе безопасности - непростая задача. Обычный способ
проверить надежность системы безопасности заключается в приглашении группы
экспертов, называемых командой тиzров, или группой вторжения, чтобы посмот­
реть, смогут ли они взломать защиту. Бывало, в качестве такой команды приглаша­
лись аспиранты [57) . За несколько лет подобные команды обнаружили множество
сфер, в которых операционные системы проявляют свои слабости. Мы рассмот­
рим наиболее распространенные методы атак, часто завершающиеся успехом.
При разработке систем убедитесь, что им под силу выдержать атаки этих типов.
+ Запросите страницы памяти, место на диске или на магнитной ленте и просто
считывайте информацию. Многие системы не очищают память при ее выде­
лении пользователю, поэтому память и диски могут содержать много инте­
ресной информации, записанной предыдущим владельцем.
+ Попытайтесь выполнять несуществующие системные вызовы или существую­
щие, но с неверными параметрами. Многие системы не выдерживают подоб­
ного обращения с ними.
+ Начните регистрацию, а затем в ходе регистрации нажмите клавишу Del,
Ru bout или Break. В некоторых системах подобным образом удается уничто­
жить процесс, осуществляющий проверку пароля, и успешно пройти регист­
рацию без ввода пароля.
+ Попытайтесь модифицировать сложные структуры операционной системы
(если таковые имеются), хранящиеся в пользовательской области памяти.
В некоторых системах (особенно в мэйнфреймах), для того чтобы открыть
файл, программа формирует большую структуру, содержащую имя файла
и множество других параметров, которую передает операционной системе.
При чтении и записи файла операционная система иногда сама обновляет эту
структуру. Модификация некоторых полей может иметь разрушительное воз­
действие на систему безопасности.
+ Напишите программу, просто запрашивающую у пользователя идентифика­
ционные данные. Многие пользователи послушно введут их, а программа ак­
куратно сохранит их для злоумышленника.
+ Прочитайте руководство и попытайтесь найти фразы, гласящие: 4 Не делай­
те х� . После этого попытайтесь проделать 4Х� в различных комбинациях.
+ Убедите системного администратора реализовать обход важных этапов про­
верки безопасности для любого пользователя с вашим регистрационным име­
нем. Подобные атаки известны как атаки с черною хода.
+ Если ничего другого не получилось, попытайтесь найти секретаршу систем­
ного администратора и притвориться несчастным пользователем, забывшим
свой пароль. В качестве альтернативы можно попытаться подкупить секретар­
шу. У секретарши, как правило, есть доступ к самой разнообразной и очень
интересной информации, а зарплата обычно невелика. Не следует недооцени­
вать человеческий фактор.
5 .4. Безо п асност ь 585

Подобные и другие методы атак обсуждаются в [8 1 ] . Существует большое коли­


чество других источников информации о безопасности и ее тестировании, осо­
бенно в Интернете. Последней работой по данной теме, ориентированной на опе­
рационную систему Windows, является [66] .

5 . 4 . 3 . П ри н ципы разработки
механизмов безопасности
В 1975 году исследователи определили несколько общих принципов, которых
необходимо придерживаться при разработке безопасных систем [ 1 02] . Приве­
дем краткий обзор некоторых из этих идей на основе опыта работы с систе­
мой MUL ТICS.
+ Устройство системы не должно быть секретом. Предположение, что взлом­
щики не знают, как работает система, - это лишь ненужные иллюзии разра­
ботчиков.
+ По умолчанию доступ не должен предоставляться. Об ошибках, в результате
которых пользователям было отказано в законном доступе, сообщат значи­
тельно быстрее, чем о случаях ошибочного предоставления несанкциониро­
ванного доступа.
+ Необходимо проверять текущее состояние прав доступа. Система не должна,
проверив наличие прав доступа и убедившись, что доступ разрешен, затем со­
хранять эту информацию для последующего использования. Многие системы
проверяют разрешение доступа при открытии файла, но не после открытия.
Это означает, что пользователь, открывший файл и держащий его открытым
неделями, будет продолжать обладать доступом к файлу, даже если владелец
файла с тех пор уже давно изменил права доступа файла.
+ Предоставляйте каждому процессу как можно меньше привилегий. Если у про­
граммы-редактора есть доступ только к редактируемому файлу (указанному
при вызове), то редакторы в виде троянских коней с ахейцами в брюхе вряд
ли смогут причинить много вреда. Применение этого принципа предполагает
схему защиты высокой степени детализации. Подобные схемы рассматрива­
ются позднее в данной главе.
+ Механизм защиты должен быть простым, одинаковым для всех и встроенным
в самые нижние уровни системы. Попытка установить механизмы безопас­
ности на существующую небезопасную систему практически невыполнима.
Безопасность, как и правильность, не является свойством, которое можно до­
бавить потом.
+ Выбранная схема должна быть психологически приемлемой. Если пользова­
тели почувствуют, что для защиты файлов требуется тратить слишком много
усилий, они не станут их защищать. В то же время они будут громко жало­
ваться, если что-либо пойдет не так. Ответы типа •это ваша вина�, как прави­
ло, восприниматься не будут.
586 Глава 5. Файловые системы

5 . 4 . 4 . Аутентификация пол ьзователей


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

Пароли
В наиболее широко применяемой форме аутентификации пользователю предла­
гается ввести имя и пароль. Парольная защита легко реализуется. В UNIX это
происходит следующим образом: программа входа в систему просит пользовате­
ля ввести имя и пароль, который немедленно шифруется. Затем программа чита­
ет файл паролей, представляющий собой последовательность АSСП-строк, где
каждая строка соответствует одному пользователю. Когда нужный пользователь
найден, введенный шифрованный пароль сравнивается с шифрованным паролем
из файла. В зависимости от результата сопоставления пользователю выдается
разрешение или отказ в доступе.
Парольная аутентификация легко уязвима. Зачастую можно видеть публикации
о том, как школьники старших и даже средних классов с домашних компьютеров
взламывают сверхсекретные системы крупных корпораций и правительственных
организаций. Почти всегда взлом происходит вследствие угадывания комбина­
ции имени пользователя и пароля.
Несмотря на более свежие исследования, классический труд по вопросу безопас­
ности паролей был написан аж в 1979 году на основе исследований систем UNIX
[90]. Авторы скомпилировали список вероятных паролей: имена и фамилии, на­
звания улиц, городов, слова из словарей среднего размера (в частности, написан­
ные задом наперед), автомобильные номера и короткие строки случайных сим­
волов. Затем они сравнили свой полученный таким образом список с системным
файлом паролей, чтобы посмотреть, есть ли совпадения. Как выяснилось, более
86 % от общего количества паролей в файле оказались в их списке.
Если бы все пароли состояли из 7 символов, случайным образом выбранных из
95 печатных символов набора ASCII, их количество было бы равно 95 7 , что при­
мерно равно 7 х 10 1 3 . Если выполнять 2000 операций шифрования в секунду, на
построение списка для сверки пароля потребуется около 2000 лет. Более того,
под хранение такого списка ушло бы 20 млн магнитных лент. Даже требование
того, чтобы пароли содержали как минимум один символ нижнего регистра, один
символ верхнего регистра, один специальный символ и имели бы как минимум
семь или восемь символов в длину, заметно улучшает ситуацию по сравнению
с тем случаем, когда пароль выбирается пользователем без всяких ограничений.
Даже для случая, когда по политическим причинам невозможно заставить поль­
зователей выбирать разумные пароли, в [90] описана технология, делающая пред­
лагаемый авторами метод атаки (заранее зашифровать большое число паролей)
практически бесполезным. Идея состоит в том, чтобы ассоциировать с каждым
5 . 4. Безо пасн ость 587

паролем п-разрядное случайное число. Это случайное число меняется при каж­
дом изменении пароля. Оно хранится в файле паролей в незашифрованном виде,
и каждый может его видеть. Пароль же сначала объединяется со случайным чис­
лом, а только затем шифруется и записывается в файл.
Рассмотрим, к каким последствиям приведет эта техника для злоумышленника,
пытающегося составить список вероятных паролей, зашифровать их и сохранить
в отсортированном виде в файле f, где их затем легко будет найти. Если злоумыш­
ленник считает, что слово Marilyn может быть паролем, ему теперь недостаточно
закодировать только это слово и записать его в f. Он должен зашифровать и за­
писать 2п строк, Mar i lyn O O O O , Mar i lyn0 0 0 1 , Mar i lyn 0 0 0 2 и т. д. Благодаря
этой технике, называемой добавлением соли в файл паролей, размер файла f уве­
личивается в 2п раз. В UNIX применяется значение п, равное 12. В некоторых
версиях UNIX сам файл паролей сделан недоступным для чтения, а для работы
с ним предоставлена программа, которая просматривает запрошенные записи,
внося дополнительную задержку, достаточную, чтобы сильно замедлить атаку.
Добавление случайных чисел к файлу паролей защищает систему от взломщи­
ков, пытающихся заранее составить большой список зашифрованных паролей
и таким образом взломать несколько паролей сразу. Однако данный метод бесси­
лен помочь в том случае, когда пароль легко отгадать, например, если пользователь
David использует пароль Dav id. Взломщик может просто попытаться отгадать
пароли один за другим. Обучение пользователей в данной области помогает, но
оно редко проводится. Однако помимо обучения пользователей в вашем распо­
ряжении помощь компьютера. На некоторых системах устанавливается програм­
ма, формирующая случайные, легко произносимые бессмысленные слова, такие
как f o t a l ly, garbungy или Ьip i t ty, подходящие в качестве паролей (жела­
тельно с чередованием прописных и строчных букв и с разбавлением специаль­
ными символами).
Некоторые операционные системы требуют от пользователей регулярной смены
паролей, чтобы ограничить ущерб в ситуации, когда пароль становится извест­
ным взломщику. Крайность здесь одноразовые пароли. В этом случае пользо­
-

ватель получает блокнот, содержащий список паролей. Для каждого входа в сис­
тему используется следующий пароль в списке. В итоге, если даже взломщику
удастся узнать уже отработавший пароль, он ему не пригодится. ( Предполагает­
ся, что пользователь не потеряет выданный ему блокнот. )
Еще одна вариация н а тему паролей предполагает, что для каждого нового поль­
зователя создается длинный список вопросов и ответов, который хранится на
сервере в надежном виде (например, в зашифрованном). Вопросы должны выби­
раться так, чтобы пользователю не нужно было их записывать. Примеры:
+ Как зовут сестру Марджолин?
+ На какой улице расположена ваша начальная школа?
+ Что преподавала мисс Воробьофф?
При регистрации сервер задает один из этих вопросов, выбирая его в списке слу­
чайным образом, и проверяет ответ.
588 Глава 5. Файловые системы

Другой вариант называется запрос-отзыв. Он работает следующим образом. Поль­


зователь выбирает алгоритм, например :J?, идентифицирующий его как пользовате­
ля. Когда пользователь входит в систему, сервер посылает ему некое случайное
число, например 7. В ответ пользователь отправляет серверу число 49. Алгоритм
может отличаться утром и вечером, в различные дни недели, для различных тер­
миналов и т. д.

Физическая идентификация
Совершенно иным подходом к авторизации является проверка наличия у поль­
зователя некоторого предмета, как правило, пластиковой карты с нанесенной
магнитной полосой. Обычно пользователь должен не только вставить карту, но
и ввести пароль; таким образом, доступ к системе обеспечивается при соблюде­
нии двух условий - наличия у пользователя карты и знания им пароля. Обычно
на этом принципе основана работа банкоматов.
Другой метод проверки подлинности пользователя основан на измерении его
физических характеристик, которые трудно подделать. Например, для иденти­
фикации пользователя может применяться специальное устройство считывания
отпечатков пальцев или распознавания тембра голоса. Поиск можно значительно
ускорить, если пользователь при этом будет сообщать системе, кто он; в этом
случае системе будет достаточно выполнить сравнение для одного отпечатка паль­
цев, нежели искать предоставленный отпечаток во всей базе данных. Визуальная
идентификация пока не встречается, но со временем и она может появиться.
Еще один метод идентификации заключается в анализе подписи. Пользователь
ставит подпись специальным пером, соединенным с терминалом, и компьютер
сверяет ее с оригиналом. Еще лучше сравнивать не подпись, а движения, выпол­
няемые при ее написании. Хороший специалист по подделке подписей может на­
рисовать довольно точную копию подписи, но не обязательно угадает, в каком
порядке выполняются движения.
На удивление часто на практике используется измерение длины пальцев. При
этом каждый терминал оснащается устройством вроде показанного на рис. 5.20.
Пользователь засовывает в него руку, и устройство измеряет длину его пальцев
и сравнивает с информацией, хранящейся в базе данных.
Подобные примеры измерения биометрических характеристик можно приводить
еще и еще, но мы остановимся только на двух, которые помогут отметить кое-что
важное. Кошки и другие животные мочой метят свои владения по периметру.
Очевидно, кошки могут идентифицировать друг друга подобным образом. Пред­
ставьте себе, что кому-нибудь удастся создать небольшое устройство, способное
производить мгновенный анализ мочи, обеспечивая, таким образом, надежную
идентификацию. Раз так, значит, каждый терминал можно снабдить подобным
устройством, вывесив табличку: •для регистрации помочитесь сюда, пожалуй­
ста� . Возможно, таким образом удалось бы создать абсолютно надежную систе­
му, хотя она вряд ли понравилась бы пользователям. То же самое можно сказать
о системе, состоящей из иголки и небольшого спектрометра и предлагающей поль­
зователю проколоть палец иголкой, предоставив таким образом каплю крови для
анализа. Дело в том, что любая схема аутентификации должна быть психологи-
5.4. Безо пасност ь 589

чески приемлема для сообщества аутентифицируемых. Измерение длины паль­


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

Пружина

Рис. 5 . 20 . Устройство для измерения длины пальцев


Контрмеры
В некоторых компьютерных системах, серьезно продуманных в плане безопасно­
сти (отношение к этому вопросу, как правило, кардинально меняется на следую­
щий день после того, как злоумышленник вломился в систему и причинил серьез­
ный ущерб), часто предпринимаются шаги, призванные максимально усложнить
неавторизованный вход в систему. Например, пользователям может быть разре­
шено входить в систему только со специального терминала и только в опреде­
ленные дни недели и часы.
Коммутируемые телефонные линии также можно сделать более безопасными.
Например, всем можно разрешить регистрироваться в системе через модем по
телефонной линии, но после успешной регистрации система немедленно прервет
соединение и сама позвонит пользователю по заранее условленному номеру.
Такая мера означает, что взломщик не вломится в систему с любой телефонной
линии. Для работы в системе подойдут только линии зарегистрированных поль­
зователей. В любом случае, с применением данной техники или без нее система
обязана выдерживать паузу, по крайней мере, в 10 с при проверке пароля и увели­
чивать этот временной интервал после каждой неуспешной регистрации, чтобы
снизить частоту попыток взломщика. После трех неуспешных попыток регистра­
ции линия должна отключаться на 10 мин, а персонал уведомляться о попытке
несанкционированного входа в систему.
590 Глава 5. Файловые систем ы

Все попытки входа в систему должны регистрироваться. Когда пользователь реги­


стрируется, система должна сообщать ему дату и время последней регистрации,
а также терминал, примененный для входа, чтобы пользователь мог заметить
взлом системы злоумышленником.
Еще один вариант защиты заключается в установке ловушки для взломщика.
Простая схема ловушки представляет собой специальное имя регистрации с про­
стым паролем (например, имя guest и пароль guest). При каждом входе в систе­
му с таким именем системные специалисты в области безопасности немедленно
уведомляются. Все команды, вводимые взломщиком, немедленно отображаются
на мониторе руководителя службы безопасности, чтобы он мог видеть, что на­
меревается сделать взломщик. Другие ловушки могут представлять собой легко
обнаруживаемые ошибки в операционной системе и тому подобные вещи, на­
меренно встроенные с целью отлавливания злоумышленников на месте преступ­
ления. В [ 1 10] опубликован занимательный доклад о ловушках, установленных
автором с целью поймать шпиона, вломившегося на университетский компьютер
в поисках военных секретов.

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

5 . 5 . 1 . Домены защиты
Компьютерная система подразумевает множество �объектов� , которые требует­
ся защищать. Это может быть аппаратура (например, центральный процессор,
память, диски или принтеры) или программное обеспечение (процессы, файлы,
базы данных или семафоры).
У каждого объекта есть уникальное имя, по которому к нему позволено обра­
щаться, и набор операций, которые вправе выполнять с объектом процессы. Так,
к файлу применимы операции read и wri t e, к семафору - операции up и down.
5 . 5 . Механиз мы за щиты 59 1

Очевидно, что для ограничения доступа к объектам требуется определенный ме­


ханизм. Более того, этот механизм должен предоставлять возможность не полного
запрета доступа, а ограничения в пределах подмножества разрешенных операций.
Например, процессу А может быть разрешено читать файл F, но не писать в него.
Чтобы обсудить различные механизмы защиты, полезно ввести концепцию домена.
Домен представляет собой множество пар (объект, права доступа). Каждая пара
указывает объект и некоторое подмножество операций, разрешенных с ним. Пра.ва
доступа означают в данном контексте разрешение выполнить одну из операций.
Домен может соответствовать одному пользователю или группе пользователей.
На рис. 5.2 1 показаны три домена, содержащие объекты и разрешения для каждо­
го объекта в формате R WX ( Read, Write, eXecute чтение, запись, исполнение).
-

Обратите внимание, что объект Принтер 1 одновременно присутствует в двух


доменах. Хотя это и не отражено в данном примере, один и тот же объект может
иметь в различных доменах разные разрешения.

Домен 1 Домен 2 Домен З

Файл 1 [R]
Файл 2 [RW]

Рис. 5 . 2 1 . Три домена защиты


В каждый момент времени каждый процесс работает в каком-либо одном домене
защиты. Другими словами, имеется некоторая коллекция объектов, к которым
он вправе получить доступ, и для каждого объекта у него есть определенный
набор разрешений. Во время выполнения процессы имеют право переключаться
с одного домена на другой. Правила переключения между доменами в значитель­
ной степени зависят от системы.
Чтобы идея домена защиты выглядела конкретнее, рассмотрим пример из систе­
мы UNIX. В UNIX домен процесса определяется идентификаторами UID и GID
процесса. По заданной комбинации ( U I D , GID) можно составить полный список
всех объектов (файлов, включая устройства ввода-вывода, представленные в ви­
де специальных файлов и т. д. ), к которым процесс может получить доступ с ука­
занием типа доступа (чтение, запись, исполнение). Два процесса с одной и той
же комбинацией (UID, GID) будут иметь абсолютно одинаковый доступ к оди­
наковому набору объектов.
Более того, каждый процесс в UNIX состоит из двух частей: пользовательской
и системной. Когда процесс выполняет системный вызов, он переключается из
пользовательской части в системную. Пользовательская и системная части про­
цесса различаются по уровню доступа к различным множествам объектов. На­
пример, системная составляющая может иметь доступ ко всем страницам в фи­
зической памяти, ко всему диску и ко всем другим защищенным ресурсам. Таким
образом, системный вызов осуществляет переключение доменов защиты.
592 Глава 5. Файловые систем ы

Когда процесс выполняет системный вызов ехе с с файлом, у которого установ­


лен бит SETU I D или S ETG I D, процесс может получить новые идентификаторы
UID и GID. При новой комбинации U I D и GID процесс получает новый 11:абор
доступных файлов и операций. Запуск программы с установленным битом SETUID
или SETG I D также представляет собой переключение домена, так как права дос­
тупа при этом изменяются.
Важным вопросом является то, как система отслеживает, какой объект какому
домену принадлежит. Можно себе представить большую матрицу, в которой ря­
дами являются домены, а колонками - объекты. На пересечении располагаются
ячейки, содержащие права доступа для данного домена к данному объекту. Мат­
рица для рис. 5.2 1 показана на рис. 5.22. При наличии подобной матрицы опера­
ционная система может для каждого домена определить разрешения к любому
заданному объекту.

Объект

Файл 1 Файл 2 Файл З Файл 4 Файл 5 Файл 6 При нтер 1 Плоттер 2


Домен
1 Чтение
Чтение
Заnись

Чтение
2 Чтение Заnись
Чтение
Заnись
Заnись
Исnолнение

Чтение
з Заnись Заnись Заnись
Исnолнение

Рис. 5 . 2 2 . Матрица защиты


Переключение между доменами также легко реализуется при помощи все той же
матрицы, если считать домены объектами, над которыми разрешена операция ent er
(вход). На рис. 5.23 снова изображена та же матрица, что и на предыдущем рисун­
ке, но с тремя доменами, выступающими и в роли объектов. Процессы в состоянии
переключаться с домена 1 на домен 2, но обратно вернуться уже не могут. Эта
ситуация моделирует выполнение программы с установленным битом SETU I D
в UNIX. Другие переключения доменов в данном примере не разрешены.

Объект
Плоттер 2 Домен 2
домен Файл 1 Файл 2 Файл З Файл 4 Файл 5 Файл 6 При нтер 1 Домен 1 Домен З

Чтение
Чтение Enter
Заnись

Чтение
Чтение
2 Чтение Заnись Заnись
Заnись
Исnолнение

Чтение
з Заnись Заnись Заnись
Исnолнение

Рис. 5 . 2 3 . Матрица защиты с доменами в роли объектов


5 . 5 . Механизмы защиты 593

5 . 5 . 2 . Списки у п равлен ия доступом


На практике разрешения доступа редко оформляются в виде матрицы, показан­
ной на рис. 5.23, поскольку такая матрица была бы очень большой и практически
пустой. В большинстве доменов доступа ко многим объектам нет, поэтому хра­
нение объемной матрицы означает бесполезную трату дискового пространства.
На практике применяются два метода, предполагающие хранение матрицы по
рядам или столбцам, причем хранятся только непустые элементы. Эти два подхо­
да, как ни странно, различаются между собой. В данном разделе мы рассмотрим
матрицу защиты в варианте по столбцам, а в следующем разделе познакомимся
с ее построчной реализацией.
Согласно первому методу, с каждым объектом ассоциируется список (упорядо­
ченный), содержащий все домены, которым разрешен доступ к данному объекту,
а также тип доступа. Такие списки называются списками управления доступом
(Access Control List, ACL, рис. 5.24). На нем изображены три процесса, А, В и С,
принадлежащие разным доменам, и три файла, F1, F2 и FЗ. Для простоты предпо­
ложим, что каждый домен соответствует ровно одному пользователю, в данном
случае, А, В и С. В литературе, посвященной безопасности, пользователей назы­
вают субъектами, или принципалами, в отличие от объектов - вещей, которыми
они обладают (например, файлов).

Процесс

'(3"
Пространство
пользователя

}"��-
Файл ____. 0 ----./ А: RW; В: А 1 ACL

0 ----./ А: R; B : RW; C : R 1/
0 ----./ B : RWX; С: RX 1
'----�����---'

Рис. 5 . 24 . Использование списков управления доступом применительно к файлам


С каждым файлом связан список управления доступом. ACL файла F1 состоит
из двух записей, разделенных точкой с запятой. Первая запись указывает на то,
что любой процесс пользователя А может читать и писать в файл, а вторая - на
то, что пользователь В может читать файл. Другие виды доступа для этих поль­
зователей, а также все виды доступа для остальных пользователей запрещены.
Обратите внимание на то, что права назначены не процессам, а пользователям.
С точки зрения системы защиты, любой процесс, владельцем которого является
пользователь А, имеет право читать и писать в файл F1. Не имеет значения,
сколько таких процессов - один или сто; учитывается не идентификатор про­
цесса, а его владелец.
594 Глава 5 . Файловые системы

У файла F2 в ACL три записи: пользователи А, В и С могут читать файл, а, кроме


того, В имеет право записи в него. Все прочие виды доступа запрещены. Файл
FЗ, очевидно, является исполняемой программой, поскольку пользователи В и С
могут читать и исполнять его. Процесс В также имеет разрешение на запись в FЗ.
Данный пример демонстрирует простейшую форму защиты посредством ACL.
Механизмы, используемые на практике, более сложны. Для начала мы показали
лишь три права: на чтение, на запись и на исполнение. Существуют и другие
права: например, широко распространены права на копирование и уничтожение
объекта. Они имеются у любого объекта независимо от его типа. Возможны права,
специфичные для конкретных объектов. Например, у объекта «Почтовый ящик�
существует право на добавление сообщения, а у объекта «каталог� - право сор­
тировки в алфавитном порядке.
До настоящего момента мы говорили о АСL-записях, соответствующих отдель­
ным пользователям. Многие системы поддерживают концепцию групп поль­
зователей. Группы имеют имена и могут включаться в ACL. Для групп поддер­
живаются два вида семантики. В некоторых системах у каждого процесса есть
идентификатор пользователя (UID) и идентификатор группы ( GID). В этом слу­
чае АСL-запись выглядит следующим образом:
U I D l , G I D l : пра ва l ; U I D 2 , G I D 2 : пра в а 2 ; . . .

При получении запроса на доступ к объекту проверяется наличие UID и GID


процесса в ACL, и, если эти идентификаторы удается найти, применяются пере­
численные права. В противном случае в доступе отказывается.
Подобное использование групп приводит к концепции роли. Рассмотрим систе­
му, в которой пользователь t ana является системным администратором, а сле­
довательно, входит в группу sy s adm. Пусть в компании имеется несколько клу­
бов для сотрудников, и t ana входит в сообщество любителей голубей. Члены
этого сообщества принадлежат группе p i g f an и имеют возможность управлять
собственной базой данных с компьютеров компании. В табл. 5.4 приведен фраг­
мент списка управления доступом.

Таблица 5 .4. Два списка управления доступом


Файл Список управления доступом
Password tапа, sysadm: RW
Pigeoп_data Ьill, pigfaп: RW; tапа, pigfaп: RW;
Если t ana попытается получить доступ к одному из этих файлов, результат за­
висит от того, к какой группе этот пользователь принадлежит в текущий момент.
Возможно, при входе система просит его указать группу, в которую он хотел бы
войти; не исключено, что для разделения групп каждой из них даже соответству­
ют свои имена входа и (или) пароли. Цель такой схемы - лишить пользователя
t ana возможности доступа к файлу паролей, если он вошел в систему как член
сообщества любителей голубей. Он может работать с файлом паролей только как
системный администратор.
5.5. Меха н измы за щиты 595

В некоторых случаях пользователь получает доступ к определенным файлам не­


зависимо от группы, к которой он принадлежит. Такой режим задается с помо­
щью маски, обозначающей любую группу. Например:
tana , * : RW

Запись подобного вида дает пользователю t ana доступ к файлу паролей незави­
симо от того, в какой группе он находится в текущий момент.
Еще одна возможность заключается в предоставлении пользователю доступа
к объекту при условии, что надлежащим правом обладает хотя бы одна группа,
в которую он входит. В этом случае пользователю, состоящему в нескольких
группах, не нужно у1<азывать текущую группу при входе в систему. В любой мо­
мент времени учитываются все группы, членом которых он является. Недоста­
ток такого подхода - меньшая изолированность: t ana имеет возможность ре­
дактировать файл паролей во время общения с любителями голубей.
Применение групп и масок позволяет выборочно блокировать доступ пользова­
телей к файлам. Например:
virgi l , *: ( nог.е ) ; * . * : RW

Эта запись дает возможность всем, кроме пользователя vi rgi l , читать и писать
в файл. Такой эффект достигается потому, что записи сканируются в порядке
перечисления и применяется первая подходящая (остальные же записи даже не
проверяются) . Для v i rg i l такой записью является первая; в ней указано отсут­
ствие каких-либо прав (none ) . На этом поиск заканчивается, и то, что все ос­
тальные пользователи имеют права доступа, остается •за кадром• .
Другой способ работы с группами предполагает, что в АСL-записях задействова­
ны не пары (UID, GID), а один из этих идентификаторов. Например, для файла
p i ge on_dat a запись может выглядеть следующим образом:
debbi e : RW ; phi l : RW ; p i g fan : RW

Она означает, что пользователи debЬ i e , phi l и все члены группы p i g fan име­
ют право чтения и записи.
Иногда владелец файла хочет отметить разрешения, назначенные пользователю
или группе. Списки управления доступом позволяют относительно легко отме­
нить назначенные ранее права. Все, что нужно сделать, - это отредактировать
их. Тем не менее если список проверяется лишь при открытии файла, то измене­
ния возымеют действие лишь при следующем вызове open. К любому открыто­
му файлу применяются права, актуальные на момент открытия, даже если позд­
нее доступ пользователю запрещается.

5 . 5 . З . М андаты
Матрица, показанная на рис. 5.23, может также храниться по рядам. Здесь с каж­
дым процессом ассоциирован список разрешенных для доступа объектов вместе
с информацией о том, какие операции разрешены, другими словами, это - домен
защиты объекта. Такой список называется списком мандатов ( Capabllity List,
C-list, рис. 5.25), а его элементы - мандатами [36, 44) .
\j
596 Глава 5. Файловые систем ы

П �ц

0 0 Пространство
пользователя

}
1 1 1
1 1 1

� �
� F1 :R
R F2:RW RX
П �mpa"cmo ""ра
§]
§]
FЗ:RWX " \
Список мандатов
Рис . 5 . 25 . У каждого процесса имеется список мандатов

Каждый мандат наделяет владельца определенными правами на определенном


объекте. На рис. 5.25, к примеру, процесс, владельцем которого является пользо­
ватель А, может читать файлы F1 и F2. Как правило, мандат состоит из иденти­
фикатора файла (или, в общем случае, объекта) и битовой карты для различных
прав. В UNIХ-подобных системах в качестве идентификатора файла, вероятно,
выступает номер индексного узла. Списки мандатов сами по себе являются объ­
ектами, и на них могут указывать другие списки мандатов, что позволяет совме­
стно использовать поддомены.
Очевидно, что списки мандатов должны быть защищены от искажения пользо­
вателями. Известны три способа защиты. Для первого требуется теговая архи­
тектура, то есть аппаратно реализованная структура памяти, в которой у каждо­
го слова памяти есть дополнительный (теговый) бит, сообщающий, содержит ли
данное слово памяти мандат или нет. Теговый бит не используется в обычных
командах процессора, таких как арифметические команды или команды сравне­
ния. Он может быть изменен только программой, работающей в режиме ядра (то
есть операционной системой ). Машины с теговой архитектурой реализованы
и могут быть эффективными [46 ] . Примером такой машины, получившей широ­
кое распространение, является I B M AS/400.
Особенность второго способа в том, что список хранится внутри операционной
системы. При этом к элементам списка можно обращаться по их позиции в списке.
Такая форма адресации напоминает использование дескрипторов файла в UNIX.
Именно таким образом работала система Hydra [ 1 29].
Третий способ заключается в хранении списка мандатов в пользовательском
пространстве, но в зашифрованном виде, так чтобы пользователь не сумел изме­
нить эту информацию. Этот подход особенно полезен для распределенных систем
и работает следующим образом. Когда клиентский процесс посылает сообщение
удаленному серверу (например, файловому), чтобы создать для него объект, сер­
вер создает объект и генерирует длинное случайное число - код проверки. Под
объект резервируется запись в файловой таблице сервера, в которой поле про­
верки хранится вместе с адресами дисковых блоков и прочей информацией. Вы­
ражаясь в терминах UNIX, поле проверки хранится на сервере в индексном узле.
5 . 5 . Механизмы за щиты 597

Оно никогда не отправляется обратно пользователю и никогда не передается по


сети. Сервер генерирует и возвращает пользователю мандат в форме, представ­
ленной на рис. 5.26.

Сервер Объект П рава f(Объект, П рава, Проверка)

Рис. 5 . 26 . Криптографически защищенный мандат


Мандат включает идентификатор сервера, номер объекта (индекс в таблицах
сервера, то есть, по существу, номер индексного узла) и права в виде битовой
карты. Последнее поле образовано путем преобразования объединения объекта,
прав и поля проверки.
Когда пользователь желает получить доступ к объекту, он отсылает серверу ман­
дат, включая его в запрос. Сервер извлекает номер объекта и использует его в ка­
честве индекса своих таблиц для поиска. Затем он вычисляет значение /( объект,
права, поле проверки), где первые два параметра взяты из мандата, а третий
извлечен из собстве;нных таблиц. Если результат совпадает с четвертым полем
мандата, запрос удовлетворяется, в противном случае отклоняется. Если пользо­
ватель пытается получить доступ к чужому объекту, ему не удастся подделать
четвертое поле, поскольку он не знает значения поля проверки.
Пользователь может запросить у сервера более ограниченный мандат, например
только на чтение. Сначала сервер проверит корректность мандата. Если провер­
ка успешна, он вычислит значение /(объект, новые права, поле проверки) и сге­
нерирует новый мандат, поместив вычисленное значение в четвертое поле. Обра­
тите внимание на то, что используется первоначальное значение поля проверки;
это объясняется тем, что от него зависят другие важные мандаты.
Новый мандат возвращается запросившему его процессу. Теперь пользователь
может послать этот мандат другу, просто написав ему сообщение. Если друг ус­
тановит биты прав, которые должны быть сброшены, сервер определит это, по­
скольку значение f изменится с изменением прав. Поскольку другу неизвестно
правильное значение поля проверки, он не сможет сфальсифицировать мандат,
соответствующий новым правам. Описанная схема была разработана для рас­
пределенной операционной системы Amoeba и активно в ней применялась [ 1 16).
Помимо специфических разрешений, зависящих от конкретного объекта, напри­
мер чтение и исполнение, мандаты (как системные, так и защищенные шифрова­
нием) включают в себя, как правило, общие права, то есть разрешения выполне­
ния действий, применимых ко всем объектам. Примеры:
1. Копирование мандата: создание нового мандата для того же объекта.
2. Копирование объекта: создание дубликата объекта с новым мандатом.
3. Удаление мандата: удаление записи в списке мандатов, объект при этом не за­
трагивается.
4. Удаление объекта: удаление объекта и мандата.
598 Глава 5. Файловые системы

Напоследок стоит отметить, что аннулировать доступ к объекту в мандатных


системах, реализованных на уровне ядра, довольно сложно. Системе трудно най­
ти все мандаты для конкретного объекта, чтобы забрать их, так как они могут
храниться в любом месте диска. Один из методов заключается в том, что мандат
должен указывать на объект не непосредственно, а косвенно. Система может в лю­
бой момент разорвать связь между объектом, таким образом аннулируя все ман­
даты. (Когда мандат позднее появится в системе, пользователь обнаружит, что
косвенный объект теперь указывает на нулевой объект.)
В системе Amoeba аннулирование мандатов выполняется легко. Все, что для это­
го требуется, - это изменить контрольное поле, хранимое с объектом. « Одним
щелчком• все существующие мандаты объявляются недействительными. Одна­
ко ни одна схема не обеспечивает выборочного аннулирования мандатов, то есть
невозможно, например, отменить мандат Джона, не затронув всех остальных
пользователей. Этот недостаток присущ всем мандатным системам.
Еще одну проблему представляет борьба с t:итуацией, когда владелец корректно­
го мандата передает его множеству своих лучших друзей. Средства управления
ядром, как в Hydra, позволяют решить этот вопрос, однако они не приносят хо­
роших результатов в распределенных системах, подобных Amoeba.
В то же время мандаты с элегантностью устраняют проблему нарушения без­
опасности мобильным кодом. При запуске чужой программы последняя получа­
ет список мандатов, содержащий лишь те мандаты, которыми ее хочет наделить
владелец машины. К примеру, программе может быть разрешено выводить данные
на экран, а также читать и писать в файлы единственного, специально выделен­
ного для нее каталога. Если мобильный код будет помещен в процесс с подобны­
ми ограниченными возможностями, он не сможет получить доступ к другим ре­
сурсам. Выполнение кода с максимально ограниченным набором прав доступа
называется принципом наименъшеzо уровня привилеzий. Этот принцип является
основополагающим при разработке безопасных систем.
Подводя краткий итог, отметим, что возможности списков управления доступом
и списков мандатов дополняют друг друга. Мандаты обладают высокой эффек­
тивностью, поскольку не требуют проверки запросов процессов типа «открыть
файл, ссылка на который имеется в мандате 3•. Использование ACL в подобной
ситуации привело бы к поиску, и, возможно, долгому. В отсутствие поддержки
групп, для разрешения чтения файла всеми пользователями в ACL пришлось бы
полностью перечислить их. Кроме того, мандаты позволяют с легкостью изоли­
ровать процесс, в то время как ACL не предоставляют такой возможности. Нако­
нец, если объект удаляется, а мандат остается, возникают проблемы. Списки
управления доступом лишены подобного недостатка.

5 . 5 . 4 . Секретн ые канал ы
Даже при наличии списков управления доступом и мандатов в системе безопасно­
сти могут существовать бреши. В данном разделе мы поговорим о существовании
утечек информации в системах, для которых строго математически доказано, что
подобные утечки невозможны. Изложенные здесь идеи впервые высказаны в [72].
5.5 . Механизм ы за щ иты 599

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


темы разделения времени, но те же идеи применимы в локальной сети и в других
многопользовательских средах. В ее чистом виде в эту модель входят три про­
цесса, работающих на одной защищенной машине. Первый процесс представляет
собой клиента, который доверяет выполнить некоторое задание второму процес­
су, серверу. Клиент и сервер доверяют друг другу не полностью. Например, ра­
бота сервера заключается в том, чтобы помочь клиентам заполнить их налоговые
декларации. Клиенты беспокоятся, что сервер запишет в тайную тетрадь их фи­
нансовую информацию, а затем, например, продаст эти сведения. Сервер озабо­
чен тем, что клиенты украдут ценную программу подсчета налогов.
Третий процесс, являясь сообщником сервера, намеревается украсть конфиден­
циальные сведения клиента. Владельцем этого процесса, как и сервера, обыч­
но является один и тот же человек. Все три процесса показаны на рис. 5.27.
Цель данной модели - помочь разработать систему, в которой невозможна утеч­
ка к сообщнику сервера информации, полученной сервером у клиента. В [72] это
названо проблемой изоляции.

Клиент Сервер Сообщник Инкапсулированный сервер

Ядро Тайный
канал
а б
Проблема изоляции:
Рис . 5 . 27 . а клиент, сервер и сообщник сервера; б даже от
- -

изолированного сервера информация может попасть к сообщнику по секретному каналу


С точки зрения разработчика системы задача заключается в изоляции сервера
таким образом, чтобы он не мог передать информацию сообщнику. С помощью
матрицы защиты несложно гарантировать, что сервер не будет общаться с со­
общником, записывая данные в файл, к которому у сообщника есть доступ для
чтения. Вероятно, можно также гарантировать отсутствие взаимодействия меж­
ду процессами сервера и сообщника при помощи системного механизма.
К сожалению, утечка информации может происходить по секретным каналам.
Например, сервер может передавать последовательность нулей и единиц, коди­
руя единицы интервалами высокой активности процессора, а нули - интервала­
ми бездействия.
Сообщник может принимать этот поток, тщательно отслеживая время отклика
сервера. Как правило, время отклика меньше у простаивающего сервера, то есть
меньшее время отклика означает ноль, большее - единицу. Этот канал связи на­
зывается секретным. Он показан на рис. 5.27, б.
Конечно, секретный канал по определению зашумлен, но если применить по­
мехоустойчивое кодирование (например, код Хэмминга), информация может
600 Глава 5. Файловые системы

приниматься сообщником с высокой степенью надежности. Пропускная способ­


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

этой цели подойдет любой способ снижения производительности системы в те­


чение определенного интервала времени. Если система позволяет блокировать
файлы, тогда сервер может блокировать некий файл, что будет означать, на­
пример, 1, и разблокировать его, кодируя, таким образом, О. Некоторые системы
позволяют процессу определить, что файл блокирован, даже если у него нет
доступа к этому файлу. Такой секретный канал представлен на рис. 5.28, где бло­
кировка файла устанавливается и снимается через определенные интервалы вре­
мени, известные серверу и его сообщнику. В проиллюстрированном примере по
секретному битовому потоку передается последовательность 1 1 О 1 О 1 00.

Сервер ___...

Сервер Сервер
блокирует разблокирует
файл, чтобы файл, чтобы
послать 1 послать О
О � Передаваемый
битовый поток
Сообщник ___...
Время •
Рис. 5 . 28 . Секретный канал, основанный на блокировке файлов

Блокировка и разблокировка предопределенного файла S не является слишком


зашумленным каналом, однако требует точного согласования по времени, если
только передача не ведется очень медленно. Надежность и производительность
можно повысить, введя протокол с подтверждениями. В этом протоколе ис­
пользуются два дополнительных файла F1 и F2, блокируемых соответственно
сервером и его сообщником с целью синхронизации. После блокировки или раз­
блокировки файла S сервер инвертирует состояние файла F1, показывая, что бит
передан. Считав бит, сообщник инвертирует состояние файла F2, информируя
сервер о том, что готов к приему следующего бита. Затем он ждет инвертирова­
ния F1, которое укажет ему на присутствие очередного бита в S. Поскольку со­
гласование по времени больше не нужно, протокол является абсолютно надеж­
ным даже в загруженной системе. Скорость его работы определяется скоростью
планирования двух процессов. Чтобы повысить ее, можно использовать два файла
в одном битовом интервале или увеличить ширину канала до байта, введя 8 сиг­
нальных файлов S0-57.
5 . 6 . Обзор файловой системы M I NIX 3 60 1

Для передачи скрытых сигналов также применимы захват и освобождение внеш­


них ресурсов (например, магнитофонов, плоттеров и т. д.). В системе UNIX сервер
может создавать файл, что будет означать 1 , и удалять его, передавая О. Сообщ­
ник может проверять наличие файла при помощи системного вызова ac c e s s .
Этот системный вызов будет работать, даже если у сообщника нет доступа к со­
здаваемому сервером файлу. К сожалению, помимо таких •семафоров� сущест­
вует множество других возможностей для создания секретного канала.
В [72) также упомянуто о еще одном возможном потайном канале связи, но уже
между сервером и человеком. Например, сервер может сообщать, сколько работы
он сделал для клиента, выставляя ему счет. Если такой счет составляет 100 дол­
ларов, а доход клиента равен 53 ООО долларов, то сервер может сообщить об этом
в виде счета в 100,53 доллара.
Обнаружить все секретные каналы чрезвычайно трудно, не говоря уже об их бло­
кировке. На практике в наших силах немногое. Добавление процесса, случайным
образом вызывающего ошибки отсутствия страниц или снижающего производи­
тельность системы другим способом, чтобы уменьшить пропускную способность
секретных каналов, в качестве варианта решения проблемы не импонирует.

5 . 6 . Обзор файловой сист е м ы M I N IX 3


Как и любая другая файловая система, файловая система MINIX 3 обязана решать
рассмотренные нами задачи. Она должна выделять и освобождать пространство
для файлов, следить за блоками на диске и за свободным местом, предоставлять
какие-либо средства защиты от несанкционированного доступа и т. д. В оставшейся
части главы мы более подробно коснемся того, как эти задачи решаются в MINIX 3.
В первой части главы мы, ради большей общности, часто ссылались на UNIX, а не
на MINIX 3, хотя интерфейс этих двух систем практически идентичен. Теперь
же мы сосредоточимся на внутреннем устройстве MINIX 3. Информацию о внут­
реннем устройстве UNIX вы можете найти в дополнительной литературе [ 4, 82,
1 19, 124).
В MINIX 3 файловая система - это просто большая С-программа, работающая
в пользовательском пространстве (см. рис. 2. 14). Чтобы прочитать или записать
файл, пользовательские процессы отправляют файловой системе сообщения, гово­
рящие, что нужно сделать. Файловая система выполняет свою работу и оmравля­
ет обратно ответ. Фактически такая система представляет собой сетевой файловый
сервер, оказавшийся на той же машине, что и обращающийся к нему процесс.
Такое устройство имеет несколько важных следствий. Прежде всего, файловую
систему можно модифицировать, экспериментировать с ней и тестировать ее прак­
тически независимо от остальных частей MINIX 3. Далее, файловую систему
можно легко перенести на другой компьютер, где есть компилятор С, скомпили­
ровать ее там и использовать как отдельный удаленный UNIХ-подобный файло­
вый сервер. Единственные изменения коснутся того, как отправляются и прини­
маются сообщения, поскольку это делается по-разному на разных платформах.
602 Глава 5. Файловые системы

В последующих разделах мы представим обзор многих ключевых компонентов


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

5 . 6 . 1 . Сообщения
Файловая система понимает 39 типов сообщений, запрашивающих различные
действия. Все они, кроме двух, соответствуют системным вызовам MINIX 3. Ис­
ключения составляют два сообщения, генерируемые другими компонентами
MINIX 3. Что касается системных вызовов, то 31 из них принимаются от пользо­
вательских процессов, 6 сообщений соответствуют системным вызовам, обраба­
тываемым сначала менеджером памяти, который затем, чтобы завершить работу,
вызывает файловую систему. Еще 2 сообщения также обрабатываются файловой
системой. Все эти сообщения перечислены в табл. 5.5.

Сообщения файловой системы. Имя файла всегда передается как указатель


Табли ца 5 . 5 .
на строку. Ответное значение, отмеченное как «Состояние», равно О К или ERROR
Сообщение Входные параметры Ответное значение
Соо бщения от пользователя
access Имя файла, режим доступа Состояние
chdir Имя нового рабочего каталога Состояние
chmod Имя файла, новая спецификация доступа Состояние
chowп Имя файла, новый владелец и группа Состояние
chroot Имя нового корневого каталога Состояние
close Дескриптор закрываемого файла Состояние
creat Имя создаваемого файла, режим Дескриптор файла
dup Дескриптор файла (DUP2 требует два дескриптора) Новый дескриптор файла
fcпtl Дескриптор файла, код функции, аргументы Зависит от функции
fstat Имя файла, буфер Состояние
ioctl Имя файла, код функции, аргументы Состояние
liпk Имя файла, на который создается ссылка, имя Состояние
ССЫЛКИ
lseek Дескриптор файла, смещение, место, откуда Новое положение
считать смещение
mkdir Имя файла, спецификация доступа Состояние
mkпod Имя каталога или признак специального файла, Состояние
режим доступа и адрес
mouпt Имя специального файла, точка монтирования, Состояние
флаг только для чтения
ореп Имя открываемого файла, флаг r/w Дескриптор файла
pipe Указатель на два файловых дескриптора Состояние
(модифицируются)
5.6 . Обзор файловой системы MI NIX 3 603

Сообщен ие Входные параметры Ответное значение


read Дескриптор файла, буфер, сколько байтов Количество прочитанных
байтов
reпame Имя файла, имя файла Состояние
rmd1r Имя файла Состояние
stat Имя файла, буфер для сохранения информации Состояние
stime Указатель на текущее значение времени Состояние
sупс Нет Всег1JР. ОК
time Указатель на место, кyfJP. будет записано текущее Состояние
значение времени
times Указатель на буфер для записи времени работы Состояние
процесса и потомков
umask Дополнение к маске режимов доступа BceгfJP. ОК
umouпt Имя демонтируемого специального файла Состояние
uпliпk Имя файла Состояние
utime Имя файла, значения времени для него BceгfJP. О К
write Дескриптор файла, буфер, сколько байтов Количество записанных
байтов
Соо бщения от менеджера процессов
ехес PID Состояние
exit PID Состояние
fork PID родителя, PID потомка Состояние
setgid PID, действительное и эффективное значения GID Состояние
setsid PID Состояние
setuid PID, действительное и эффективное значения UID Состояние
Про чие соо бщения
revive Процесс, который будет оживлен Ответного сообщения нет
uпpause Процесс, который нужно проверить (Пояснения в тексте)
Файловая система имеет ту же основную структуру, что и менеджер памяти или
драйверы ввода-вывода. В ней есть главный цикл, ожидающий сообщений. Ко­
гда сообщение принято, определяется его тип, который затем используется как
индекс в таблице указателей на функции, обрабатывающие различные систем­
ные вызовы. Затем найденная процедура вызывается, она выполняет свои дейст­
вия и возвращает код завершения. Далее файловая система отправляет ответное
сообщение процессу, сделавшему вызов, и возвращается в начало цикла ожидать
следующее сообщение.

5 . 6 . 2 . Структура файловой системы


Файловая система MINIX 3 представляет собой самостоятельную логическую
сущность с индексными узлами, каталогами и блоками данных. Она может хра­
ниться на любом блочном устройстве, например на дискете или жестком диске
(или разделе жесткого диска). В любом случае, файловая система имеет одну
и ту же структуру. Рисунок 5.29 иллюстрирует эту структуру на примере дискеты
604 Глава 5. Файловые систем ы

или небольшого жесткого диска с блоком размером 1 Кбайт и индексными узла­


ми в количестве 64. В этом простом случае битовая карта зон занимает 1 Кбайт.
Она позволяет отслеживать не более 8 1 92 8-килобайтных зон (блоков), а, следо­
вательно, размер файловой системы составляет не более 8 Мбайт. Даже для гиб­
кого диска 64 индексных узла делают число файлов весьма ограниченным, по­
этому под индексные узлы обычно отводят не 4 блока, как на рисунке, а больше.
На практике 8 блоков было бы лучше, однако в этом случае наша диаграмма ут­
ратила бы наглядность. Разумеется, для современного жесткого диска и индекс­
ные узлы, и битовые карты зон занимают значительно больше, чем один блок.
Относительный размер различных компонентов (рис. 5.29) варьируется от од­
ной файловой системы к другой и зависит от ее размера, максимального числа
файлов и других параметров. Тем не менее наличие всех компонентов и их поря­
док всегда неизменны.

Загрузочный СуnеР,бnок
бnок
1-уэлы Один бnок диска
,--'---,

Данные
Битовая Битовая
карта i-уэлов карта зон
Рис. 5 . 29 . Структура дискеты или небольшого жесткого диска с 64 индексными узлами
и размером блока 1 Кбайт (то есть два подряд идущих сектора по 5 1 2 байт
рассматриваются как один блок)
Любая файловая система начинается с заzрузачноzо блока. Этот блок содержит
исполняемый код. Размер загрузочного блока всегда составляет 1 024 байта (два
сектора диска), хотя в других случаях в MINIX 3 могут использоваться блоки
большего размера (что и имеет место по умолчанию). При включении компьютера
его аппаратное обеспечение считывает содержимое загрузочного блока в память
и исполняет его. Код в загрузочном блоке инициирует процесс загрузки самой
операционной системы. После того как система загружена, загрузочный блок боль­
ше не используется. Для загрузки системы подходит не каждый дисковый накопи­
тель, но ради единообразия структуры на каждом блочном устройстве резерви­
руется загрузочный блок. Худшее, к чему может привести такая стратегия, - это
потеря одного блока. Чтобы аппаратное обеспечение не пыталось загрузиться с уст­
ройства, не предназначенного для загрузки, в известное заранее место загрузочно­
го блока записывается сигнатура ( -«маzическое» число) в том и только том случае,
если блок содержит исполняемый код. Аппаратура (а в действительности, код
BIOS) откажется загружаться с устройства, если загрузочный блок не имеет такой
сигнатуры. Таким образом, случайный «мусор� в качестве программы не запустится.
Суперблок содержит информацию, описывающую структуру файловой системы.
Его размер, как и размер загрузочного блока, составляет 1 024 байта независимо
от размера блоков, используемых в остальной части файловой системы. Струк­
тура суперблока показана на рис. 5.30.
5 . 6 . Обзор файловой систем ы M I N IX 3 605

Число i-узлов
(не используется)
Число блоков битовой карты i-узлов
Число блоков битовой карты зон
Первая зона данных
Log, (количество блоков в зоне)
Присутствуют
и на диске, Замещение страниц
и в памяти Максимальный размер файла
Количество зон
Магическое число
Замещение страниц
Размер блока (в байтах)
Подверсия ФС �
Указатель на i-узел для корня,
смонтированной файловой системы
Указатель на i-узел,
смонтированный выше
1-узлов на блок
Номер устройства
Есть в памяти, Флаг только чтение
но отсутствуют Флаг направления байтов в ФС
на диске Версия ФС
Прямых зон на индексный узел
Косвенных зон на косвенный блок
Первый свободный бит в битовой
карте i-узлов
Первый свободный бит в битовой
карте зон
Рис. 5 . 30 . Структура суперблока в MINIX

Основное назначение суперблока - сообщить файловой системе, насколько ве­


лики отдельные ее части. Зная размер блока и число индексных узлов, несложно
подсчитать размер битовой карты индексных узлов и количество блоков индекс­
ных узлов. Например, если блок имеет размер 1 Кбайт, каждый блок битовой
карты будет содержать 1 Кбайт (8 Кбит), то есть может быть использован для
отслеживания состояния 8 1 9 2 индексных узлов. ( Строго говоря, первый блок
битовой карты описывает только 8 1 9 1 индексный узел, так как О-го индексного
узла нет, но бит в битовой карте для него все равно выделяется. ) Если всего име­
ется 1 О ООО индексных узлов, то потребуется два блока для хранения битовой
606 Глава 5. Файловые системы

карты. Так как каждый индексный узел занимает 64 байта, блок размером 1 Кбайт
может содержать до 16 индексных узлов. При наличии 64 используемых индекс­
ных узлов для их хранения потребуется 4 дисковых блока.
Позже мы подробно объясним различие между дисковыми блоками и зонами,
сейчас же достаточно сказать, что место на диске может вьщеляться частями (зона­
ми) из 1, 2, 4, 8 или, в общем случае, 2п блоков. Битовая карта зон позволяет от­
слеживать свободное пространство в зонах, а не в блоках. Для стандартных гибких
дисков, используемых MINIX 3, размер зоны совпадает с размером блока ( 1 Кбайт),
поэтому для таких устройств в первом приближении можно считать, что зона -
это то же самое, что и блок. Пока мы позже в этой главе не приступим к обсужде­
нию деталей выделения места, вы можете считать, что эти понятия эквивалентны.
Обратите внимание, что количество блоков в зоне не хранится в суперблоке, так
как оно нигде не требуется. Все, что нужно, - это логарифм по основанию два от
этого числа, который используется как значение сдвига при преобразовании
блоков в зоны, и наоборот. Например, если зона содержит 8 блоков, log2 8 3. =

То есть, чтобы найти зону, содержащую блок 1 28, нужно сдвинуть 1 28 на три бита
вправо (получится зона 1 6).
Битовая карта зон содержит только зоны, занимаемые данными (то есть в нее не
попадают блоки, хранящие битовые карты и индексные узлы), причем первая зо­
на данных соответствует биту 1 в битовой карте. Как и в случае с картой индекс­
ных узлов, нулевой бит не используется, а значит, первый блок битовой карты
описывает 8 1 9 1 зон, а последующие - 8 1 92 каждый. Если вы посмотрите на бито­
вые 1<арты только что отформатированного диска, вы увидите, что в обеих бито­
вых картах (зон и индексных узлов) установлено два бита. Первый из них соот­
ветствует несуществующей 0-й зоне или индексному узлу. Второй соответствует
индексному узлу и зоне корневого каталога устройства, которая создается при
создании файловой системы.
Информация, хранящаяся в суперблоке, избыточна, так как иногда она требует­
ся в одной форме, а иногда в другой. Так как на размещение суперблока отводит­
ся 1 Кбайт места, имеет смысл хранить информацию во всех необходимых пред­
ставлениях, а не преобразовывать ее в ходе работы. Например, номер первой
зоны данных на диске можно вычислить, исходя из размера блока, размера зоны,
числа индексных узлов и числа зон, но удобнее просто хранить это значение в су­
перблоке. Оставшаяся часть суперблока все равно не используется, потому вы­
деление в нем одного лишнего слова ничего не стоит.
Когда загружается MINIX 3, суперблок с корневого устройства считывается в таб­
лицу в памяти. Аналогичным образом, при монтировании других файловых сис­
тем их суперблоки также помещаются в память. В этой таблице есть несколько
полей, которых нет на диске. Среди них флаг, индицирующий, что разрешено
только чтение, флаг, позволяющий установить нестандартный порядок следова­
ния байтов, а также поля, предназначенные для ускорения доступа. Они указы­
вают положение в битовых картах, ниже которого все биты установлены (то есть
заняты). Кроме того, здесь есть поле, описывающее устройство, с которого при­
шел данный суперблок.
5.6. Обзор файловой системы M I N IX 3 607

Прежде чем файловая система MINIX 3 сможет использовать диск, он должен


быть приведен в соответствие структуре, показанной на рис. 5.29. Для построе­
ния файловой системы имеется утилита mk f s . Она может быть вызвана, на­
пример, так:
mk f s / dev/ f d l 1 4 4 0

Такая команда создаст файловую систему из 1440 блоков н а гибком диске в при­
воде 1 . Ей можно указать файл-прототип с перечислением каталогов и файлов,
подлежащих включению в созданную файловую систему. Эта же команда запи­
сывает в суперблок необходимую сигнатуру, идентифицирующую файловую
систему как файловую систему MINIX. MINIX развивается, и в ранних версиях
некоторые атрибуты файловой системы (например, размер индексного узла) бы­
ли другими. При попытке монтировать дискету, имеющую другой формат, на­
пример дискету MS-DOS, системный вызов mount , проверяющий суперблок на
наличие сигнатуры, сообщит об ошибке.

5 . 6 . З . Битовые карты
MINIX 3 следит за свободными и занятыми индексными узлами и зонами при
помощи двух битовых карт. Когда удаляется файл, несложно подсчитать, какой
бит в битовой карте соответствует освободившемуся индексному узлу, и найти
его при помощи обычного механизма кэширования. В найденном блоке бит, со­
ответствующий освободившемуся индексному узлу, сбрасывается. Зоны освобо­
ждаются точно так же, только используется битовая карта зон.
Логически, при создании файла файловая система должна последовательно про­
смотреть битовые карты, чтобы найти первый свободный индексный узел, кото­
рый будет выделен новому файлу. Фактически же, хранящийся в памяти супер­
блок содержит поле, указывающее на следующий свободный индексный узел,
поэтому требуется искать свободный индексный узел, начиная с этого положе­
ния (зачастую свободным оказывается следующий узел). Аналогичным образом,
когда индексный узел освобождается, проверяется, есть ли перед ним другие
свободные, и при необходимости обновляется значение указателя. Если оказалось,
что все индексные узлы на диске заняты, процедура поиска указывает на 0-й эле­
мент. Именно поэтому 0-й индексный узел не используется (другими словами,
он является индикатором заполнения диска). ( Когда программа mk f s создает
новую файловую систему, она обнуляет индексный узел О и устанавливает млад­
ший бит в битовой карте, соответственно, файловая система никогда не пытается
выделить этот блок.) Все, что только что было сказано для индексных узлов, от­
носится и к зонам. Когда необходимо дисковое пространство, логически ищется
первая свободная зона, начиная с начала, а чтобы избежать ненужного последо­
вательного поиска, поддерживается указатель на первую свободную зону.
Теперь мы можем объяснить разницу между зонами и блоками. В основе идеи
зон лежит попытка гарантировать, что блоки одного файла расположены на одном
цилиндре - это дает возможность повысить производительность за счет после­
довательного чтения. Для этого за один раз выделяется несколько блоков. Если,
608 Глава 5 . Файловые системы

например, размер блока равен 1 Кбайт, а зоны - 4 Кбайт, битовая карта зон от­
слеживает зоны, а не блоки. Диск объемом 20 Мбайт будет разбит на 5 К зон по
4 Кбайт, и в битовой карте зон, таким образом, будет 5 Кбит.
Большинство файловых систем работают с блоками. Обмен данными с диском
всегда производится целыми блоками, кэш также оперирует блоками. Только та
небольшая часть файловой системы, которая работает с физическим адресом
(то есть с битовой картой зон и индексными узлами), знает о зонах.
В процессе разработки файловой системы MINIX 3 необходимо было принять
некоторые решения о ее структуре. В 1 985 году, когда была задумана операцион­
ная система MINIX, диски имели небольшой объем, и ожидалось, что у многих
пользователей будет только дисковод. Поэтому было решено в первой версии
файловой системе ограничить дисковый адрес значением 16 бит, чтобы впоследст­
вии хранить большую их часть в блоках косвенной адресации. При 1 6-разрядном
номере зоны и размере зоны в 1 Кбайт такая система позволяла работать с диска­
ми объемом до 64 Мбайт. В те дни это бьm огромный объем, и казалось, что если
диски станут больше, будет несложно переключиться на зоны размером 2 Кбайт
или 4 Кбайт, не меняя размер блока. Благодаря 1 6-разрядному номеру зоны раз­
мер индексного узла был ограничен значением 32 байта.
Когда широко распространились диски значительно большей емкости, стало оче­
видно, что желательны изменения. Многие файлы имеют объем менее 1 Кбайт,
поэтому увеличение размера блока привело бы к бесполезному расходованию
объема диска из-за того, что записываются и считываются почти пустые блоки,
и к бесполезной трате драгоценной оперативной памяти на кэш. Можно было бы
увеличить размер зоны, но большие зоны означают менее эффективное использова­
ние свободного места на диске, при этом все равно желательно было бы сохранить
эффективность работы с дисками маленького объема. Другой разумной альтерна­
тивой было бы задание разного размера зоны для больших и маленьких дисков.
В конце концов, было принято решение увеличить разрядность дискового ука­
зателя до 32 бит. Благодаря этому файловая система MINIX 2 могла бы работать
с дисками объемом до 4 Тбайт при размере зоны и блока 1 Кбайт и с дисками
объемом до 16 Тбайт при размере блока и зоны 4 Кбайт (в настоящее время
принятом по умолчанию). Однако другие факторы заставили урезать этот раз­
мер (к примеру, 32-разрядные указатели не позволяют выйти за предел 4 Гбайт).
Вместе с увеличением размера индексных узлов требуется увеличивать разряд­
ность дисковых указателей. Это не обязательно плохо: индексный узел MINIX 2
(а теперь и M INIX 3 ) совместим со стандартными индексными узлами UNIX
и имеет пространство для хранения трех значений времени, дополнительных зон
с одинарным и двойным уровнем косвенности, а также место для будущего рас­
ширения зонами с тройным уровнем косвенности.
Зоны приводят еще к одной неожиданной проблеме, которую можно проиллюст­
рировать следующим простым примером, опять же, с зонами размером 4 Кбайт
и блоками 1 Кбайт. Предположим, что имеется файл размером 1 Кбайт, для
которого выделена одна зона. Последние три блока зоны содержат случайный
<1:мусор� , оставшийся от предыдущих файлов, но ничего плохого в этом нет, так
5 . 6 . Обзор файловой системы M I N IX 3 609

как в индексном узле четко указано, что размер файла равен 1 Кбайт. Фактиче­
ски, этот мусор не попадет даже в дисковый кэш, так как чтение производится
блоками, а не зонами. Попытки прочитать информацию после конца файла все­
гда возвращают О вместо данных.
Предположим, что кто-то установил файловый указатель на 32 768 и дописал
1 байт. Теперь размер файла станет равным 32 769. Если после этого установить
файловый указатель на 1 К и выполнить чтение, удастся получить данные, кото­
рые были в данном блоке ранее, что является серьезной угрозой безопасности.
Решение состоит в том, чтобы в ситуации, когда производится запись после кон­
ца файла, явно обнулять все еще не выделенные блоки в зоне, которая ранее бы­
ла последней. Хотя такая ситуация встречается достаточно редко, система долж­
на ее отслеживать, в результате ее код несколько усложняется.

5 . 6 . 4 . И ндексные узл ы
Схема индексного узла MINIX показана на рис. 5.3 1 . Она практически совпадает
со строением индексного узла в UNIX. Имеются девять 32-разрядных указателей
на зоны диска, из которых семь прямых и два косвенных. В MINIX 3 индексный
узел занимает 64 байта, как и в стандартной системе UNIX, поэтому остается ме­
сто для дополнительного десятого указателя (тройного уровня косвенности), хотя
в текущей версии файловой системы он не поддерживается. Время последнего
доступа, модификации и изменения индексного узла в MINIX 3 хранятся стан­
дартным образом, как в UNIX. Последний из этих параметров обновляется прак­
тически при каждой операции, за исключением чтения файла.
Когда файл открывается, его индексный узел считывается в память и помещает­
ся в таблицу индексных узлов, где остается до закрытия файла. Записи в этой
таблице имеют несколько дополнительных полей, которых нет на диске. Напри­
мер, благодаря номеру индексного узла и номеру устройства, откуда он считан,
файловая система знает, куда перезаписать индексный узел, если он изменен
в памяти. Кроме того, у каждого индексного узла имеется счетчик. Если файл
открывается дважды, вторая копия индексного узла в память не копируется,
вместо этого увеличивается на единицу значение счетчика. При закрытии файла
счетчик декрементируется, и только тогда, когда он достигает нуля, индексный
узел удаляется из таблицы в памяти. Если за время нахождения в памяти он бьm
изменен, он записывается на диск.
Главное предназначение индексного узла в том, чтобы хранить сведения о положе­
нии блоков файла на диске. Первые семь номеров зон записываются в индексный
узел напрямую. Таким образом, при стандартных параметрах, когда зоны и блоки
имеют размер 1 Кбайт, файлы размером до 7 Кбайт не требуют использования
блоков косвенной адресации. После 7 Кбайт применяются косвенные блоки при­
мерно так, как это показано на рис. 5.8, за тем исключением, что это только бло­
ки первого и второго уровней косвенности. Если блоки и зоны имеют размер
1 Кбайт, блок первого уровня косвенности содержит 256 записей, что соответствует
61 О Глава 5 . Файловые систем ы

четверти мегабайта. Блок второго уровня косвенности ссылается н а 2 5 6 блоков


первого уровня, обеспечивая доступ к области в 64 Мбайт. При размере блока
в 4 Кбайт блок второго уровня косвенности ссылается на 1 024 х 1 024 блоков, что
составляет более миллиона, а, следовательно, размер файловой системы превы­
шает 4 Гбайт. На практике использование 32-разрядных указателей в качестве
файловых смещений ограничивает максимальный размер файла до величины
2 32 - 1 байт. Таким образом, при блоках размером 4 Кбайт MINIX 3 не нужны
блоки третьего уровня косвенности. Предельный размер файла ограничен разряд­
ностью указателя, а не возможностью следить за достаточным количеством блоков.

1 6 бит
Режим ...._ Тип файла и биты rwx
Число ссылок ...._ Записи в директориях для этого файла
Uid ...._ Идентифицирует владельца файла
Gid ...._ Группа владельца
- Размер файла ...._Количество байт в файле

1
- Время доступа
Время модификации Время измеряется в секундах
,.._
с 1 января 1 970 года
Время изменения состояния
,.._

- зона о
- Зона 1
64 байта
- Зона 2
- Зона 3 � Номера семи первых зон
- Зона 4
,_ Зона 5
,_ Зона 6

}
- Косвенная зона Используются для файлов,
Косвенная зона второго уровня
,_..
занимающих больше семи зон
- Не используется Можно использовать для косвенных зон
� третьего уровня вложенности

Рис. 5 . 3 1 . Структура индексного узла в MINIX

Кроме того, индексный узел хранит информацию о типе файла (обычный файл,
каталог, специальный блочный файл, специальный символьный файл, канал
ввода-вывода) и биты защиты, а также биты S ETU I D и S ETGI D. В поле l i nk
5.6 . Обзор файловой системы M I NIX 3 61 1

фиксируется, сколько разных каталогов ссылаются на данный файл, чтобы фай­


ловая система могла знать, когда следует освободить занимаемое им место. Не
путайте этот счетчик со счетчиком количества открытий файла, обычно разными
процессами (этот счетчик есть только в памяти в таблице индексных узлов).
В заключение отметим, что структуру, представленную на рис. 5.3 1 , можно из­
менять для различных целей. Пример, используемый в MINIX 3, - индексные
узлы для блочных и символьных устройств, которым не нужны указатели зон,
так как не требуется ссылаться на области данных диска. Главный и вспомога­
телы,1ый номера устройств хранятся в пространстве нулевой зоны. Индексный
узел также можно использовать для хранения данных небольшого файла (непо­
средственного файла), хотя эта возможность в MINIX 3 не реализована.

5 . 6 . 5 . Кэш блоков
Для повышения производительности файловой системы в MINIX 3 применяет­
ся кэширование блоков. Кэш реализован в виде массива буферов, каждый из ко­
торых состоит из заголовка, содержащего указатели, счетчики и флаги, и тела -
области памяти для хранения одного блока. Все свободные буферы связаны друг
с другом в двунаправленный список, отсортированный в направлении от послед­
них использовавшихся ( Most Recently Used, MRU) к дольше всего не использо­
вавшимся ( Least Recently Used, LRU). Это иллюстрирует рисунок 5.32.

Хеш-таблица Конец (MRU)

Рис. 5.32. Связанный список кэша блоков


С целью быстрого выяснения, находится нужный блок в кэше или нет, приме­
няется хеш-таблица. Все буферы, имеющие одинаковое хеш-значение k, связаны
в однонаправленный список, на который ссылается запись k хеш-таблицы. Сдела­
но так по той причине, что применяемая хеш-функция просто извлекает п млад­
ших битов из номера блока, и хеш-значения могут совпадать. Каждый буфер
принадлежит одной из цепочек. Конечно, сразу после загрузки MINIX 3 все
буферы свободны, поэтому все они находятся в одной цепочке, на которую ссы­
лается нулевая запись хеш-таблицы. Все остальные записи таблицы в это время
содержат нулевые указатели. Но после того как система начинает работу, буфе­
ры исключаются из нулевой цепочки и формируются новые цепочки.
Если файловой системе необходим блок, она вызывает функцию get_Ы o ck.
Эта функция вычисляет хеш-код блока и ищет его в соответствующем списке.
61 2 Глава 5. Файловые системы

При вызове get_Ь l o c k ей передается как номер блока, так и номер устройства,
и при поиске оба эти параметра сравниваются с соответствующими полями
буферов из цепочки. Если нужный блок найден, увеличивается на единицу зна­
чение счетчика в его заголовке и возвращается указатель на него. Если затребо­
ванный буфер в кэше не обнаруживается, выбирается первый из LRU-списка; он
гарантированно не используется, и содержащийся в нем блок может быть удален
из оперативной памяти.
Когда удаляемый из памяти блок выбран, проверяется один из флагов в его заго­
ловке, означающий, что блок был модифицирован после считывания. Если флаг
установлен, блок записывается на диск. Затем с диска считывается нужный блок,
для этого отправляется сообщение драйверу диска. До тех пор пока запрошен­
ный блок не прочитан, файловая система приостанавливается, а после этого воз­
вращает указатель на прочитанный блок.
По окончании работы запросившей блок процедуры, чтобы освободить блок, она
вызывает другую процедуру, put_Ы o c k. Обычно блок используется сразу по­
сле выделения и затем освобождается, но так как существует вероятность того,
что перед освобождением блока будут сделаны дополнительные запросы, функ­
ция put_Ы o c k сначала уменьшает на единицу счетчик использования и поме­
щает блок обратно в LRU-список в том и только том случае, если счетчик достиг
нуля. Если он имеет ненулевое значение, блок не освобождается.
Один из аргументов функции put_Ы o c k описывает, к какому классу принадле­
жит освобождаемый блок (индексные узлы, каталог, данные). В зависимости от
этого значения принимаются два ключевых решения.
1. Поместить блок в начало или в конец LRU-списка.
2. Записать блок на диск немедленно (если он был модифицирован).
Почти все блоки присоединяются в конец списка в полном соответствии с идеей
сортировки по частоте использования. Исключение составляют блоки виртуаль­
ного диска; поскольку они уже находятся в памяти, хранение их в кэше практи­
чески бессмысленно.
Модифицированный блок не записывается до тех пор, пока не произойдет одно
из двух следующих событий.
1. Блок достиг начала LRU-списка и удаляется из памяти.
2. Выполнен системный вызов sync .
Вызов sync не перебирает элементы LRU-списка. Вместо этого он обрабатывает
массив буферов и записывает модифицированные буферы на диск даже в том
случае, если они еще используются.
Однако есть одно исключение. В старой версии MINIX суперблок модифициро­
вался при монтировании системы, поэтому, чтобы снизить вероятность повреж­
дения файловой системы по причине неожиданного сбоя, он сохранялся без про­
медления. Сейчас суперблок модифицируется лишь в случае, если необходимо
изменить размер виртуального диска во время запуска системы потому, что он
оказался больше, чем размер устройства. Тем не менее чтение и запись супер­
блока отличаются от аналогичных операций для обычного блока, поскольку размер
5.6 . Обзор файловой системы M I N IX 3 61 3

суперблока всегда равен 1 024 байт независимо от размера блоков, подлежащих


кэшированию. Еще одним неудачным экспериментом в предыдущих версиях
MINIX стал макрос ROBUST в конфигурационном файле inc lude /minix/
con f i g . h системы. С его помощью можно сделать так, чтобы файловая система
помечала индексные узлы, каталоги, блоки косвенной адресации и блоки битовых
карт как записываемые незамедлительно. Это должно было сделать файловую
систему более устойчивой, но ценой устойчивости оказалась потеря производи­
тельности. В результате идея была признана неэффективной. К тому же сбой
питания, произошедший, когда еще не все блоки записаны на диск, будь то блоки
с данными или индексными узлами, в любом случае вызвал бы проблемы.
Заметьте, что флаг в заголовке, означающий, что блок был модифицирован, уста­
навливается процедурой в файловой системе, которая его запросила и использует.
Процедуры get_Ы o c k и put_Ь l o c k занимаются только манипуляциями с од­
нонаправленными списками. Они не имеют представления о том, какой проце­
дуре файловой системы какой блок нужен и зачем.

5 . 6 . 6 . Каталоги и пути
Другим важным компонентом файловой системы является подсистема управле­
ния каталогами и путями. Многие системные вызовы, такие как open, в качестве
аргумента получают имя файла. Но в действительности им нужен номер индекс­
ного узла этого файла, поэтому файловая система должна просмотреть дерево
каталогов и найти нужный индексный узел.
В предыдущих версиях M INIX каталог представлял собой файл, содержащий
1 6-байтовые записи, где первые два байта отводились под номер индексного
узла, а оставшиеся 14 - под имя файла. Такая структура ограничивала число
файлов раздела значением 2 16 , а длину имени - 14 символами, как и в UNIX
версии 7. Вместе с объемом дисков имена файлов также выросли. В MINIX 3
новая версия файловой системы поддерживает записи каталога длиной 64 байта,
где 4 байта занимает номер индексного узла и 60 байт - имя файла. Четыре мил­
лиарда файлов в одном разделе, по сути, является бесконечно большим числом,
а любого программиста, называющего файлы именами длиной более 60 симво­
лов, следует отправить на медицинское освидетельствование.
Обратите внимание на то, что пути не имеют 60-символьного ограничения на
длину:
/ u s r / a s t / cours e " ma t er i a l " f o r " t hi s " y e ar / opera t i ng " sy s t ems / examinat ion - 1 . ps

Ограничение распространяется лишь на отдельные компоненты пути. Использо­


вание записей каталогов фиксированной длины (в данном случае, равной 64 сим­
волам) представляет пример компромисса между простотой, скоростью и требуе­
мым объемом памяти. Другие операционные системы, как правило, организуют
свои каталоги в виде структуры данных, называемой кучей. В ней для каждого
файла имеется фиксированный заголовок, указывающий на имя, находящееся
в куче в конце каталога. Схема, применяемая в MINIX 3, очень проста; для ее
создания практически не потребовалось изменять код предыдущей версии. Кроме
61 4 Глава 5 . Файловые систем ы

того, она оперативна в поиске существующих имен и сохранении новых, посколь­


ку куча не требует какого-либо управления. Расплатой за это является потеря
дискового пространства, так как большинство файлов имеют имена длиной го­
раздо меньше 60 символов.
Наше твердое убеждение состоит в том, что оптимизация ради экономии диско­
вого пространства (и оперативной памяти, поскольку каталоги время от времени
попадают в нее) нежелательна. На первом месте должны стоять простота и пра­
вильность, и лишь затем следует обращать внимание на скорость. В условиях,
когда современные жесткие диски обычно имеют емкость свыше 1 00 Гбайт, не­
большая экономия памяти за счет усложнения и замедления кода - не лучшая
идея. К сожалению, многие программисты начинали в то время, когда диски бы­
ли крошечными, а оперативная память - еще меньше. С первого дня их учили
разрешать все компромиссы между сложностью кода, скоростью и требуемым объе­
мом памяти в пользу минимизации потребностей в памяти. Такую скрытую «ус­
тановку�, безусловно, необходимо пересмотреть с учетом современных реалий.
Рассмотрим, как осуществляется поиск пути / u s r / a s t / mЬox / . Сначала систе­
ма ищет каталог u s r в корневом каталоге, затем каталог a s t в каталоге / u s r / ,
и, наконец, каталог mbox в каталоге / u s r / a s t / . Фактически поиск осуществля­
ется последовательно, по одному компоненту пути за шаг (см. рис. 5. 14).
Единственная сложность возникает тогда, когда встречается смонтированная
файловая система. В обычной конфигурации MINIX 3, как и во многих других
UNIХ-подобных операционных системах, имеется небольшая корневая файло­
вая система, содержащая основные файлы, необходимые для запуска и обслужи­
вания системы, а основное число файлов, включая пользовательские каталоги,
находятся на отдельном устройстве, монтируемом в / u s r. Здесь самое время по­
знакомиться с механизмом монтирования. Допустим, пользователь вводит с тер­
минала команду:
mount / dev/ c 0 dlp2 / u s r

В этом случае файловая система, размещенная на втором разделе первого жест­


кого диска, монтируется в каталог / u s r корневой файловой системы. Файловые
системы до и после монтирования показаны на рис. 5.33.
Ключевым моментом при монтировании является флаг, который устанавливает­
ся в хранящейся в памяти копии индексного узла каталога / u s r после успешно­
го подсоединения. Этот флаг означает, что в данный каталог вмонтирована фай­
ловая система. Кроме того, вызов mount загружает суперблок файловой системы
в таблицу super_Ы o c k и устанавливает два указателя на него. Дополнительно
вызов записывает корневой индексный узел смонтированной файловой системы
в таблицу индексных узлов.
На рис. 5.30 можно видеть, что в копии суперблока в памяти есть два поля, от­
носящихся к монтированию файловых систем. В первое из них записывается
указатель на корневой индексный узел смонтированной файловой системы. Вто­
рое поле (указатель на индексный узел, смонтированный выше) хранит номер
5.6 . Обзор файловой системы M I N IX 3 61 5

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


случае это будет индексный узел / u s r . Два этих поля служат для соединения
смонтированной и корневой файловых систем и представляют собой тот самый
�клей� , который удерживает их вместе (на рис. 5.33, в это соединение схематич­
но показано точками) . Благодаря данным полям и работают смонтированные
файловые системы.

Корневая файловая Файловая система После монтирования


система до монтирования
/ / /

/ast/f1 /ast/f2

а б в /usr/ast/f2
Механизм монтирования:
Рис. 5 . 3 3 . а корневая файловая система; б немонтированная
- -

файловая система; в результат монтирования файловой системы в каталог /usr


-

Когда файловой системе передается путь наподобие / u s r / a s t / f 2 , она уви­


дит установленный в индексном узле каталога / u s r флаг и поймет, что продол­
жать поиск файла нужно с индексного узла файловой системы, смонтированной
в каталоге / u s r . Возникает вопрос, как же она найдет этот корневой индексный
узел?
Ответ прост. Система перебирает все хранящиеся в памяти суперблоки, пока не
встретит тот, у которого поле указателя на индексный узел, смонтированный вы­
ше, указывает на / u sr. Это должен быть суперблок файловой системы, смонтиро­
ванный в каталог / u s r . Обнаружив его, несложно определить корневой индекс­
ный узел соответствующей файловой системы. После этого можно продолжить
поиск файла. В данном случае файловая система перейдет в каталог a s t в кор­
невом каталоге второго раздела жесткого диска.
616 Глава 5 . Файловые систем ы

5 . 6 . 7 . Дескрипторы файлов
После того как файл открыт, пользователю возвращается дескриптор этого фай­
ла, который впоследствии может использоваться в системных вызовах r ea d
или wr i t е. В данном разделе мы рассмотрим, как файловая система обращается
с дескрипторами.
Подобно ядру и менеджеру процессов, файловая система поддерживает в своем
адресном пространстве собственную часть таблицы процессов. Особенно интерес­
ны три поля этой таблицы. Первые два из них содержат указатели на индексные
узлы корневого и рабочего каталогов. Поиск файла всегда начинается с одного
из указанных каталогов, в зависимости от того, задан абсолютный или относи­
тельный путь. Значения этих указателей можно обновить при помощи систем­
ных вызовов chroot и chdir, которые изменяют соответственно текущие кор­
невой и рабочий каталоги.
Третье интересное нам сейчас поле представляет собой массив, индексами в ко­
тором являются номера файловых дескрипторов. Этот массив служит для того,
чтобы по данному дескриптору определить соответствующий ему файл. На
первый взгляд, достаточно, чтобы элементы этого массива представляли собой
указатели на индексный узел файла. В конце концов, индексный узел загружает­
ся в память при открытии файла и хранится там до закрытия файла, то есть ин­
дексный узел гарантированно доступен.
К несчастью, этот простой план обречен на провал, так как в MINIX 3 (как
и в UNIX) можно совместно работать с файлом. Возникающая проблема связа­
на с 32-разрядным числом, индицирующим, какой байт считывается следующим.
Это тот самый номер, называемый файловым указателем (или указателем по­
зиции в файле), который меняется системным вызовом l s eek. Проблема выра­
жается следующим вопросом: « Где должен храниться файловый указатель? �
Первый вариант - поместить его в индексный узел. Но, к сожалению, если два
(или более) процесса будут в одно и то же время держать файл открытым, им
придется завести собственные указатели, так как иначе вызов l s eek, сделанный
одним процессом, будет влиять на следующую операцию чтения другого процес­
са. Вывод: файловый указатель нельзя хранить в индексном узле.
А что насчет того, чтобы поместить его в таблицу процессов? Почему бы не
взять второй массив, параллельный массиву файловых дескрипторов, и хранить
в нем файловый указатель для каждого файла? Эта идея также не работает, хотя
и по более тонкой причине. Основная подоплека проблемы кроется в семантике
системного вызова f o rk. Когда процесс ветвится, то и родительский, и дочерний
процессы должны использовать единый указатель для всех открытых файлов.
Чтобы лучше понять проблему, представим себе сценарий оболочки, в котором
стандартный вывод перенаправлен в файл. Когда оболочка ответвляет от себя
первую программу, позиция в файле стандартного вывода для нее равна О. Это
значение наследуется потомком, который направляет в стандартный вывод,
предположим, 1 Кбайт данных. После завершения потомка значение указателя
должно быть равно 1 К.
5.6. Обзор файло в ой системы M I N IX 3 617

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


следующую программу. Важно, чтобы эта программа унаследовала от оболочки
текущее положение в файле, равное 1 К, чтобы начать вывод в файл с того места,
на котором остановилась предшественница. Если оболочка не будет совместно с до­
черними программами использовать значение указателя, вторая программа, вместо
того чтобы дописать свои данные в конец файла, перекроет ими вывод первой.
Таким образом, и в таблице процессов нельзя хранить файловые указатели.
Они действительно должны использоваться совместно. Для решения проблемы
в M INIX применяется новая разделяемая таблица, называемая f i lp. В ней
хранятся значения всех файловых указателей (рис. 5 .34). Благодаря тому, что
файловые указатели действительно используются совместно, можно корректно
ре<µшзовать семантику вызова f ork, и сценарии оболочки заработают правильно.

Таблица filp
Таблица
процессов Таблица i-узлов
Указатель положения
в файле для i-узла
Родитель
Потомок

r r r r
Рис. 5 . 34 . Совместное использование файловых указателей родительским
и дочерним процессами
Единственным обязательным значением, которое необходимо хранить в таблице
f i lp, является значение файлового указателя, но для удобства туда же записы­
вается и указатель на индексный узел. Массив файловых дескрипторов в табли­
це процессов при этом содержит указатели на записи в массиве f i lp. Кроме то­
го, в таблице f i lp находятся значения битов управления доступом, некоторые
флаги, которые могут указывать, что файл открыт в каком-либо специальном режи­
ме, и счетчик количества процессов, использующих данный указатель. Счетчик
необходим для того, чтобы файловая система могла определить, когда завершит­
ся последний процесс, обращающийся к данной ячейке, и освободить ее.

5 . 6 . 8 . Блоки ровка файлов


Специальная таблица нужна для реализации еще одного аспекта управления
файловой системой - блокировки файлов. В MINIX 3 поддерживается описы­
ваемый POSIX механизм добровольной блокировки файлов (advisory file locking).
Данный механизм позволяет отметить весь файл, его часть или несколько его
частей как заблокированные. Операционная система не мешает получать доступ
61 8 Глава 5. Файловые систем ы

к заблокированным данным, но ожидается, что процессы будут вести себя при­


лежно и проверять наличие блокировки, прежде чем делать что-либо, что может
привести к конфликту с другим процессом.
Причина, по которой для этого выделена отдельная таблица, аналогична дово­
дам, приведенным в предыдущем разделе для таблицы f i lp. Один процесс мо­
жет одновременно блокировать несколько файлов, и в то же время несколько
частей одного файла может быть заблокировано разными процессами (хотя за­
блокированные области, конечно же, не перекрываются). Поэтому ни таблица
процессов, ни таблица f i lp не подходят для хранения этих данных. Так как на
одном файле может быть установлено несколько блокировок, индексный узел
тоже не годится.
В M INIX 3 для хранения сведений обо всех блокировках вводится таблица
f i l e_lock. В каждой ячейке этой таблицы есть поля, указывающие тип блоки­
ровки (на чтение или на запись), идентификатор процесса-владельца, указатель
на индексный узел блокированного файла, а также смещения первого и послед­
него байтов заблокированной области.

5 . 6 . 9 . Канал ы ввода-вы вода


и специальные файл ы
Каналы и специальные файлы имеют важное отличие от обычных файлов. Когда
процесс пытается читать дисковый файл или писать в него, в худшем случае опе­
рация займет несколько сотен миллисекунд. Максимально может потребоваться
два или три доступа к диску, не больше. При чтении из канала ситуация иная:
если в канале данных нет, читающий из него процесс будет ждать до тех пор, по­
ка кто-то другой не поместит их туда, на что могут потребоваться часы. Анало­
гично, при чтении с терминала процесс будет ждать до момента, пока пользова­
тель что-нибудь не введет с клавиатуры.
Как следствие, обычное правило обслуживать запрос до его полного выполнения
здесь не работает. Такие запросы нужно приостанавливать и запускать позже.
Когда процесс делает попытку чтения из канала ввода-вывода, операционная
система вправе немедленно проверить его состояние и выяснить, можно ли за­
вершить операцию. Если да, операция выполняется, если нет, файловая система
сохраняет аргументы системного вызова в таблице процессов, чтобы перезапус­
тить его, когда придет время.
Заметьте, что файловая система не предпринимает никаких действий, чтобы
приостановить сделавший вызов процесс. Все, что она делает, - воздерживается
от отправки ответного сообщения, в результате процесс остается заблокирован­
ным в ожидании ответа. Таким образом, после приостановки процесса файловая
система возвращается в основной цикл, ожидая следующего системного вызова.
Как только другой процесс приведет канал ввода-вывода в состояние, позволяю­
щее выполняться операции, запрошенной приостановленным процессом, файло­
вая система устанавливает флаг, чтобы при следующем проходе основного цикла
извлечь из таблицы процессов аргументы системного вызова и выполнить его.
5.6. Обзор файло в ой системы M I N IX 3 61 9

С терминалами и прочими символьными устройствами ситуация немного иная.


У каждого специального файла в индексном узле хранятся два числа - главный
и вспомогательный номера устройств. Первое из этих чисел характеризует класс
устройства (то есть RАМ-диск, жесткий диск, терминал). Оно используется как
индекс в одной из таблиц файловой системы, которая ставит в соответствие это­
му номеру драйвер ввода-вывода. В результате главный номер определяет, какой
драйвер вызвать. Второе число передается драйверу как аргумент. Оно определя­
ет, какое конкретно устройство использовать, например терминал 2 или диск 1 .
В некоторых случаях, особенно это касается терминалов, в о вспомогательном
номере закодирована информация о категории устройств, обслуживаемых драй­
вером. Например, основная консоль MINIX 3, / dev / cons o l e, имеет номера 4, О.
Виртуальные консоли обслуживаются той же частью драйвера - это устройства
/ dev / t tyc l (4, 1 ) , / dev / t ty s 2 (4, 2) и т. д. Для обслуживания терминалов на
последовательных линиях требуется другое низкоуровневое программное обеспече­
ние, и этим устройствам присваиваются следующие номера: 4, 1 6 ( / dev / t ty O O )
и 4, 1 7 ( / dev / t ty O l ). Аналогичным образом, для обслуживания сетевых псев­
дотерминалов нужен отдельный низкоуровневый код, и эти устройства ( t typ O l
и t typ 0 2 ) получают номера 4 , 1 28 и 4 , 1 29. С каждым из псевдоустройств ассо­
циировано устройство ptyp l , ptyp2 и т. д. с номерами 4, 192 и 4, 193 ... Эти номера
выбираются так, чтобы драйверу устройства было проще вызывать низкоуровне­
вые функции, необходимые для работы с каждой из групп объектов. Никто не
ожидает, что когда-нибудь кому-нибудь придет в голову оснастить машину с сис­
темой MINIX 3 более чем 192 терминалами.
Когда процесс читает специальный файл, файловая система извлекает из его ин­
дексного узла главный и вспомогательный номера и по главному номеру при по­
мощи таблицы в файловой системе определяет номер процесса соответствующе­
го драйвера устройства. Определив драйвер, файловая система отправляет ему
сообщение, параметрами которого являются вспомогательный номер, выполняе­
мая операция, номер сделавшего вызов процесса, адрес буфера и количество
байтов. Формат совпадает с показанным в табл. 3.3, с той разницей, что поле по­
зиции устройства не используется.
Если драйвер устройства может выполнить работу немедленно (то есть строка
уже введена в терминал), он копирует данные из собственного внутреннего бу­
фера в пользовательский и отправляет файловой системе сообщение об успеш­
ном завершении работы. Затем файловая система посылает ответное сообщение
пользователю, и вызов завершается. Обратите внимание: драйвер устройства не
передает данные файловой системе. При работе блочных устройств данные пере­
даются через кэш блоков, но со специальными символьными файлами это не так.
Если же драйвер не может сразу выполнить работу, он записывает параметры
сообщения в свои внутренние таблицы и рапортует файловой системе о своей
недееспособности. В этот момент файловая система попадает в ту же ситуацию, ко­
торая возникает, когда кто-либо пытается прочитать из пустого канала ввода-выво­
да. Она фиксирует факт приостановки процесса и ждет следующего сообщения.
620 Глава 5. Файловые системы

Когда драйвер получает достаточно данных для выполнения запроса, он перено­


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

5 . 6 . 1 0 . П ример системного вызова read


Как мы скоро увидим, большая часть кода файловой системы предназначена для
обслуживания системных вызовов. Следовательно, естественно было бы заклю­
чить данный обзор кратким примером того, как работает один из самых важных
системных вызовов, read.
Когда пользовательская программа, чтобы прочитать обычный файл, выполняет
следующую команду, вызывается библиотечная подпрограмма read с тремя ар­
гументами:
n = read ( f d , bu f f e r , nby t e s ) ;

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


read и отправляет его файловой системе, блокируясь в ожидании ответа. Полу­
чив сообщение, файловая система выбирает из таблицы адрес функции-обработ­
чика, пользуясь типом сообщения как индексом. В данном случае будет выбрана
процедура, ответственная за чтение.
Процедура извлекает из сообщения дескриптор файла, определяет по нему соот­
ветствующую запись в таблице f i lp, а затем находит индексный узел считывае­
мого файла (см. рис. 5.34). Далее запрос разбивается на такие части, чтобы каж­
дая из них уместилась в один блок. Например, если текущее значение файлового
указателя равно 600 и запрошен 1 Кбайт данных, запрос будет разбит на две час­
ти, от 600 до 1023 и от 1 024 до 1 623 (если размер блока равен 1 Кбайт).
Затем проверяется наличие каждого из этих блоков в кэше. Если блок в кэше от­
сутствует, файловая система выбирает последний из использовавшихся незаня­
тых буферов и передает драйверу диска запрос на его перезапись, если в нем есть
мусор. Затем драйверу диска передается запрос на считывание блока.
После того как блок оказался в кэше, файловая система посылает системному за­
данию сообщение, требующее поместить данные в соответствующее место в поль­
зовательском буфере (то есть байты от 600 до 1 023 скопировать в начало буфера,
а байты от 1024 до 1 623 - по смещению 424 от начала). Выполнив копирование,
системное задание отправляет пользователю сообщение, указывая в нем, сколь­
ко байтов обработано.
Когда ответ достигает пользователя, библиотечная подпрограмма read извлека­
ет из него код возврата и передает его значение в вызвавший процесс.
Тут есть один дополнительный шаг, который в действительности не является ча­
стью самого системного вызова re ad. Файловая система, выполнив чтение и: от­
правив ответ, инициирует чтение следующего блока, если считывание произво­
дится с блочного устройства и удовлетворены некоторые усJtовия. Так как чаще
5.7. Реал изация файловой системы MINIX 3 62 1

всего производится последовательное чтение, разумно ожидать, что при следую­


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

5 . 7 . Реализа ция файловой системы M I N IX 3


Код файловой системы M INIX 3 относительно объемен ( более 1 00 страниц
С-кода), но достаточно прост. В нем запрос на выполнение системного вызова
принимается, выполняется и отправляется ответ. В последующих разделах мы
файл за файлом рассмотрим этот код, указывая на основные моменты. В свою
очередь и код содержит немало комментариев, чтобы облегчить задачу читателя.
Рассматривая код остальных составляющих MINIX 3, мы обычно сначала изуча­
ли главный цикл процесса, а затем процедуры, обслуживающие различные типы
сообщений. Здесь же мы возьмем за основу другой подход. Сначала мы изучим
основные подсистемы (работу с кэшем, индексными узлами и т. д.). Затем рас­
смотрим главный цикл и системные вызовы, работающие с файлами. Потом на­
станет пора системных вызовов для управления каталогами, и далее мы обсудим
оставшиеся системные вызовы. В заключение мы научимся обращаться со спе­
циальными файлами устройств.

5 . 7 . 1 . Загол овоч ные файл ы и глобал ьные


структуры дан н ых
В файловой системе, как и в ядре, как и менеджере процессов, применяется
большое количество структур и таблиц, определяемых заголовочными файлами.
Некоторые из этих структур помещены в общесистемные заголовочные фай­
лы, расположенные в каталоге inc l ude / и его подкаталогах. Например, файл
inc l ude / sy s / s t а t . h описывает формат представления информации индекс­
ного узла для других программ, а структура данных каталога определяется фай­
лом i n c l ude / sy s / di r . h. Оба этих файла предписаны стандартом POSIX.
Управляют файловой системой множество глобальных определений, расположен­
ных в файле inc l ude /minix / c on f i g . h. Например, макросы NR_BUFS и NR_
BUFS_HASH отвечают за размер кэша блоков.

Заголовочные файл ы файловой системы


Собственные заголовочные файлы файловой системы расположены в катало­
ге s rc / f s / . Имена многих из них уже знакомы вам по другим компонентам
MINIX 3. Главный заголовочный файл, f s . h (строка 20900), очень напоминает
файлы s rc / kerne l / kerne l . h и s r c / pmm / pm . h. Он включает остальные заго­
ловочные файлы, необходимые всем другим файлам С-кода файловой системы.
Как и везде, в главный заголовочный файл включаются локальные заголовочные
файлы c onst . h, type . h, pro t o . h и g l o . h. Их мы и рассмотрим далее.
В файле c ons t . h (строка 2 1 000) определяются некоторые константы, такие
как размеры таблиц или флаги, используемые далее во всей файловой системе.
622 Глава 5. Файловые систем ы

У MINIX 3 уже есть история. В предыдущих версиях применялись другие фай­


ловые системы. Хотя MINIX 3 не поддерживает их, некоторые определения со­
хранены для справки и для того, чтобы желающие могли в будущем обеспечить
их поддержку. Подобная поддержка полезна не только тем, что позволяет полу­
чать доступ к файлам предшествующих версий файловой системы MINIX, но
и тем, что делает возможным обмен файлами.
Старые версии файловой системы MINIX поддерживаются другими операцион­
ными системами. К примеру, система Linux с самого начала использовала и до
сих пор их использует (то, что MINIX 3 не предоставляет подобную поддерж­
ку, в таком свете выглядит несколько комичным). Существуют утилиты для
MS- DOS и Windows, обеспечивающие доступ к устаревшим файловым системам
MINIX. Суперблок файловой системы содержит магическое число, по которому
операционная система способна определить тип файловой системы. Три магиче­
ских числа, соответствующих трем версиям файловой системы MINIX, задаются
константами SUPER_МAG I C , SUPER_V2 и SUPER_VЗ . Кроме того, для двух пер­
вых версий определены константы с суффиксом _REV, которые содержат значе­
ния магических чисел с обратным порядком следования байтов. Они применя­
лись в версиях MINIX, перенесенных на системы с другим порядком следования
байтов (не обратным, а прямым). Это позволяло идентифицировать сменный
диск, записанный на компьютере с противоположным порядком следования бай­
тов. В MINIX версии 3. 1 .0 необходимости в константе SUPER_VЗ _REV не было,
однако, вероятно, ее определение будет добавлено в будущем.
В файле type . h (строка 2 1 1 00) описаны как структуры предыдущей, так и но­
вой версии в том виде, в котором они записываются на диск. Размер нового ин­
дексного узла вдвое больше, чем в старой системе, которая разрабатывалась как
компактная система для машин без жесткого диска и дискетами по 360 Кбайт. В но­
вой версии выделено место для всех трех полей времени, которые есть в UNIХ­
подобных системах. В индексном узле версии 1 было всего одно поле времени,
а вызовы s t at и f s t a t , возвращающие структуру s t a t , содержащую все три
поля, имитировали их наличие. При поддержке двух версий файловых систем
есть небольшое затруднение (комментарий в строке 2 1 1 1 6 описывает его). Старое
программное обеспечение MINIX 3 полагается на то, что тип gi d_t 8-разряд­
ный, поэтому поле d2_g i d необходимо явно объявлять как u l б_t .
В файле prot o . h (строка 2 1 200) в форме, приемлемой как для компилятора
стандартного (ANSI) языка С, так и для старых компиляторов «а ля Керниган
и Ричи», описаны прототипы функций. Это большой, но не очень интересный
файл. Тем не менее тут нужно указать на один момент: так как файловая система
обслуживает такое большое количество системных вызовов, код различных про­
цедур do_xxx рассеян по нескольким файлам. Описания в файле p r ot o . h орга­
низованы так, чтобы было удобно узнать, в каком из файлов находится функция,
обрабатывающая интересующий вас системный вызов.
Наконец, в файле g l o . h (строка 2 1 400) описываются глобальные переменные.
Здесь же находятся буферы входящих и исходящих сообщений. При описании
переменных применяется уже знакомый вам трюк с макросом EXTERN, поэтому
5.7. Реал изация файловой системы MINIX 3 623

они доступны из всех частей файловой системы. Как и в других компонентах


MINIX 3, место в памяти для этих переменных выделяется при компиляции
файла t аЫ е . с .
Принадлежащая файловой системе часть таблицы процессов содержится в фай­
ле f sproc . h (строка 2 1 500). В нем при помощи макроса EXTERN объявляется
массив f sproc. В последнем хранятся маска режима доступа, указатели на ин­
дексные узлы текущих рабочего и корневого каталогов, массив файловых деск­
рипторов, UID, GID, и номер терминала для каждого процесса. Здесь же можно
найти идентификатор процесса и группы процессов, которые дублируют поля
таблицы процессов, принадлежащие ядру и менеджеру процессов.
Несколько полей отведены под хранение аргументов остановленных на полпути
системных вызовов, таких как вызов для чтения из пустого канала. Для полей
fp_susp ended и fp_r evi ved в действительности требуется только один бит,
но для символа компиляторы практически всегда генерируют лучший код. Кро­
ме того, есть поле для битов FD_CLOEXEC, требуемых стандартом POSIX. Их
назначение в том, чтобы указать, что файл должен быть закрыт, когда делается
системный вызов ехе с .
Теперь м ы приступим к файлам, в которых описьщаются остальные таблицы, ис­
пользуемые файловой системой. Прежде всего, это файл bu f . h (строка 2 1 600),
где задается кэш блоков. Все структуры в нем описаны при помощи макроса
EXTERN. Все буферы хранятся в массиве bu f s , и каждый из них содержит об­
ласть данных Ь, полный указателей заголовок и счетчики. Область данных объ­
явлена как объединение пяти типов (строки 2 1 6 1 8 - 2 1 63 2 ) , поскольку иногда
к блоку удобнее обращаться как к массиву символов, иногда как к каталогу и т. д.
Так, чтобы обратиться к области данных буфера 3 как к массиву символов, нуж­
но использовать запись bu f [ З J . b . b_da t a , так как bu f [ З J . Ь ссылается на
всю область данных, из которой выделяется поле b_dat a . Хотя такой синтаксис
и правилен, он неуклюж, поэтому далее создается макрос b_dat a , позволяющий
использовать запись bu f [ З ] . b . b_da t a. Обратите внимание: поле записывается
как b_dat a , с двумя символами подчеркивания, а макрос b_dat a - с одним.
Далее задаются аналогичные макросы для других способов обращения к данным
блока (строки 2 1 650-2 1 655).
Далее, сразу после этих макросов задается хеш-таблица буфера, bu f_ha sh (стро­
ка 2 1 657). Каждый из ее элементов ссылается на список буферов, которые снача­
ла пусты. Макросы в конце файла bu f . h определяют различные типы блоков.
Бит WRI TE_IMM:ED сигнализирует о том, что измененный блок должен быть пере­
записан на диск немедленно, а бит ONE_SHOT отмечает блок, вероятность исполь­
зования которого в ближайшем будущем низка. Ни один из этих битов в настоя­
щий момент не применяется, однако они определены на случай, если у кого-нибудь
возникнет идея о том, как повысить производительность или надежность систе­
мы путем внесения изменений в обслуживание очереди блоков в кэше.
Наконец, в последней строке определяется константа HASH_МASK, значение ко­
торой зависит от константы NR_BUF _НASH, заданной в файле inc lude /rninix/
c on f ig . h. С константой HASH_МASK и номером блока выполняется операция
624 Глава 5. Файловые систем ы

логического И, чтобы определить, какая запись в bu f_ha sh будет использована


в качестве отправной точки при поиске буфера блока.
Файл f i l е . h (строка 2 1 700) содержит промежуточную таблицу f i lp (описан­
ную с ключевым словом EXTERN ) , применяемую для хранения текущего положе�
ния в файле и указателя на его индексный узел (см. рис. 5.34). Эта же таблица
дает ответ на вопрос, был ли файл открыт на чтение и (или) на запись, а также
сколько файловых дескрипторов в текущий момент ссылаются на ее элемент.
Таблица f i l e_l ock (объявленная с ключевым словом EXTERN ) из файла l ock . h
хранит сведения о блокировке файлов (строка 2 1 800). Размер этого массива зада­
ется параметром NR_LOCKS , который определяется в файле con st . h и по умол­
чанию равен 8. Если когда-либо потребуется реализовать на основе MINIX 3
многопользовательскую базу данных, это значение необходимо будет увеличить.
В файле inode . h (строка 2 1 900) объявляется (опять же, как EXTERN ) массив
индексных узлов inode. В нем хранятся все используемые в текущий момент
индексные узлы. Как уже отмечалось, индексный узел файла загружается в па­
мять при его открытии и хранится там до тех пор, пока файл не будет закрыт.
В структуре inode есть информация, присутствующая в памяти, но отсутствую­
щая на диске. Заметьте, что здесь описывается только одна версия и нет никаких
зависящих от версии файловой системы особенностей. Различия между фай­
ловыми системами версии 1 и версий 2/3 учитываются при считывании индекс­
ного узла с диска, и остальной системе не приходится задумываться о формате
диска, по крайней мере до тех пор, пока не понадобится записать измененную
информацию обратно.
К этому моменту вам должно быть понятно назначение большинства полей. Не­
большого пояснения заслуживает только поле i_s eek. Ранее упоминалось, что
при последовательном чтении файловая система, в качестве оптимизации, пыта­
ется заранее (до того как он запрошен) поместить следующий блок в кэш. При
произвольном доступе к файлу опережающее чтение не нужно. Поэтому, когда
делается вызов l s eek, чтобы отключить опережающее чтение, устанавливается
поле i_s e ek.
Файл pararn . h (строка 22000) аналогичен файлу менеджера процессов с тем же
именем. В нем определяются имена для полей сообщений с параметрами, чтобы,
например, можно было писать rn_i n . bu f f er вместо rn_i n . rnl_p 1, имени одного
из полей буфера сообщения rn_in.
В файле super . h (строка 22 1 00) находится определение таблицы суперблоков.
Она отвечает за загрузку суперблока корневой системы, сюда же записываются
суперблоки монтируемых файловых систем. Как и другие таблицы, sup e r_
Ыосk объявлена с ключевым словом EXTERN.

Выделение памяти для данных файловой системы


Последний файл, который мы обсудим, t аЫ е . с (строка 22200), не является за­
головочным. Но, как и при описании менеджера процессов, имеет смысл рас­
смотреть его сразу после заголовочных файлов, поскольку все они присоедин:я­
ются при компиляции файла t ab l e . с. Большинство упомянутых нами структур
5.7. Реал изация файловой системы MINIX 3 625

данных - кэш блоков, таблица f i lp и др. - объявлены при помощи ключевого


слова EXTERN. Это же касается глобальных переменных и локальной части табли­
цы процессов. Так же как и в других частях MINIX 3, память для таких перемен­
ных в действительности выделяется при компиляции файла t аЫ е . с . Кроме
того, в этом файле содержится важный неинициализированный массив. Массив
c a l l_vec t o r хранит указатели на функции, он нужен в главном цикле, чтобы
определить, какая функция какой системный вызов выполняет. Похожую табли­
цу мы видели и внутри менеджера процессов.

5 . 7 2 Табл ицы
. .

С каждой важной таблицей, будь то таблица блоков, индексных узлов, супербло­


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

Управление блоками
С кэшем блоков работают подпрограммы из файла c ache . с . Он содержит де­
вять процедур, перечисленных в табл. 5.6. Первая из них, get_Ы ock (строка
22426), реализует стандартный способ получения блока данных. Когда процеду­
ре файловой системы необходимо прочитать блок пользовательских данных, ка­
талог, суперблок или блок любого другого типа, она вызывает функцию get_
Ы о сk, указывая ей номер блока и устройство.

Табл и ца 5 . 6 . Процедуры управления блоками


Процедура Действие
get_Ыock Извлекает блок для чтения или записи
put_Ыock Помещает обратно блок, ранее запрошенный вызовом get_Ыock
alloc_zoпe Выделяет новую зону (чтобы увеличить файл)
free_zoпe Освобождает зону (когда файл удаляется)
rw_Ыock Перемещает блок между диском и кэшем
iпvalidate Чистит все кэшированные блоки с одного из устройств
flushall Сбрасывает на диск все измененные блоки для некоторого устройства
rw_scattered Читает или записывает разрозненные данные с устройства
rm_lru Удаляет блок из LRU-списка
Когда вызывается функция get_Ы o ck, она прежде всего смотрит, есть ли запро­
шенный блок в кэше. Если да, возвращается указатель на него. Иначе запрошен­
ный блок необходимо считать. Блоки в кэше объединены в списки, всего NR_
BUF _HASH списков. Этот параметр, NR_BUF _HASH, можно настраивать, как и па­
раметр WR_BUF S , определяющий размер кэша блоков. Оба они устанавливаются
в файле i n c l ude / m i n i x / conf i g . h. В заключение скажем несколько слов об
оптимизации размера кэша блоков и хеш-таблицы. Параметр HASH_МASK равен
626 Глава 5. Файловые систем ы

NR_BUF _НASH - 1 . Если имеется 256 списков, маска равна 255, и все блоки, в но­
мерах которых совпадают последние 8 бит, попадают в один список. Получается
256 списков для номеров 00000000, 0000000 1 , ... , 1 1 1 1 1 1 1 1 .
При поиске блока на первом шаге выясняется, в какой из цепочек хеш-таблицы
блок находится, хотя есть один особый случай, когда считывается свободное ме­
сто из разреженного файла и поиск пропускается. Это - причина проверки, вы­
полняемой в строке 22454. Если данный особый случай не обнаружен, следую­
щие две строки устанавливают указатель Ьр на начало той цепочки, в которой
оказался бы нужный блок, если бы он был в кэше, для чего на номер блока на­
кладывается маска НАSН_МАSК. В следующем далее цикле перебираются элемен­
ты цепочки в поисках запрошенного блока. Если блок найден и в данный момент
не используется, он исключается из LR U-списка. Если он используется, то в LR U­
списке его уже нет. Затем вызывающей программе возвращается указатель на
найденный блок (строка 22463).
Если в списке нужный блок не обнаружен, то и в кэше его нет, потому из LRU­
списка выбирается дольше всех не использовавшийся блок. Выбранный блок ис­
ключается из хеш-цепочки, так как ему будет назначен новый номер, и он попа­
дет в другую цепочку. Если информация в блоке изменена, она записывается на
диск (строн:а 22495). Если это делать при помощи вызова f lu sha l l , будут со­
хранены все измененные блоки с того же устройства. Большинство блоков запи­
сываются именно так. Применяемые в текущий момент блоки никогда не могут
быть отданы для другого запроса, так как они отсутствуют в LRU-списке. Одна­
ко блоки почти никогда не находятся в работе, и процедура put_Ы o c k обычно
освобождает блок сразу по окончании использования.
Как только получен новый буфер, во все его поля, включая поле b_dev, записы­
ваются новые значения (строки 22499-22504), и можно считывать данные блока
с диска. Есть только два случая, в которых считывать диск с блока необязатель­
но. Функция get_Ы o c k может быть вызвана с параметром only_search. Это
может означать, что выполняется упреждающее выделение блока. Упреждающее
выделение подразумевает, что содержимое обнаруженного буфера при необхо­
димости перезаписывается на диск и ему назначается новый номер, но в поле Ь_
dev заносится значение NO_DEV, как свидетельство того, что блок не содержит
данных. Пример мы увидим, когда станем обсуждать функцию rw_s c a t t ered.
Кроме того, параметр only_search может использоваться тогда, когда блок ну­
жен файловой системе для полной перезаписи его содержимого. Тогда считыва­
ние старых данных было бы пустым расточительством. В том и другом случа­
ях параметры блока обновляются, но реальное чтение не выполняется (строки
22507-225 13). Когда новый блок готов и при необходимости считан, get_Ы o c k
возвращает указатель н а него в вызывающую программу.
Предположим, что файловой системе временно, чтобы найти имя файла, нужен
блок каталога. Тогда, чтобы получить этот блок, она вызывает функцию get_
Ы о сk. Обнаружив имя файла, она, чтобы вернуть блок в кэш, вызывает функ­
цию put_Ыo ck (строка 22520), освобождая буфер про запас, в расчете, что позд­
нее он понадобится для другого блока.
5.7. Реали зация файл овой систем ы MINIX 3 627

Функция pu t_Ы o c k отвечает за возврат блока в LRU-список, а также в некото­


рых случаях перезаписывает его содержимое на диск. В строке 22544 принима­
ется решение о том, будет ли блок помещен в начало или конец списка. Блоки
виртуального диска всегда размещаются в начале очереди. Кэш блоков не представ­
ляет большой пользы для виртуального диска, так как его данные уже находятся
в памяти и доступны без операций ввода-вывода. По значению флага ONE_SHOT
определяется, будет ли блок нужен в скором времени, и если нет, то он помещается
в начало очереди, где в скором времени его ждет повторное использование. Тем
не менее это происходит крайне редко, если вообще происходит. Почти все бло­
ки, за исключением блоков виртуального диска, помещаются в конец очереди.
После того как блок помещен в список, делается другая проверка, чтобы выяс­
нить, нужно ли записать его на диск немедленно. Как и предыдущий тест, про­
верка WR I TE_IMM:ED является последствием неудачного эксперимента; в настоя­
щее время блоки не помечаются для немедленной записи.
По мере роста файла необходимо время от времени выделять новые зоны для
хранения его данных. За выделение зон отвечает процедура a l l o c_z one (стро­
ка 22580). Она ищет свободную зону по битовой карте зон. Если это - первая зо­
на файла, то перебирать всю битовую карту не нужно, так как поле s_z s e arch
в суперблоке всегда указывает на первую свободную зону на диске. В противном
случае, чтобы зоны располагались вместе, ищется ближайшая к последней зоне
файла свободная зона. Соответственно, поиск начинается с последней зоны файла
(строка 22603). В строке 22615 номер бита в битовой карте преобразуется в но­
мер зоны (таким образом, что бит 1 соответствует первой зоне данных).
При удалении файла его зоны следует вернуть в битовую карту. Это действие
находится в ведении функции f re e_z one (строка 2262 1). Вся ее работа сводится
к вызовам функции f re e_Ь i t , которой передается номер зоны и битовая карта.
Функция free_b i t применяется и для освобождения индексных узлов, конечно,
тогда ей в качестве первого аргумента передается битовая карта индексных узлов.
При работе с кэшем требуется записывать и считывать блоки. Простой интерфейс
для взаимодействия с диском обеспечивает функция rw_Ы ock (строка 2264 1 ) .
О н а записывает или считывает один блок. Аналогично, функция rw_inode за­
писывает или считывает один индексный узел.
Следующая процедура называется inva l i da t e (строка 22680). Она вызывается,
например, при размонтировании диска, чтобы убрать из кэша все блоки, принад­
лежащие размонтируемой файловой системе. Если этого не сделать, при следую­
щем использовании устройства (с другим гибким диском) система может уви­
деть старые блоки вместо новых.
Функцию f lusha l l (строка 22694) вызывает функция ge t_Ы o ck, чтобы уда­
лить измененный блок из LR U-списка. Именно она осуществляет запись боль­
шинства данных. К функции f lusha l l также обращается системный вызов sync,
чтобы сбросить на диск все измененные буферы определенного устройства. Он пе­
риодически используется демоном обновления и однократно вызывает f lusha l l
для каждого монтированного устройства. Эта функция работает с кэшем буфе­
ров как с одномерным массивом, поэтому она может обнаруживать изменен­
ные буферы даже в том случае, если их нет в LRU-списке. Весь массив буферов
628 Глава 5. Файловые систем ы

сканируется, и указатели н а т е и з них, что принадлежат указанному устройству


и содержат обновленные данные, добавляются в массив указателей d i r ty. По­
следний массив, чтобы не выделять его в стеке, объявлен как s t a t i c . Затем мас­
сив передается rw_s cat t e red.
В MINIX 3 ответственность за планирование дисковой записи снята с драйверов
устройств и целиком переложена на функцию rw_s c a t t ered (строка 227 1 1 ) .
Она получает н а входе идентификатор устройства, указатель н а массив указате­
лей на буферы, размер этого массива и флаг чтения/записи. Первым делом
функция сортирует переданный ей массив указателей по номерам блоков, чтобы
передача данных происходила в наиболее эффективном порядке. Затем она фор­
мирует векторы смежных блоков, чтобы передать их драйверу устройства вы­
зовом dev_io . Драйверу не нужно выполнять дополнительное планирование.
Вполне вероятно, что электронные компоненты современного жесткого диска
оптимизируют порядок запросов, однако эти действия находятся за пределами
видимости MINIX 3. Функция rw_s c a t t ered вызывается с флагом WRI T I NG
только описанной ранее функцией f lu sha l l . В этом случае несложно понять
происхождение номеров блоков, так как передаваемые буферы содержат данные,
которые были ранее считаны, а теперь изменены. Для чтения функция rw_
s c a t t ered вызывается только из функции rahead в файле read . с . Сейчас мы
не будем углубляться в детали ее работы, скажем лишь, что перед вызовом rw_
s c a t t e red несколько раз в режиме подготовки вызывается get_Ы o ck, тем
самым резервируется группа буферов. Буферам назначается номер блока, а но­
мер устройства - нет, но это не проблема, поскольку номер устройства передает­
ся в качестве одного из аргументов в rw_s c a t t e red.
Есть важное отличие между тем, как драйвер устройства реагирует на чтение
и запись из rw_s c a t t e red. Запрос на запись некоторого количества блоков
обязательно должен быть выполнен полностью, в то время как запрос на чтение
обрабатывается разными драйверами по-разному, в зависимости от того, что
эффективнее для конкретного драйвера. Функция rahead зачастую вызывает
функцию rw_s cat t e red, передавая ей список буферов, которые в действитель­
ности могут не требоваться. Поэтому лучше всего считать только те блоки, до
которых легко добраться, не выискивая их по всему устройству и теряя драго­
ценное время. Например, драйвер гибкого диска может остановиться на границе
дорожки, а другие драйверы будут читать только последовательные блоки. Вы­
полнив чтение, rw_s c a t t e red помечает прочитанные блоки, вписывая в содер­
жащие их буферы номер устройства.
Последняя функция из табл. 5.6, rm_l ru (строка 22809), предназначена для того,
чтобы удалять блоки из LRU-списка. Она используется только внутри функции
get_Ы o ck, поэтому вместо PUBL I C объявлена как PRIVATE с целью скрыть ее
от процедур из других файлов.
Прежде чем закончить изучение кэша блоков, скажем несколько слов о его тон­
кой настройке. Значение параметра NR_BUF_НАSН должно быть степенью двой­
ки. Если оно больше, чем NR_BUF S , средняя длина хеш-цепочки будет меньше
единицы. Но когда хватает памяти для большого количества буферов, ее хватит
и для большого количества хеш-цепочек, поэтому значение этого параметра
5.7. Реал изация файловой системы MINIX 3 629

обычно выбирается равным ближайшей степени двойки, большей NR_BUFS. В об­


суждаемом коде заведено 1 28 блоков и 1 28 хеш-цепочек. Оптимальный размер
зависит от характера эксплуатации системы, поскольку определяет, сколько ин­
формации будет кэшироваться. В исходном коде полной версии MINIX 3, пред­
ставленной на компакт-диске, который сопровождает эту книгу, задано 1 280 бу­
феров и 2048 хеш-цепочек. Опытным путем было установлено, что дальнейшее
увеличение числа буферов не дает прироста производительности при переком­
пиляции MINIX 3, видимо, потому, что этого достаточно, чтобы хранить проме­
жуточные файлы для всех проходов компилятора. Для некоторых задач более
адекватной была бы меньшая емкость кэша, а другим может потребоваться уве­
личить кэш ради увеличения быстродействия.
Буферы стандартной версии MINIX 3, размещенной на компакт-диске, занима­
ют более 5 Мбайт оперативной памяти. Кроме этого, имеется дополнительный
двоичный файл с именем image_sma l l , который скомпилирован под использо­
вание кэша блоков с 1 28 буферами. Буферы такой системы занимают лишь чуть
более половины мегабайта, а вся система способна работать на компьютере, ос­
нащенном оперативцой памятью объемом 8 Мбайт. Стандартная версия нужда­
ется в 16 Мбайт памяти. Нет сомнений в том, что, проведя некоторую оптимиза­
цию, можно снизить требования к оперативной памяти до 4 Мбайт.

Уп равление индексными узл ами


Не только кэшу блоков требуются управляющие подпрограммы. Они также нуж­
ны для работы с таблицей индексных узлов. Многие из этих процедур аналогич­
ны процедурам управления блоками. Сами процедуры перечислены в табл. 5.7.
Табли ца 5 . 7 . Процедуры управления индексными узлами
Процедура Назначение
get_iпode Помещает индексный узел в память
put_iпode Возвращает более не нужный индексный узел
alloc_iпode Выделяет новый индексный узел (для нового файла)
wipe_iпode Очищает некоторые поля индексного узла
free_iпode Освобождает индексный узел (при удалении файла)
update_times Обновляет поля времени в индексном узле
rw_iпode Передает индексный узел между памятью и диском
old_icopy Преобразует данные индексного узла, чтобы записать его на диск
в формате версии 1
пew_icopy Преобразует данные индексного узла, чтобы записать его на диск
в формате версии 2
dup_iпode Указывает, что кто-то еще использует индексный узел
Функция get_inode (строка 22933) является аналогом функции get_Ы o ck.
Когда какой-либо части системы требуется получить индексный узел, она вызы­
вает эту функцию. Сначала get_inode ищет узел в таблице, чтобы узнать, не
загружен ли он уже. Если да, счетчик использования индексного узла увеличивает­
ся и возвращается указатель на него. Поиск производится в строках 22945-22955.
Если в памяти индексный узел не найден, он загружается вызовом rw_i node.
630 Глава 5. Файловые систем ы

Когда процедура, которой потребовался индексный узел, завершает свои дейст­


вия с ним, то, чтобы возвратить индексный узел, она вызывает процедуру pu t_
inode (строка 22976), уменьшающую значение счетчика использования. Нуле­
вое значение счетчика подразумевает, что файл больше никому не нужен и мо­
жет быть удален из таблицы. Если при этом индексный узел был изменен, он пе­
резаписывается на диск.
Если поле i_l ink равно нулю, значит, на файл не ссылается ни один из катало­
гов, поэтому все его зоны могут быть освобождены. Обратите внимание, что об­
нуление счетчика ссылок i_l i nk и счетчика использования - разные события,
они обусловлены разными причинами и приводят к разным последствиям. Если
индексный узел соответствует каналу ввода-вывода, все его зоны должны быть
освобождены, даже если число ссылок не равно нулю. Такое может произойти,
когда процесс, читающий из канала, освободит его. Нет никакого смысла поддер­
живать канал для одного процесса.
Когда создается файл, для него должен быть выделен новый индексный узел при
помощи процедуры a l l o c_inode (строка 23003). В MINIX 3 устройства можно
монтировать в режиме только для чтения, поэтому функция сначала проверяет
в суперблоке, разрешена ли запись на устройство. В отличие от зон, которые вы­
бираются в стремлении держать все зоны файла близко друг к другу, здесь по­
дойдет любой индексный узел. Чтобы сократить время поиска в битовой карте
индексных узлов, используются поля суперблока, в которых хранится положе­
ние первого свободного индексного узла.
После того как новый индексный узел получен, он загружается в таблицу в па­
мяти при помощи функции get_inode. Затем, частично прямо на месте (строки
23038-23044), а частично - при помощи процедуры wipe_inode (строка 23060),
его поля инициализируются. Такое <�:разделение труда� выбрано здесь потому,
что wipe_inode вызывается и в некоторых других частях системы для очистки
отдельных полей индексного узла.
При удалении файла его индексный узел освобождается посредством процедуры
f r e e_inode (строка 23079) . Эта подпрограмма просто сбрасывает соответст­
вующий бит в карте индексных узлов и обновляет ссылку на первый свободный
индексный узел в суперблоке.
Следующая функция, updat e_t ime s (строка 23099), вызывается, чтобы полу­
чить от системных часов время и изменить нуждающиеся в обновлении значе­
ния полей времени. Она также вызывается из s t at и f s t at , поэтому объявлена
как PUBL I C .
Процедура rw_inode (строка 23 125) аналогична процедуре rw_Ы ock. Е е назна­
чение в том, чтобы считать индексный узел с диска. Это действие она выполняет
в четыре этапа.
1. Определение блока, в котором находится необходимый индексный узел.
2. Считывание блока при помощи функции get_Ы o ck.
3. Извлечение индексного узла и копирование его в таблицу индексных узлов.
4. Возврат блока посредством функции put_Ы o ck.
5.7. Реализаци я файловой системы MINIX 3 631

Код rw_i node несколько сложнее, чем подразумевает приведенная схема, так
как от него требуются некоторые дополнительные действия. Во-первых, поскольку
определение текущего времени требует вызова ядра, все поля индексного узла,
поддежащие обновлению значением времени, только помечаются путем установки
соответствующих битов в поле индексного узла в памяти i_update. Если это поле
будет иметь ненулевое значение, при записи вызывается функция updat e_t ime s.
Во-вторых, дополнительную сложность вносит история MINIX. В старой версии
файловой системы (в версии 1 ) индексные узлы на диске имели структуру, от­
личную от структуры в версии 2. Поэтому о преобразовании заботятся две функ­
ции, o l d_ic opy (строка 23 1 68) и new_ i c opy (строка 232 14). Первая из них
преобразует представление индексного узла в памяти в формат версии 1 . Вторая
делает то же самое для версий 2 и 3. Обе функции используются только в преде­
лах этого файла, поэтому они объявлены как PRIVATE. Обе они выполняют пре­
образование в двух направлениях, как из памяти на диск, так и обратно.
Предшествующие версии MINIX были перенесены на системы, где расположе­
ние байтов в слове отличается от такового в процессорах Intel. В будущем анало­
гичный перенос предполагается и для M INI X 3. В каждой реализации инфор­
мация на диске хранится в том порядке, какой принят в системе. А что это за
порядок, система узнает из поля sp - >nat ive в суперблоке. Поэтому функции
o l d_ i c opy и new_i c opy при необходимости вызывают подпрограммы c onv2
и c onv4 с целью изменить порядок следования байтов. Разумеется, многое из
того, что мы описали, не используется в MINIX 3, поскольку эта система не под­
держивает файловую систему версии 1 в степени, достаточной ддя использования
дисков с этой файловой системой. На момент написания книги никто не перенес
MINIX 3 на платформу с другим порядком следования байтов. Тем не менее эти
биты и поля сохраняются на случай, если кто-нибудь захочет расширить область
применения операционной системы.
Процедура dup_inode (строка 23257) просто увеличивает на единицу счетчик
использования индексного узла. Она вызывается при повторном открытии фай­
ла, при этом индексный узел не нужно еще раз считывать с диска.

Управление суперблоками
В файле super . с находятся процедуры для работы с суперблоками и битовыми
картами. Эти шесть процедур перечислены в табл. 5.8.

Табл и ца 5 . 8 . Процедуры для работы с суперблоками и битовыми картами


Процедура Назначен ие
alloc_Ьit «Выделяет» бит в битовой карте индексных узлов или зон
free_Ьit «Освобождает» бит в битовой карте индексных узлов или зон
get_super Ищет устройство в таблице суперблоков
get_Ыock_size Ищет размер блока для использования
mouпted Сообщает, принадлежит индексный узел монтированной или корневой
файловой системе
read_super Считывает суперблок
632 Глава 5 . Файловые систем ы

Как мы уже отмечали, когда требуется индексный узел или зона, вызывается
функция a l l o c_inode или a l l o c_z one. Каждая из них, в свою очередь, вызы­
вает функцию a l l o c_Ь i t (строка 2332 4 ) , чтобы найти неустановленный бит
в битовой карте. Поиск включает в себя три вложенных цикла, работающих сле­
дующим образом.
1. Внешний цикл перебирает все блоки битовой карты.
2. Промежуточный цикл перебирает все слова блока.
3. Внутренний цикл проверяет все биты в слове.
В промежуточном цикле выясняется, является ли текущее слово дополнением
нуля, то есть состоит ли оно целиком из единиц. Если да, значит, в этом слове
нет •свободных� бит о в, и проверяется следующее слово. Когда обнаруживается
другое значение, где по крайней мере один бит равен О, запускается внутренний
цикл, который определяет его позицию в слове. Если оказалось, что проверены
все блоки, но нулевых битов не нашлось, значит, свободных индексных узлов
или зон на диске нет, и возвращается код NO_B I T ( О ) . Подобный поиск может
потребовать много процессорного времени, но благодаря указателям на первый
свободный индексный узел (или зону), передаваемый из суперблока в a l l oc_
Ьi t в качестве начальной позиции для поиска, его удается сократить.
Обнулить •занятый� бит проще, чем установить, так как не требуется выполнять
поиск. Функция f ree_Ь i t (строка 23400) вычисляет, какой блок битовой кар­
ты содержит сбрасываемый бит, и дальше вызывает функцию get_Ь l o c k, обну­
ляет бит и завершает операцию вызовом put_Ь l ock.
При помощи следующей процедуры, ge t_sup e r (строка 23445), в таблице су­
перблоков ищется запись для заданного устройства. Например, когда монтиру­
ется файловая система, нужно проверить, не делается ли это повторно. Для этого
можно вызовом get_sup e r попробовать найти устройство, на котором файло­
вая система расположена. Если устройство не найдено, то и файловая система
еще не смонтирована.
В MINIX 3 сервер файловой системы способен работать с файловыми система­
ми, размеры блоков которых различны, хотя в каждом разделе диска использует­
ся один и тот же размер. Функция get_Ь l o c k_s i z e (строка 23467) выясняет,
какой размер имеет блок в файловой системе. Она ищет заданное устройство в таб­
лице суперблоков и возвращает размер блока, если устройство смонтировано.
В противном случае возвращается минимальный размер блока, MIN_BLOCK_S I Z E.
Функция mount ed (строка 23489) вызывается только при закрытии блочного
устройства. Обычно при закрытии такого устройства все данные, сохранен­
ные в кэше, сбрасываются. Но если оказалось, что устройство смонтировано, это
нежелательно. Функции mounted передается указатель на индексный узел уст­
ройства. Она проверяет, является ли устройство корневым или смонтированным,
и если устройство смонтировано, возвращает TRUE.
Наконец, перейдем к функции re ad_sup e r (строка 23509). Она отчасти похожа
на функции rw_Ь l o c k и rw_inode, но выполняет только чтение. Суперблок не
считывается в кэш; обращение к устройству происходит для прямого считыва­
ния 1024 байт, расположенных со смещением 1 024 байта от начала устройства.
5.7. Реал изация файловой системы MINIX 3 633

Записывать суперблок при нормальной работе системы не требуется. Считав


суперблок, функция re ad_supe r узнает версию файловой системы и при необ­
ходимости выполняет форматное преобразование. Таким образом, копия су­
перблока в памяти всегда имеет стандартный формат, даже если она прочитана
с диска с другой структурой или другим порядком следования байтов.
Следует сказать несколько слов об �умном� методе определения порядка следо­
вания байтов в системе, записавшей диск, хотя этот метод в MINIX 3 пока и не
используется. Магическое число суперблока записано в порядке, принятом исход­
ной системой; по этой причине проверке подлежат не только сами магические
числа, но и их значения, записанные с обратным порядком следования байтов.

Уп равление файловыми дескрипторами


В MINIX 3 имеются специальные подпрограммы и для работы с дескрипторами
и таблицами f i lp (см. рис. 5.34). Эти процедуры находятся в файле f i l ede s . с .
Когда файл создается или открывается, с ним связываются свободный дескрип­
тор и незанятая ячейка в таблице f i lp. Для поиска свободных ресурсов предна­
значена процедура gE:!t_fd (строка 237 1 6). Она не помечает их как занятые, так
как успешному завершению вызовов c r e a t или open предшествует еще множе­
ство различных проверок.
Функция get_f i lp (строка 2376 1 ) проверяет, находится ли файловый дескрип­
тор в заданных пределах и возвращает его указатель, взятый из f i lp.
Последняя процедура в файле, f i nd_ f i lp (строка 23774), позволяет выяснить,
что процесс пытается писать в неработающий канал (то есть канал, не открытый
другим процессом на чтение). Она прямым перебором ищет в таблице f i lp про­
цессы-читатели на другом конце канала. Если такой процесс найти не удалось,
значит, канал разрушен, и попытка записи в него обречена на провал.

Блокировка файлов
Функции POSIX для блокировки файлов перечислены в табл. 5.9. Область фай­
ла можно заблокировать на запись и чтение или только на запись при помощи
системного вызова f cnt l с запросом F _S ETLK или F_SETLKW. Узнать, заблоки­
рована ли та или иная область файла, можно с помощью запроса F _GETLК.

Операции блокировки записей согласно POSIX. Они выполняются по запросу


Табл и ца 5 . 9 .
с помощью системного вызова FCNTL
Операция Действие
F_SEТLK Блокирует область на запись и чтение
F_SEТLКW Блокирует область на запись
F_GEТLK Проверяет, свободна ли область
В файле l o c k . с есть только две функции. К первой из них, l o c k_op (стро­
ка 23820), обращается системный вызов fcnt l при выполнении каждой из опе­
раций, перечисленных в таблице. Функция делает несколько тестов, гаранти­
рующих, что область в файле задана корректно. Блокировка области не должна
634 Глава 5. Файловые систем ы

конфликтовать с существующими блокировками и н е должна выполняться дваж­


ды. Для снятия блокировки вызывается другая процедура из этого файла, l o c k_
revive (строка 23964). Она разблокирует все процессы, ранее заблокированные
в ожидании освобождения данной области.
Это компромиссная стратегия, потому для точного решения, какой процесс по­
лучит управление, потребовался дополнительный код. Те процессы, время кото­
рых еще не наступило, вновь блокируются после запуска. Подобная стратегия
выбрана, исходя из предположения, что блокировка файлов - операция редкая.
Если на основе MINIX 3 будет построена многопользовательская база данных,
не исключено, что потребуется изменить данный алгоритм.
Процедура l ock_revi ve также вызывается при закрытии заблокированного
файла, это может произойти, например, если процесс был завершен принудитель­
но до того, как закончил работать с блокируемым им файлом.

5 . 7 . З . Главная програм м а
Код главного цикла файловой системы находится в файле main . с (строка 24040).
Вход в него осуществляется после обращения к функции f s_i ni t за инициали­
зацией. По своей структуре он очень похож на главный цикл менеджера процес­
сов и драйверов устройств ввода-вывода. В нем вызывается функция get_work,
ожидающая прибытия следующего запроса на обслуживание (если не может
быть обслужен процесс, ранее приостановленный на чтении из канала или с тер­
минала). Она же устанавливает значение глобальной переменной who, куда за­
писывается номер ячейки в таблице процессов, принадлежащей вызывающему
процессу, а также заполняет другую глобальную переменную, c a l l_nr, записы­
вая в нее номер сделанного системного вызова.
Как только управление возвращается в главный цикл, устанавливаются следую­
щие флаги: fp указывает на запись вызывающего процесса в таблице процессов,
а super_u s e r говорит, принадлежит ли этот процесс суперпользователю. Уве­
домления имеют наивысший приоритет, поэтому в первую очередь проверке
подлежит сообщение SYS_S I G, чтобы выяснить, собирается ли система завершить
работу. Вторым проверяется сообщение SYN_ALARМ, указывающее на то, что ус­
тановленный файловой системой таймер истек. Сообщение NOT I F Y_ME S SAGE
означает, что драйвер устройства готов к обслуживанию, и передается на обра­
ботку функции dev_s t a t u s . Затем начинается главное действо: на передний
план выходит процедура, выполняющая системный вызов. Адрес процедуры вы­
бирается в таблице c a l l_ve c s указателей на процедуры, где в качестве индекса
выступает значение c a l l_nr.
При возврате управления в главный цикл проверяется значение флага dont_
reply. Если он установлен, значит, ответное сообщение не требуется (то есть
процесс заблокирован по причине чтения из пустого канала). Иначе отправляет­
ся ответное сообщение при помощи функции rep ly (строка 24087 ). Последняя
команда в цикле должна распознавать последовательное чтение файла и, соот­
ветственно, для повышения производительности пытаться загрузить следующий
блок до того, как в действительности придет запрос.
5.7. Реал изация файловой системы MINIX 3 635

Две следующие функции файла играют важную роль в главном цикле файловой
системы. Функция get_wo rk (строка 24099) проверяет, имеются ли ранее за­
блокированные процедуры, которые могут продолжить работу. Если такие есть,
они считаются приоритетнее новых сообщений. Только тогда, когда у файловой
системы нет отложенных задач, она отправляет ядру запрос на получение сле­
дующего сообщения (строка 24 1 24 ) . Несколькими строками далее находится
функция rep ly (строка 24 1 59), запускаемая после завершения системного вы­
зова (успешного или неуспешного ). Процесс может быть завершен по сигналу,
и код состояния, возвращаемый ядром, игнорируется. В этом случае все равно
ничего больше не сделаешь.

И нициал изация файловой системы


Оставшуюся часть файла rna in . с составляют функции инициализации, выпол­
няемые при запуске системы. Основную роль здесь играет функция f s_ini t ,
вызываемая файловой подсистемой н а этапе запуска всей системы еще до входа
в главный цикл. Рассматривая в главе 2 планирование процессов, на рис. 2.22 мы
показали начальную очередь процессов при запуске MINIX 3. Файловая система
помещается в очередь с более низким приоритетом, чем менеджер процессов, по­
этому можно быть уверенными в том, что менеджер процессов будет запущен
раньше. Инициализация менеджера процессов была рассмотрена нами в главе 4.
Создавая собственную часть таблицы процессов, менеджер процессов добавляет
в загрузочный образ записи о себе и других процессах. При формировании каж­
дой записи он посьшает файловой системе сообщение, чтобы она могла инициа­
лизировать соответствующую запись в своей таблице процессов. Теперь мы мо­
жем ознакомиться со второй частью этого взаимодействия.
Когда файловая система начинает работу, она незамедлительно запускает собст­
венный цикл в f s_ini t (строки 24 1 89-24202). Первой инструкцией цикла яв­
ляется вызов re c e i ve для получения сообщения, которое было отправлено
функцией инициализации менеджера процессов prn_ini t в строке 18235. Каждое
сообщение содержит номер и идентификатор процесса. Номер процесса использу­
ется в качестве индекса таблицы процессов файловой системы, а идентификатор
сохраняется в поле fp_p i d каждой ее записи. Далее для каждой записи устанав­
ливаются реальные и эффективные идентификаторы пользователя и группы су­
перпользователя, а в поле urna s k всем битам присваивается единичное значение.
При получении сообщения, содержащего символьное значение NONE в поле но­
мера процесса, выполнение цикла прекращается, а менеджеру процессов отправ­
ляется сообщение о том, что все прошло успешно.
Затем завершается инициализация файловой системы. Сначала проверяется
корректность значений важных констант; далее вызывается ряд других функций
с целью подготовить кэш блоков и таблицу устройств, при необходимости загру­
зить виртуальный диск, а также загрузить суперблок корневого устройства. На
этом этапе имеется доступ к корневому устройству, и часть таблицы процессов,
принадлежащая файловой системе, считывается в цикле, чтобы каждый процесс
из загрузочного образа получил информацию о своем корневом каталоге и уста­
новил его в качестве текущего (строки 24228-24235).
636 Глава 5. Файловые систем ы

Начало Конец

NULL
о

k c=J-------. NULL
.

п c=J-------. NULL
а

Начало Конец

NULL
о

п c=J-------. NULL
б

Начало Конец

NULL
о

п c=J-------. NULL в

Рис. 5 . 35 . Инициализация кэша блоков:


а начальное состоя н ие ; б после запроса
- -

одного блока; в после того, как блок освобожден


-

После завершения взаимодействия с менеджером процессов функция f s_in i t


вызывает функцию bu f__po o l , формирующую списки для кэширования бло­
ков (строка 24 132). На рис. 5.32 было представлено нормальное состояние кэша,
5.7. Реал изация файловой системы MINIX 3 637

где все блоки связаны друг с другом в LRU-список и хешированы. Полезно ра­
зобраться в том, как возникла ситуация, показанная на рисунке. Сразу после
инициализации процедурой bu f_p o o l все буферы находятся в списке и все они
связаны в нулевую хеш-цепочку (рис. 5.35, а). Когда запрашивается буфер,
структура приходит в состояние, показанное на рис. 5.35, б, и остается в нем, пока
буфер используется. На этом рисунке мы можем видеть, что блок исключен из
LRU-списка и помещен в отдельную хеш-цепочку.
Обычно же блок освобождается и возвращается в LRU-список немедленно
(рис. 5.35, в). Здесь блок, хотя и не используется более, все еще содержит инфор­
мацию и при необходимости может быть извлечен из хеш-цепочки. После того
как система поработает некоторое время, почти все блоки, скорее всего, окажут­
ся случайно распределенными между различными цепочками. LR U-список при
этом будет выглядеть так, как показано на рис. 5.32.
Следующую функцию, bu i l d_dmap, мы опишем позже, когда пойдет речь о функ­
циях, работающих с файлами устройств. После этого вызьшается функция l o ad_
ram, которая, в свою очередь, использует функцию iget env (строка 264 1 ). По­
следняя получает от ядра численный идентификатор, передавая ему в качестве
аргумента параметр загрузки. Если вы пользовались командой sy s env для про­
смотра параметров загрузки работающей системы MINIX 3, вы видели, что ин­
формация отображалась в виде строк, подобных следующей:
r o o t dev= 9 1 2

Файловая система использует числа для идентификации устройств . Эти числа


вычисляются по формуле:
256 х главный_номер + вспомогателъный_номер.
Здесь главный_номер и вспомогательный_номер - соответственно главный и вспо­
могательный номера устройств . В приведенном примере главный номер равен 3,
а вспомогательный - 144, что соответствует устройству / dev / c O d lp O s O , на ко­
торое обычно устанавливают MINIX 3 в системах с двумя дисками.
Функция l o ad_ram (строка 24260) в ыделяет память для виртуального диска
и загружает в него корневую файловую систему, если этого требуют загрузочные
параметры. С помощью функции iget env она считывает параметры rootdev,
ramimagedev и rams i z e , установленные в окружении загрузки (строки 24278-
24280). Если в параметрах загрузки указана следующая строка, корневая файло­
вая система блок за блоком, начиная с загрузочного блока, копируется с устрой­
ства ramimagedev на виртуальный диск:
root dev = r am

При этом различные структуры файловой системы не интерпретируются. Если


значение параметра rams i z e меньше объема корневой файловой системы, вир­
туальный диск увеличивается, чтобы вместить ее. Если же виртуальный диск
вмещает всю корневую файловую систему и на нем еще остается место, произво­
дится подстройка с целью подогнать размер диска под объем файловой систе­
мы (строки 24404-24420). Это - единственный случай, когда файловая система
638 Глава 5. Файловые системы

осуществляет запись в суперблок, однако, как и при чтении последнего, кэш бло­
ков не используется и данные пишутся непосредственно на устройство с помо­
щью функции dev_io.
Сейчас два момента заслуживают комментария. Первый - код в строках 2429 1 -
24307, обрабатывающий вариант загрузки с компакт-диска. В нем используется
функция cdp robe, не рассматриваемая в этой книге. Заинтересованным читате­
лям мы предлагаем обратиться к коду файла f s / cdprobe . с, который можно
найти на компакт-диске и веб-сайте MINIX 3. Второй момент: независимо от ис­
пользуемого MINIX 3 размера обычных блоков, размер загрузочного блока всегда
равен 1 Кбайт, а суперблок загружается из второго килобайта дискового устрой­
ства. Любые другие варианты оказались бы сложными, поскольку размер блока
можно узнать, лишь загрузив суперблок.
Функция l oad_rarn выделяет пространство под пустой виртуальный диск, если
указан ненулевой размер rarns i z e, при этом виртуальный диск не используется
в качестве корневой файловой системы. Виртуальный диск будет можно задей­
ствовать как файловую систему лишь после инициализации командой rnk f s ,
поскольку структуры файловой системы не скопированы в него. В качестве аль­
тернативы такой виртуальный диск можно сделать вторичным кэшем, если соот­
ветствующая поддержка интегрирована в файловую систему.
Последняя функция файла rna i n . с, функция l oad_sup e r (строка 24426), ини­
циализирует таблицу суперблоков и считывает в нее суперблок корневого уст­
ройства.

5 . 7 4 Операции с отдел ьными файлами


. .

В этом разделе м ы рассмотрим системные вызовы для операций с отдельными


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

Создание , открытие и закрытие файлов


Файл open . с содержит код шести системных вызовов: c re a t , open, rnknod,
rnkdi r, c l o s e и l s eek. Вызовы c r e a t и open мы рассмотрим вместе, а затем
перейдем к остальным.
В старых версиях UNIX вызовы creat и open служили для разных целей. При
попытке открыть несуществующий файл возникала ошибка, а новый файл дол­
жен был создаваться вызовом c r e a t , еще одной целью которого является уста­
новка размера файла в нулевое значение. Однако в современной системе, соот­
ветствующей стандарту POSIX, два отдельных вызова не требуются. Согласно
POSIX созданием файла или �обнулением� существующего файла должен зани­
маться вызов open, поэтому возможности вызова c reat теперь составляют лишь
часть потенциала open, и нужен он только для совместимости со устаревшими
5.7. Реал изация файловой системы MINIX 3 639

программами. Процедуры, выполняющие действия c reat и open, называются


соответственно do_c reat (строка 24537) и do_op en (строка 24550), то есть, как
и в случае с менеджером процессов, здесь соблюдается соглашение именовать
обработчик системного вызова .х:х:х как do_.x:x:x. Открытие или создание файла
включает три шага.
1. Поиск индексного узла (или выделение и инициализация нового индексного
узла, если файл создается).
2. Поиск или создание записи в каталоге.
3. Настройка дескриптора файла и его возврат.
Оба вызова, creat и open, лишь получают имя файла и перекладывают задачи,
общие для обоих вызовов, �на плечи� вызова c ommon_open.
Процедура c ommon_open (строка 24573 ) начинает с того, что проверяет наличие
свободных файловых дескрипторов и ячеек в таблице f i lp. Затем, если при вызове
было указано создать новый файл (то есть установлен бит O_CREAT ) , вызывает­
ся функция new_node (строка 24594). Если запись в каталоге уже присутствует,
эта функция возвращает указатель на существующий индексный узел, в против­
ном случае она создает новые запись и индексный узел. Если индексный узел не
может быть создан, функция записывает код ошибки в глобальную переменную
e r r_code. Наличие кода ошибки не обязательно означает сбой. Когда функция
new_node обнаруживает одноименный файл, она сигнализирует об этом через
код ошибки, но в действительности здесь нет ничего противоправного ( стро­
ка 24597). При неустановленном бите O_CREAT поиск индексного узла осущест­
вляется альтернативным методом - с помощью функции eat__path из файла
ра th . с, которую мы обсудим позже. На данном этапе важно лишь знать, что
если индексный узел не удалось ни найти, ни создать, вызов c ommon_op en за­
вершается с ошибкой прежде, чем достигается строка 24606. Иначе функция
продолжает работу, назначая файлу дескриптор и запрашивая запись в табли­
це f i lp. Следующий блок кода (строки 246 1 2 -24680) пропускается, если файл
создается именно сейчас.
Если же файл существовал ранее, файловая система должна определить, что это
за файл, какие разрешения выставлены у него и т. д., чтобы выяснить, может ли
он быть открыт. Проверку битов rwx (то есть битов, управляющих доступом)
выполняет вызов f o rЬ i dden (строка 246 1 4 ) . Если файл представляет собой
обычный файл и при вызове был установлен бит O_TRUNC, длина файла прирав­
нивается нулю и снова вызывается функция f o rЬ i dden (строка 24620), на этот
раз чтобы проверить доступность файла на запись. Когда запись разрешена,
вызываются функции wipe_inode и rw_inode, очищающие индексный узел
и сохраняющие его на диске. Для других типов файлов (каталогов, специальных
файлов и каналов ввода-вывода) делаются соответствующие проверки. Так, в слу­
чае устройства, при помощи структуры dmap выполняется вызов нужной проце­
дуры, которая открывает его (строка 24640). За открытие именованного канала
отвечает функция p ip e_op en (строка 24646), для него выполняются различные
проверки, имеющие отношение к каналам.
640 Глава 5. Файловые системы

В коде функции c ommon_open, как и в тексте других процедур файловой систе­


мы, есть большое количество проверок различных ошибок и недопустимых ком­
бинаций параметров. Хотя этот код и не изящен, он важен для получения надеж­
ной и свободной от врожденных дефектов файловой системы. Если произошла
какая-либо ошибка, ранее выделенные файловый дескриптор и ячейка в таблице
f i lp освобождаются (строки 24683 - 24689 ) . Функция c ommon_op en в этом
случае возвращает отрицательное значение, говорящее об ошибке. Если проблем
с файловым дескриптором не было, код возврата является положительным числом.
Отложим в сторону файлы и займемся каталогами. И для начала поглядим на
работу функции new_node (строка 24697 ) , которая занимается выделением
индексного узла и вводом пути для вызовов c reat и open. Она также использу­
ется системными вызовами rnknod и rnkd i r, которые еще будут обсуждаться.
Сначала эта функция анализирует путь к файлу (строка 24 7 1 1 ), то есть про­
сматривает его компонент за компонентом, пока не достигает конечного катало­
га. При помощи функции advanc e проверяется, может ли быть открыт послед­
ний компонент.
Например:
f d = c r e a t ( " / u s r / a s t / f o obar " , 0755 ) ;

Если сделан этот вызов, то l oa d_di r попытается загрузить в таблицы индекс­


ный узел для пути / u s r / a s t и вернуть указатель на него. Если файл f o obar
еще не существует, этот индексный узел скоро понадобится, чтобы добавить но­
вый файл в каталог. Другие системные вызовы, добавляющие или удаляющие
файлы, также используют l a s t_di r.
Если функция new_node обнаруживает, что файл не существует, она вызывает
a l l o c_inode (строка 247 17), тем самым выделяя новый индексный узел, и воз­
вращает указатель на него. Если свободных индексных узлов не осталось, назад
вернется код NI L_INODE.
Если выделить индексный узел удалось, выполнение продолжается со строки
24727: его поля заполняются и он записывается обратно на диск, а в конечный
каталог заносится запись с именем файла (строка 24732 ). Опять же, здесь мы
видим, что файловая система обязана постоянно проверять, не произошло ли
ошибки. Если ошибка происходит, необходимо без паники аккуратно освобо­
дить все ресурсы, такие как индексный узел и удерживаемый блок. Если вместо
того, чтобы, скажем, при нехватке индексных узлов мы позволили бы MINIX
просто завершиться с фатальной ошибкой вместо того, чтобы тщательно отме­
нять все действия вызова и возвращать код ошибки, файловая система была бы
ощутимо проще, но и, сами понимаете".
Ранее упоминалось, что каналы ввода-вывода требуют особых действий. Когда
с каналом не связан хотя бы один процесс, читатель либо писатель, функция
p i p e_open (строка 24758) приостанавливает процесс-инициатор вызова. В про­
тивном случае она вызывает процедуру release, которая ищет в таблице процес­
сов те, что ожидают данный канал. Если такие процессы найдены, они запускаются.
Системный вызов rnknod выполняется функцией do_rnknod (строка 24785). Эта
процедура аналогична do_c r e a t за исключением того, что она лишь создает
5.7. Реализаци я файловой системы M INIX 3 641

индексный узел и делает для него запись каталога. Фактически, большую часть
работы выполняет функция new_node (строка 24797). Если индексный узел уже
существует, возвращается код ошибки. Код ошибки тот же самый, который бьm
приемлемым при открытии файла. Однако в данном случае код возвращается
в вызывающую программу, которая, предположительно, подобающим образом его
интерпретирует. Подробный анализ отдельных случаев, проводимый в c ommon_
open, здесь не нужен.
Функция do_rnkdi r (строка 24805) выполняет системный вызов rnkd i r. Как
и в случае с предыдущими системными вызовами, важную роль здесь играет
функция new_node. Каталоги в отличие от файлов никогда не бывают пустыми,
так как любой каталог содержит, по крайней мере, две записи, точку ( . ) и две
точки ( . . ), первая из которых ссьmается на сам каталог, а вторая - на вышестоя­
щий. Количество ссылок на один файл ограничено константой L INК_МAX (в файле
inc l ude / l irni t s . h для стандартной конфигурации MINIX 3, предназначенной
для платформы lntel, она определена как SHRT_МAX и ее значение равно 32767).
Поэтому, раз дочерний каталог содержит ссылку на родительский, функция do_
rnkdi r сначала проверяет, можно ли добавить новую ссылку на родительский ка­
талог (строки 24819-24820). Когда эта проверка пройдена, вызывается функция
new_node. Если и этот вызов удался, для каталогов создаются записи . и . .
(строки 2484 1 -24842). Описанный алгоритм прямолинеен, но учитывает возмож­
ность сбоев (например, переполнение диска). Чтобы ничего не испортить, обес­
печивается откат к исходному состоянию, если процесс не может быть завершен.
Закрыть файл всегда проще, чем открыть. Поэтому функции do_c l o s e (стро­
ка 24865) для обычных файлов, фактически, требуется только уменьшить значе­
ние счетчика в f i lp и, если оно достигло нуля, возвратить индексный узел при
помощи функции put_i node. (Каналам же и специальным файлам требуется
особое внимание.) На последнем шаге аннулируются все блокировки, связанные
с этим файлом, и пробуждаются все процессы, приостановленные по факту бло­
кировки.
Заметьте, что возврат индексного узла означает только, что уменьшается счетчик
в таблице индексных узлов, следовательно, в конце концов, он может быть уда­
лен из таблицы. Эта операция не имеет ничего общего с освобождением индекс­
ного узла (то есть со сбросом бита в битовой карте индексных узлов). Объект
индексного узла освобождается только тогда, когда файл больше не находится
ни в одном из каталогов.
Последняя процедура в файле open . с - процедура do_l s eek (строка 24939).
Она вызывается, когда осуществляется переход на заданную позицию в файле. При
этом блокируется упреждающее чтение (строка 24968), поскольку явное переме­
щение на заданную позицию в файле несовместимо с последовательным доступом.

Чтение файла
Открыв файл, его можно читать или записывать в него данные. Функций для
этого больше, чем достаточно, - все связанные с чтением функции можно найти
в файле read . с. Мы обсудим сначала их, а затем перейдем к следующему файлу,
642 Гла ва 5. Файло в ые систем ы

wri t e . с , чтобы взглянуть н а код, предназначенный специально для записи. Ме­


ханизмы чтения и записи во многом различны, но у них довольно много обще­
го, поэтому все, что требуется от do_read (строка 25030), это вызвать общую
-

процедуру read_wr i t e с флагом READ ING. В следующем разделе мы увидим,


что функция do_wr i t e столь же проста.
Функция read_wr i t e начинается в строке 25038. Код в строках 25063-25066
используется менеджером процессов, чтобы файловая система загрузила для
него целые сегменты в пользовательском пространстве. Обработкой обыч­
ных вызовов занимается код, начинающийся в строке 25068. В первую очередь
выполняется несколько проверок корректности операции (скажем, не делает­
ся ли попытка читать из файла, открытого только на запись) и инициализиру­
ются некоторые глобальные переменные. Чтение из специальных символьных
файлов производится в обход кэша блоков, поэтому такие файлы фильтруются
в строке 25122.
Проверки в строках 25 1 3 2 - 2 5 1 4 5 имеют место только при записи и работают
с файлами, размер которых может превысить размер устройства, а также предот­
вращают попытки записи за пределы конца файла, вызывающие образование
�дыр». Как уже упоминалось в обзоре MINIX 3, наличие нескольких блоков в зо­
не приводит к некоторым проблемам, с которыми приходится бороться. Каналы
ввода-вывода также являются особым случаем.
Сердцем механизма чтения, по крайней мере, для обычных файлов, является
цикл, начинающийся в строке 2 5 1 57. Он разбивает данные на фрагменты, каж­
дый из которых умещается в один дисковый блок. Фрагмент начинается с теку­
щей позиции и считается завершенным при выполнении одного из трех условий:
+ считаны все байты;
+ встретилась граница блока;
+ достигнут конец файла.
Эти правила означают, что один фрагмент не может занимать два блока. На ри­
сунке 5.36 показано, как определяется квота размера, для фрагментов разме­
ром 6, 2 и 1 байт соответственно. Фактический расчет осуществляется в стро­
ках 2 5 1 59-25 1 69.
Считывание выполняется функцией rw_chunk. Когда эта функция возвращает
управление, увеличиваются значения различных счетчиков и продвигаются указа­
тели, после чего начинается следующая итерация. Когда цикл завершается, теку­
щая позиция в файле и другие переменные (например, указатели канала) могут
быть обновлены.
Наконец, если было запрошено упреждающее чтение, позиция и индексный узел,
откуда его нужно начинать, сохраняются в глобальных переменных, чтобы фай­
ловая система, отправив пользователю ответное сообщение, могла начать считы­
вать следующий блок. Часто при этом файловая система блокируется, ожидая
считывания блока, и у пользовательского процесса есть время начать работать
с только что полученными данными. Такое чередование обработки и ввода-вы­
вода может значительно повысить производительность.
5.7. Реализаци я файловой системы MINIX 3 643

Номера байтов
о 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 ... Блок Блок •1
я позиция = 1
�- Порция данных б
Текущая позиция = 6
t
1 1 J 1 � Порция данных 2
Текущая позиция = 9
t
1 1 1 1 � Порция данных 1
Рис . 5 . 36 . Три примера того, как для 1 О-байтового файла определяется размер первого
фрагмента данных. Размер блока равен 8 байт, запрашивается 6 байт.
Фрагмент отмечен штриховкой
Процедура rw_chunk (строка 2525 1 ) получает на входе индексный узел и пози­
цию в файле, преобразует эти значения в физический номер блока на диске и за­
прашивает передачу этого блока (или его части) в область пользовательских
данных. Преобразование относительной позиции в файле в физический адрес на
диске выполняется функцией read_rnap, которая осведомлена о структуре ин­
дексных узлов и блоков косвенной адресации. Для обычного файла переменные
Ь и dev в строках 25280 и 25281 содержат соответственно физический номер
блока и номер устройства. Вызов get_Ы o c k (строка 25303) запрашивает у об­
работчика кэша блоков нужный блок, считывая его при необходимости. Затем
вызов rahead (строка 25295) убеждается в том, что блок считан в кэш.
После того как указатель на блок получен, вызов sys_vi rc opy ядра в строке
253 1 7 обеспечивает копирование данных в пользовательское пространство. За­
тем блок освобождается, чтобы позже он мог быть удален из кэша. ( После того
как вызов get_Ы oc k находит нужный блок, тот исключается из LRU-списка
и пребывает вне его до тех пор, пока счетчик использования в заголовке буфера
не обнулится. Вызов put_Ыock уменьшает значение этого счетчика и, если наста­
ло время, возвращает буфер в LRU-список.) Код в строке 25327 определяет, запол­
нился ли блок при записи. Правда, на данном этапе это уже не принципиально,
так как функция put_Ыock теперь игнорирует значение, передаваемое ей во вто­
ром аргументе, и всегда добавляет освободившиеся блоки в конец LR U-списка.
Функция read_rnap (строка 25337) преобразует логическое смещение в файле
в физический номер блока, пользуясь для этого информацией индексного узла.
Те блоки, которые достаточно близки к началу файла, попадут в одну из первых
семи зон (то есть в одну из зон, хранящихся в самом индексном узле). При этом
чтобы узнать, какая из зон необходима, требуется только несложная арифметика.
644 Глава 5. Файловые систем ы

Для блоков, расположенных дальше, может понадобиться считать один или боль­
ше блоков косвенной адресации.
Функция rd_i ndi r (строка 25400) вызывается, когда необходимо считать кос­
венный блок. Комментарии к ней несколько устарели; во-первых, код поддержки
процессора 68000 удален, во-вторых, файловая система MINIX 1 больше не ис­
пользуется и соответствующий ей код также можно исключить. Тем не менее
следует отметить, что в случае организации поддержки других файловых систем
или платформ, проблемы иных форматов сохранения данных на диске, типов дан­
ных и порядка следования байтов можно решить в этом файле. Если без труд­
ных для понимания преобразований данных не обойтись, то совершив их здесь,
вы обеспечите единую форму представления данных во всей файловой системе.
Функция read_ahead (строка 25432 ) преобразует логическое положение в фи­
зический адрес блока, вызывает функцию get_Ы ock, в результате чего блок
оказывается в кэше, и немедленно возвращает его. В конце концов, сделать с ним
она все равно ничего не может. Задача read_ahead лишь в том, чтобы повысить
вероятность найти данные в кэше, если они скоро понадобятся.
Обратите внимание, что функция read_ahead вызывается только из главного
цикла в rna in. Ее вызов не является частью вызова re ad. Важно понять, что эта
функция вызывается после того, как пользователю отправлен ответ, чтобы он
мог продолжать свою работу, пока файловая система дожидается завершения
упреждающего чтения.
Сама функция r ead_ahead написана так, чтобы запрашивать всего один блок.
Реально трудится вызываемая ею подпрограмма rahead. В rahead (строка 2545 1 )
заложена та жизненная концепция, что если немного больше - хорошо, а намно­
го больше - еще лучше. Так как дискам и прочим накопителям часто требуется
много времени, чтобы найти первый запрошенный блок, но зато они могут быст­
ро считать несколько смежных блоков, делается ставка на то, что получится счи­
тать много последовательных блоков ценой небольших дополнительных затрат.
Запрос на упреждающую выборку передается функции get_Ь l ock, подготавли­
вающей кэш блоков к получению нескольких блоков за раз. Затем вызывается
функция rw_scatt ered, которой передается список блоков. Работу этой функции
мы уже обсуждали. Вспомните, что rw_s c a t t e r e d передает запрос драйверу
устройства, который в ответ имеет право обслужить столько запросов, сколько
он способен выполнить эффективно. Все это звучит довольно витиевато, но зато
позволяет на нужное •много� повысить быстродействие приложений, считываю­
щих с диска ожидаемое •больше� данных.
На рис. 5.37 показаны взаимосвязи между основными подпрограммами, участ­
вующими в чтении файла. В том числе указано, кто кого вызывает.

Зап ис ь
Код, обеспечивающий запись, находится в файле wri t e . с. Запись в файл в боль­
шинстве своем сходна с чтением, и функция do_wr i t e (строка 25625) просто
вызывает read_wri t e с флагом WRITING. Основное отличие записи в том, что
здесь может потребоваться выделение дополнительных блоков. Функция wri t e_
5.7. Реализаци я файло вой системы MINIX 3 645

map (строка 25635) аналогична read_map с той разницей, что вместо поиска
физического адреса блока по индексному узлу и косвенным блокам она добавля­
ет номер новой зоны, а не блока.

Точки входа

Главная процедура для чтения/записи

Запись или чтение одного блока


Специальные К
аналы
файлы

Определение�
дискового адреса
, Возврат
блока


В КЭШ

Доступ к косвенному
блоку Поиск в кэше
(если нужно )

Адрес в таблице dmap

seпdrec Отправляет сообщение ядру


Рис . 5 . 37 . Некоторые из процедур, участвующих в чтении файла

Код функции wr i t e_map сложный и длинный, поскольку эта функция должна


учитывать несколько ситуаций. Если новая зона вставляется в начало файла, она
должна быть вставлена в его индексный узел (строка 25658).
Самый же худший случай, когда по мере роста файла простых косвенных блоков
перестает хватать и приходится применять блоки второго уровня косвенности.
После этого выделяется блок первого уровня косвенности, и его адрес заносится
в блок второго. Как и в случае с чтением, для этого предусмотрена отдельная
процедура, wr_indi r. Если блок второго уровня косвенности удалось выделить,
но из-за этого диск переполнился, а на блок первого уровня косвенности места
уже не хватает, то блок второго уровня нужно вернуть, чтобы избежать повреж­
дения битовой карты.
646 Глава 5. Файловые системы

Опять же, если бы в случае неудачи можно было просто сообщить о фатальной
ошибке ядра, код был бы намного компактнее. Однако с точки зрения пользовате­
ля, гораздо лучше, когда при переполнении диска wr i t e возвращает код ошибки
вместо того, чтобы провоцировать крах, к тому же с разрушением файловой системы.
Функция wr_indi r (строка 25726) записывает новый номер зоны в косвенный
блок, а чтобы преобразовать данные в нужный формат (порядок следования бай­
тов), она вызывает подпрограмму преобразования c onv4 . Здесь мы снова имеем
дело с устаревшим кодом, работающим с файловой системой версии 1 ; на самом
деле, используется лишь код, работающий с версией 2. Пусть имя этой функции
не вводит вас в заблуждение. Помните, что действительную запись данных на
диск выполняют функции, обслуживающие кэш блоков.
Далее в файле wr i t e . с следует функция c l e ar_z one (строка 25747). Она за­
нимается очисткой блоков, оказавшихся в середине файла. Такое может слу­
читься, если записать некоторый объем данных после конца файла. К счастью,
это случается не очень часто.
Функция new_Ы ock (строка 25787) вызывается из rw_chunk, когда требуется
новый блок. На рис. 5.38 показаны шесть последовательных этапов увеличения
файла. В этом примере размер блока равен 1 Кбайт, а зоны 2 Кбайт. -

Свободные зоны: 1 2 20 3 1 36 . . .

в
j 24 j 2s j 40 j
г
l 24 l 2s l 40 l 4 1 1
д
l фs l 40 l • 1 l 02 I
2 r Номор бл<жо
е
l 24 l 2s l 40 l 4 1 j в2 I�
Рис. 5 . 38 . Последовательное выделение блоков размером 1 Кбайт при размере зоны 2 Кбайт
Здесь при первом своем вызове функция new_Ы o ck выделяет зону 12 (блоки
24 и 25). Затем она задействует блок 25, который уже выделен, но еще не исполь­
зован. При третьем вызове выделяется зона 20 (блоки 40 и 4 1 ) и т. д.
Функция z ero_Ы ock (строка 25839) очищает блок, стирая его предыдущее со­
держимое. Честно говоря, это описание длиннее, чем сам код.

Каналы ввода - вывода


Каналы во многих отношениях подобны файлам. В этом разделе мы сконцен­
трируем внимание на отличиях. Код, который мы будем обсуждать, находится
в файле p ipe . с .
5.7. Реализация файловой системы M I N IX 3 647

Прежде всего, каналы создаются иначе - вызовом p ip e , а не c r e a t . Вызов


p ip e выполняется функцией do_p i p e (строка 25933 ) . Вся работа do_p ipe
сводится к выделению индексного узла для канала, после чего для него воз­
вращаются два файловых дескриптора. Владельцем каналов является система,
а не пользователь, и располагаются они на устройстве, назначенном в файле
inc l ude /minix/ c o n f i g . h. В качестве этого устройства наиболее удобен вир­
туальный диск, так как данные каналов не нуждаются в долговременном хра­
нении.
Запись в канал и чтение из него отличаются от случая с файлом, поскольку
канал имеет ограниченную пропускную способность. Попытка записать данные
в переполненный канал приведет к приостановке пишущего процесса. Анало­
гичным образом, к блокировке приведет попытка чтения из пустого канала.
Итак, канал должен иметь два указателя, текущую позицию (она нужна чита­
телям) и размер (интересный писателям). Все это вместе определяет, откуда и ку­
да следуют данные.
Функция pipe_che ck (строка 25986) делает различные проверки, выясняя, воз­
можна ли работа с каналом. В дополнение к этим проверкам, которые могут при­
вести к приостановке вызывающего процесса, p i p e_che ck вызывает r e l e a s e ,
чтобы определить, может л и быть оживлен ранее приостановленный процесс,
пытавшийся прочитать отсутствующие данные или злоупотребляющий записью.
За возобновление пишущих процессов отвечает код в строке 260 17, читающих -
код на строке 26052. Здесь же обнаруживаются некорректно проложенные или
«сломанные» (то есть без читателей) каналы.
За приостановку процесса отвечает функция s u s p end (строка 26073). Она про­
сто сохраняет в таблице процессов параметры вызова и устанавливает в TRUE
флаг dont_rep ly, чтобы файловая система не отправляла ответное сообщение.
Процедура re l e a s e (строка 26099) вызывается для проверки, может ли быть
запущен ранее приостановленный на канале процесс. Если может, вызывается
функция revive, устанавливающая флаг, на который позже обратит внимание
главный цикл. Эта функция не относится к системным вызовам, но работает че­
рез механизм передачи сообщений.
Последняя процедура в файле p ip e . с называется do_unp au s e (строка 26189).
Когда менеджер памяти пытается передать процессу сигнал, он обязан узнать, не
приостановлен ли процесс на доступе к каналу или к специальному файлу (если
это так, процесс будет возобновлен с ошибкой E I NTR). Так как менеджер памяти
о каналах и специальных файлах никто в известность не ставил, он просит сове­
та у файловой системы, передавая ей сообщение. Это сообщение обрабатывается
функцией do_unpau s e , которая возобновляет работу процесса в том случае, ес­
ли он заблокирован. Как и revi ve, функция do_unp au s e имеет сходство с сис­
темным вызовом, хотя и не является таковым.
Последние две функции файла p ipe . с - s e l e c t_reques t_p ipe (строка 26247)
и s e l e ct_mat ch_p ipe (строка 26278). Они поддерживают вызов s e l e c t , здесь
не рассматриваемый.
648 Глава 5. Фай л овые систем ы

5 . 7 . 5 . Каталоги и пути
Итак, мы завершили обзор функций записи и чтения файлов. Наша следующая
задача - разобраться, как обрабатываются пути к файлам и каталоги.

П реобразование пути в индексный узел


Многие системные вызовы принимают в качестве входного аргумента путь (то есть
имя файла). Это, например, вызовы open, unl i nk, rnount . Большинству из них,
перед тем как начать выполнять сам вызов, требуется определить индексный узел
для указанного файла. Поэтому теперь мы подробно изучим, как имя файла преоб­
разуется в индексный узел. Общие контуры уже были обрисованы на рис. 5 . 1 4.
Для анализа имен файлов служит код в файле path . с . Первая его процедура,
eat_path (строка 26327), получает на входе указатель на имя файла, анализи­
рует имя, загружает нужный индексный узел в память и возвращает указатель на
узел. Она решает свою задачу при помощи функции l a s t_d i r , возвращающей
индексный узел каталога, в котором непосредственно находится файл. Затем,
чтобы получить последний компонент пути, она вызывает функцию advanc e .
Если поиск объекта закончился неудачей, например, такое может произойти, ес­
ли один из каталогов в пути не существует или существует, но недоступен для
поиска, вместо указателя на индексный узел возвращается значение NIL_INODE.
Имена файлов могут быть абсолютными или относительными и иметь произ­
вольное число компонентов, разделенных символами косой черты. Распознава­
нием занимается функция l a s t_di r. Сначала она проверяет первый символ пу­
ти с целью выяснить, абсолютный тот или относительный (строка 2637 1). Если
путь абсолютный, в r i p записывается указатель на корневой индексный узел,
для относительных путей в эту переменную помещается указатель на индексный
узел рабочего каталога.
С этого момента у l a s t_d i r есть путь и указатель на каталог, в котором нужно
искать первый компонент. В строке 26382 начинается цикл, где путь анализиру­
ется компонент за компонентом. После конечной итерации возвращается указа­
тель на конечный каталог.
Функция get_narne (строка 264 1 3 ) является вспомогательной процедурой, из­
влекающей компоненты пути из строк. Более интересна функция advance ( стро­
ка 26454), которая получает на входе указатель на каталог и строку и ищет ука­
занную строку в каталоге. Если ей удается обнаружить в каталоге объект с таким
именем, она возвращает указатель на его индексный узел. Эта же функция учи­
тывает особенности смонтированных файловых систем.
Хотя функция advanc e и управляет процессом поиска строки, самим сравнением
строк с записями в каталоге занимается функция s e arch_d i r (строка 26535).
Это - единственный элемент во всей файловой системе, который непосредст­
венно имеет дело с файлами каталогов. В функции есть два вложенных цикла,
во внешнем перебираются блоки каталогов, а во внутреннем просматриваются
записи внутри каждого из блоков. Функция s e arch_d i r также вызывается
для удаления записей из каталога или для внесения новых записей. Основные
5.7 . Реализация файловой системы MINIX 3 649

взаимосвязи между процедурами, участвующими в поиске файла по его пути,


показаны на рис. 5.39.

get_iпode Загрузка
i-узла
по компонентам
пути
Определение Поиск бпока Возврат бпока
адресов в кэше в кэш
на диске
Рис . 5 . 3 9 . Некоторые из процедур, участвующих в поиске файла по его пути

Монтирование файловых систем


Есть два системных вызова, оказывающих влияние на файловую систему в целом,
это mount и umount . При помощи этих вызовов можно •склеивать� воедино от­
дельные файловые системы на различных дополнительных устройствах, образуя
общую древовидную структуру. Как можно было видеть на рис. 5.33, при мон­
тировании файловой системы ее суперблок и корневой индексный узел считыва­
ются, и в суперблоке устанавливаются два указателя. Первый из них ссылается
на точку монтирования (то есть индексный узел, к которому присоединена смонти­
рованная файловая система), а второй указывает на корневой индексный узел но­
вой файловой системы. Эти два указателя и сцепляют файловые системы вместе.
Означенные указатели устанавливаются в строках 268 19-26820 функцией do_
mount из файла mount . с. Две страницы кода, которые предшествуют этому мо­
менту, практически целиком относятся к проверке различных ошибок, ожидае­
мых при монтировании файловых систем:
+ указанный специальный файл не является блочным устройством;
+ специальный файл представляет собой блочное устройство, но он уже смон-
тирован;
+ у монтируемой файловой системы неправильная сигнатура;
+ монтируемая файловая система некорректна (то есть нет индексных узлов);
+ файл, к которому присоединяется файловая система, не существует или явля-
ется специальным;
+ не хватает памяти для битовых карт монтируемой файловой системы;
+ не хватает памяти для суперблока монтируемой файловой системы;
+ не хватает памяти для корневого индексного узла монтируемой файловой
системы.
650 Глава 5 . Файловые системы

Хотя может показаться, что нет смысла бесконечно повторять проверки, практи­
ка показывает, что в реальных операционных системах значительная часть кода
выполняет рутинную работу, которая не слишком интересна, но имеет ключевое
значение для системы. Если пользователь иногда случайно будет пытаться мон­
тировать поврежденную дискету, это приведет к тотальной порче файловой сис­
темы, и он решит, что система ненадежна, в чем виноватым, естественно, окажет­
ся не пользователь, а разработчик.
Томас Эдисон сделал одно замечание, которое применимо и к нашему случаю.
Он сказал, что гений - это 1 % вдохновения и 99 % труда. Различие между хоро­
шей и посредственной системой состоит не в превосходном алгоритме планиро­
вания, а в том внимании, которое уделено деталям.
Демонтировать файловую систему проще, чем монтировать. Эту задачу в два
этапа решает функция do_umount (строка 26828). Она убеждается, что вызов
бьш сделан суперпользователем, преобразует имя в номер устройства, а затем
обращается к подпрограмме unmount (строка 26846), завершающей операцию.
Единственная проблема в том, что все файлы на демонтируемой файловой сис­
теме должны быть закрыты, и ни у одного процесса не должно быть текущего
каталога на ней. Проверка осуществляется тривиально: сканируется вся табли­
ца индексных узлов на предмет поиска в памяти хотя бы одного индексного
узла, принадлежащего демонтируемой файловой системе. Если да, umount рапор­
тует об ошибке.
Последняя подпрограмма в файле mount . с носит имя name_t o_dev (строка
26893). Она определяет главный и вспомогательный номера устройства по пере­
даваемому ей имени специального файла. Эти номера хранятся в самом индекс­
ном узле, там, где у обычных файлов хранится информация о первой зоне. Это
место пустует, поскольку у специальных файлов нет зон.

Создание и уничтожен ие ссылок


Следующий файл, который мы рассмотрим, называется l ink . с . Процедура do_
l ink (строка 27034) очень напоминает do_mount в том смысле, что практиче­
ски весь ее код связан с проверкой ошибок. Обратите внимание на вызов:
l i nk ( f i l e_name , l i nk_name ) ;

Вот некоторые из возможных ошибок, которые могут произойти при этом вызове:
+ файл f i l e_name не существует или недоступен;
+ у файла f i l e_name уже есть максимальное количество ссьшок;
+ файл f i l e_name является каталогом (создавать такие ссылки имеет право
только суперпользователь);
+ файл l ink_name уже существует;
+ файлы l ink_name и f i l e_name расположены на разных устройствах.
Если все в порядке, в каталоге создается новая запись с именем l ink_name и но­
мером индексного узла f i l e_name . В коде имя name l соответствует f i l e_
name, а name 2 - l ink_name. Вносит новую запись в каталог функция s e arch_
dir, вызываемая из do_l ink в строке 27086.
5.7. Реализация файловой системы MINIX 3 65 1

Удаление файлов и каталогов происходит путем уничтожения ссылок на них.


Поэтому оба системных вызова un l i nk и rmd i r обслуживаются единственной
функцией do_un l i nk (строка 2 7 1 04). Опять же, в ней делается множество раз­
личных проверок, общий код убеждается, что файл существует и не является
точкой монтирования, после чего в зависимости от типа вызова управление пе­
редается либо процедуре remove_di r, либо un l i nk_f i l e. Мы вскоре обсудим
эти две подпрограммы.
Код в файле l i nk . с обеспечивает работу еще одного системного вызова, rename.
Пользователям UNIX должна быть знакома команда mv оболочки, которая рабо­
тает исключительно с этим вызовом. Ее название отражает еще один из аспектов
вызова, так как он способен не только менять имя файла, но и эффективно пере­
мещать его из одного каталога в другой, причем операция перемещения атомар­
ная, что позволяет избежать условий гонок. Этот вызов обрабатывается функцией
do_r ename (строка 27 1 62). Перед выполнением команды проверяется множест­
во условий, среди которых есть следующие:
+ исходный файл должен существовать (строка 27 1 77);
+ новый путь не должен быть подкаталогом старого (строки 272 1 7-272 18);
+ новое имя не может быть ни записью точки ( . ), ни записью две точки ( . . )
(строка 2722 1 ) ;
+ исходный и конечный каталоги должны находиться н а одном устройстве
(строки 27224-27225);
+ как исходный, так и конечный каталоги должны быть доступны для записи
и поиска файла и располагаться на устройстве, доступном для записи (строки
2 7 1 95-272 1 2);
+ ни старое, ни новое имя не могут обозначать каталог, в который смонтирова-
на файловая система.
Если одноименный файл существует, нужно проверить еще ряд дополнительных
условий. Самое главное - должно быть разрешено удалить имеющийся файл.
Несколько приемов, реализованных в коде do_rename , служат для снижения
риска возникновения некоторых проблем. Если при переименовании файл с но­
вым именем уже существует, то при заполненном диске может произойти ошиб­
ка, невзирая на то, что в конечном итоге дополнительное место не используется.
Чтобы обойти эту проблему, старый файл сначала удаляется, за это отвечают
строки кода с 27260 по 27266. По тем же соображениям из каталога сначала уда­
ляется старое имя файла (в строке 27280), а затем в него записывается новое во
избежание выделения для каталога нового блока. Это соображение неприменимо
к случаю, когда новый и старый файлы расположены в разных каталогах, поэто­
му в строке 27285 сначала создается новое имя, а затем удаляется старое. Такой
подход должен снизить риск повреждения файловой системы, если произойдет
сбой, так как с точки зрения целостности гораздо лучше иметь две ссылки на
один индексный узел, чем индексный узел, на который нет ссылок ни в одном из
каталогов. Вероятность, что в процессе переименования кончится свободное ме­
сто, невысока, а вероятность краха системы еще меньше, но в данном случае ни­
чего не стоит приготовиться к худшему варианту.
652 Глава 5. Файловые системы

Оставшиеся в файле l i nk . с функции обеспечивают работу уже рассмотренных


функций. В дополнение к ним, функция t runc a t e (строка 273 1 6) вызывается
и из некоторых других мест в коде файловой системы. Она проходит по индекс­
ному узлу зона за зоной, освобождая все найденные зоны, а также косвенные
блоки. Функция remove_di r (строка 27375) сначала делает ряд проверок, чтобы
удостовериться, что каталог можно удалить, а затем вызывает функцию un l i nk_
f i l e (строка 274 15). Если никаких ошибок не произошло, запись каталога очи­
щается и счетчик в индексном узле декрементируется.

5 . 7 6 П роч ие вызовы файловой системы


. .

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


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

Управление состоянием катало гов и файлов


Файл s t adi r . с содержит код для шести системных вызовов: chd i r , f chdi r,
chro o t , s t a t , f s t at и f s t at f s . Изучая код l a s t_di r, мы видели, что поиск
файла по имени начинается с проверки первого символа пути. Если первый сим­
вол оказывался косой чертой, брался указатель на корневой каталог, если нет -
на рабочий.
Чтобы сменить текущий рабочий (или текущий корневой) каталог, нужно всего
лишь изменить значения этих двух указателей в таблице процессов. То есть
обратиться к функциям do_chd i r (строка 27542) и do_chroot (строка 27580)
соответственно. Обе они сначала выполняют необходимые проверки, а затем
вызывают функцию change (строка 27594), осуществляющую несколько до­
полнительных проверок, и change_int o (строка 276 1 1 ) с целью открыть новый
каталог и заменить им старый.
Функция do_ f chdi r (строка 27529) поддерживает вызов f c hdi r , который
делает то же самое, что и chd i r , однако в качестве аргумента получает не путь,
а дескриптор файла. Вызов f chdi r проверяет допустимость дескриптора, и в слу­
чае положительного результата вызывает функцию change_in to, фактически
выполняющую всю работу.
В функции do_chdi r есть код, не исполняемый при вызове chd i r пользова­
тельскими процессами (строки 27552-27570). Он предназначен специально для
менеджера процессов, чтобы менять каталог при выполнении вызова ехе с . Когда
пользователь запускает в своем рабочем каталоге файл, скажем, а . out , менед­
жеру процессов проще перейти в этот каталог, чем раздумывать, где он находится.
Оставшиеся два вызова, s t at и f s t at , одинаковы во всем, кроме способа зада­
ния файла. Первому требуется имя файла, в то время как второму - дескриптор
открытого файла - по аналогии с парой вызовов chd i r и f chdi r. Поэтому обе
процедуры верхнего уровня, do_s t a t и do_f s t a t , вызывают функцию s t at_
inode, которая и выполняет все, что требуется. В функции do_s t a t перед вы­
зовом s t at_inode файл сначала открывается, с целью получить его дескрип­
тор. Таким образом, обе эти функции передают в s t a t_inode дескриптор.
5.7. Реализация файловой системы MINIX 3 653

Вся работа функции s t a t_inode (строка 27673) сводится к сбору информации


о файле и записи ее в буфер. Затем этот буфер должен быть явным образом
скопирован в пользовательское адресное пространство, так как он слишком велик
и не помещается в сообщении. Это действие выполняет вызов sys_dat ac opy
ядра (строки 277 1 3-277 14).
Последняя функция, которую мы встречаем - do_f s t a t s (строка 2772 1 ) . Вы­
зов f s t at f s не относится к стандарту POSIX, однако в POSIX определен похо­
жий вызов f s t at v f s , возвращающий значительно более объемную структуру
данных. В MINIX 3 вызов f s t а t f s возвращает лишь одно значение - размер
блока файловой системы. Прототип вызова имеет следующий вид:
_PROTOTYPE ( int f s t a t f s , ( in t f d , s t ru c t s t a t f s * s t ) ) ;

Используемая им структура s t at f s проста и описывается одной строкой:


s t ru c t { o f f_t f_bs i z e ; / * размер блока файло вой сис темы * / } ;

Эти определения находятся в файле inc lude / sy s / s t at f s . h.

З ащита
Механизм защиты в MINIX 3 основан на битах rwx. Это три набора битов, каж­
дый из которых определяет доступность файла для его владельца, группы и для
остальных. Значениями этих битов можно управлять при помощи системного
вызова chmod, инициируемого функцией do_chmod из файла pro t e c t с (стро­ .

ка 27824). Она сначала делает ряд проверок, а затем меняет режим доступа к фай­
лу (строка 27850).
Вызов chown подобен вызову chrnod в том, что он тоже меняет значения внут­
ренних полей индексного узла некоторого файла. Поэтому их реализация тоже
достаточно схожа, хотя do_chown (строка 27862) позволяет менять владельца
файла только суперпользователю. Обычные пользователи вправе применять этот
вызов, чтобы менять принадлежность их собственных файлов к группе.
Вызов urnask позволяет задать маску (хранящуюся в таблице процессов), которая
маскирует биты разрешений при последующих вызовах creat. Весь код уместил­
ся бы в одну строку (27907), если бы не потребность в восстановлении старого
значения маски. Это дополнительное требование утраивает объем кода ( стро­
ки 27906-27908).
При помощи системного вызова ac c e s s процесс может выяснить, разрешен ли
ему определенный способ доступа к файлу (например, на чтение). Этот вызов
реализуется функцией do_ac c e s s (строка 279 14), которая считывает индекс­
ный узел файла, а затем вызывает вспомогательную функцию f orЬi dden (стро­
ка 27938). Та, в свою очередь, проверяет U I D и GID процесса, а также инфор­
мацию в индексном узле и в зависимости от этих данных принимает решение
о том, разрешен доступ или запрещен.
Небольшая процедура re ad_only проверяет файловую систему, в которой распо­
ложен переданный ей индексный узел, и определяет, смонтирована ли она с дос­
тупом только на чтение. Эта функция необходима для предотвращения попыток
записи в такую файловую систему.
654 Глава 5 . Файловые системы

5 . 7 . 7 . И нтерфейс устройств ввода - вывода


Как уже неоднократно упоминалось в этой книге, целью разработки MINIX 3
было повышение надежности операционной системы за счет превращения драйве­
ров устройств в процессы, выполняемые в пользовательском пространстве и не
имеющие доступа к коду и структурам данных ядра. Основным преимуществом
такого подхода является то, что неисправность драйвера устройства не вызывает
краха всей системы, однако есть и другие достоинства. Так, драйверы устройств,
не нужные сразу после запуска системы, могут быть запущены в любой момент
после окончания загрузки. Это подразумевает, что драйверы можно останавли­
вать, перезапускать и заменять во время работы системы. Разумеется, за подоб­
ную гибкость приходится расплачиваться ограничениями - вы не можете запус­
тить несколько драйверов для одного устройства. В то же время, если драйвер
жесткого диска выйдет из строя, его можно перезапустить из копии, находящей­
ся на виртуальном диске.
В MINIX 3 доступ к драйверам устройств осуществляется из файловой системы.
В ответ на пользовательские запросы ввода-вывода файловая система посылает
сообщения драйверам устройств, находящимся в пользовательском пространст­
ве. В таблице dmap хранятся записи для всех возможных типов устройств; она
сопоставляет главные номера устройств с соответствующими драйверами. Сле­
дующие два файла, которые мы рассмотрим, работают с таблицей dmap . Объяв­
ление таблицы находится в файле dmap . с. Этот файл также поддерживает ини­
циализацию таблицы и новый системный вызов devc t l , предназначенный для
запуска, остановки и перезапуска драйверов устройств. Далее мы изучим файл
devi c e . с , поддерживающий обычные действия над устройствами в процессе
работы, такие как open, c l o s e , re ad, wri t e и i o c t l .
При открытии и закрытии устройства, записи и чтении из него таблица dmap
предоставляет имя процедуры, которая вызывается для обслуживания соответ­
ствующей операции. Все эти процедуры расположены в адресном пространстве
файловой системы. Многие из них ничего не делают, однако некоторые вызывают
драйвер устройства, чтобы запросить фактический ввод-вывод. Таблица dmap так­
же содержит номера процессов, соответствующих каждому главному устройству.
При добавлении в M INIX 3 нового устройства в таблицу должна быть вклю­
чена новая строка, указывающая, какие действия нужно предпринять (если нуж­
но) при открытии, закрытии, чтении и записи устройства. Простой пример: если
в MINIX 3 добавляется накопитель на магнитных лентах, при открытии его файла
необходимо проверить, не используется ли он уже кем-либо.
Файл dmap . с начинается с макроопределения DT (строки 281 1 5-28 1 17), исполь­
зуемого для инициализации таблицы dmap . Оно упрощает добавление нового
драйвера устройства при реконфигурировании MINIX 3. Элементы таблицы dmap
определены в файле inc lude /mini x / dmap . h; каждый элемент состоит из ука­
зателя на функцию, вызываемую при операциях open и c l o s e , указателя на
функцию, вызываемую при операциях r e ad и wri t e , номера процесса (индек­
са в таблице процессов, а не PID) и набора флагов. Фактически таблица пред­
ставляет собой массив перечисленных элементов, объявленный в строке 28132.
5.7. Реал изация файловой системы M I N IX 3 655

Глобальный доступ к ней осуществляется через файловый сервер. Размер табли­


цы определяется константой NR_DEV I C E S , равной 32 в описываемой здесь вер­
сии MINIX 3. Это почти вдвое превышает число устройств, поддерживаемых
операционной системой в текущий момент. К счастью, все неинициализирован­
ные переменные в языке С устанавливаются равными нулю, что гарантирует от­
сутствие недостоверной информации в неиспользуемых элементах.
За объявлением dmap следует объявление i ni t_dmap массива макросов DT,
-

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


запись в глобальном массиве на этапе компиляции. Чтобы понять, как использу­
ются макросы, следует рассмотреть несколько из них. Макрос ini t_dmap [ 1 J
определяет запись драйвера памяти, главный номер которого равен 1 . Макрос
имеет следующий вид:
DT ( l , gen_opc l , gen_i o , MEM_PROC_NR , О)

Драйвер памяти всегда присутствует и загружается вместе с загрузочным образом


системы. Первый параметр, равный 1 , указывает на необходимость присутствия
драйвера. Второй и третий параметры определяют, что открытие и закрытие уст­
ройства осуществляет функция gen_opc l , а запись и чтение - функция gen_i o.
Параметр MEM_PROC_NR задает запись в таблице процессов, используемую драй­
вером памяти, а О означает, что ни один флаг не установлен. Теперь рассмотрим
запись драйвера дисковода для дискет; она выглядит следующим образом:
DT ( O , no_dev , 0, 0, DМAP_МUTABLE )

Первое нулевое значение указывает на то, что запись относится к драйверу, ко­
торый не обязан быть в загрузочном образе. По умолчанию при попытке откры­
тия устройства первый указатель вызывает функцию no_dev, возвращающую
вызвавшему процессу ошибку ENODEV (нет такого драйвера). Следующие два
нулевых значения также используются по умолчанию: поскольку устройство
невозможно открыть, нет необходимости вызывать функцию для фактического
ввода-вывода. Нуль в элементе таблицы процессов интерпретируется как отсут­
ствие процесса. Значение DМAP_МUTABL E указывает на то, что изменения этой
записи разрешены (обратите внимание, что отсутствие такого флага в записи
драйвера памяти означает невозможность ее модификации после инициализа­
ции). Система MINIX 3 может быть сконфигурирована как на наличие, так и на
отсутствие драйвера дисковода для дискет в загрузочном образе. Если драйвер
присутствует и определен параметром загрузки l abe l FLOPPY как дисковое
=

устройство по умолчанию, при запуске файловой системы его запись изменяет­


ся. Если драйвера нет в загрузочном образе либо он не является драйвером по
умолчанию, его запись не изменяется с запуском файловой системы. Тем не ме­
нее возможность активации драйвера дискет позднее сохраняется. Как правило,
ее выполняет сценарий / et c / rc при запуске i ni t .
Функция do_devc t l (строка 28157) исполняется первой при обслуживании вы­
зова devc t l . Текущая ее версия очень проста: она распознает два запроса, DEV_
МАР и DEV_UNМAP, причем в последнем случае возвращается ошибка ENOSYS
(функция не реализована). Очевидно, эта мера временная; в случае запроса DEV_
МАР вызывается вторая функция, rnap_dri ver.
656 Глава 5 . Файловые системы

Полезно ознакомиться с текущим использованием вызова devc t l и планами его


применения в будущем. В MINIX 3 поддержку запуска серверов и драйверов
в процессе функционирования операционной системы осуществляет серверный
процесс, называемый сервером реинкарнации ( Reincarnation Server, RS). Ин­
терфейс к серверу реинкарнации предоставляет утилита s e rvi c e , а примеры ее
применения можно найти в файле / e t c / rc . Вот один из них:
s ervi c e up / s bin / f l oppy - dev / dev/ f dO

По этой команде сервер реинкарнации совершает вызов devc t l , чтобы запустить


двоичный файл / s Ь i n / П арру в качестве драйвера для устройства с файлом
/ dev / f d O . С этой целью сервер реинкарнации «запускает• указанный двоич­
ный файл посредством команды ехе с , однако также устанавливает специальный
флаг, откладывающий фактический запуск до тех пор, пока соответствующий
процесс не будет преобразован в системный. После того как процесс загружается
в память и выясняется его номер в таблице процессов, определяется главный но­
мер устройства. Затем эта информация включается в сообщение, адресованное
файловому серверу, который запросил операцию devc t l DEV_МAP. С точки зре­
ния инициализации интерфейса ввода-вывода эта часть работы сервера реин­
карнации наиболее важна. Для полноты описания отметим, что в завершение
инициализации драйвера устройства сервер реинкарнации совершает еще вызов
sys_p r i vc t l , чтобы подготовить запись процесса драйвера в таблице p r i v
и дать ему возможность запуститься. В главе 2 м ы уже говорили о том, что имен­
но наличие собственной записи в таблице p r i v превращает обычный пользова­
тельский процесс в системный.
Сервер реинкарнации является нововведением и в описанной здесь версии MINIX 3
находится в «зачаточном• состоянии. В будущих выпусках операционной систе­
мы планируется сделать сервер реинкарнации мощным компонентом, способным
не только запускать драйверы, но также останавливать и перезапускать их. Кро­
ме того, сервер реинкарнации будет наблюдать за драйверами и автоматически
перезапускать их при появлении проблем. О текущем состоянии разработки вы
можете осведомиться на веб-сайте MINIX 3 (www . minixЗ.org ) и группе новостей
comp.os. m i n ix.
Продолжим рассмотрение файла drnap . с . Функция map_dri ver (строка 281 78)
проста: если для записи в таблице drnap установлен флаг DМAP_MUTABLE, в каж­
дую запись вносятся соответствующие значения. Для открытия и закрытия фай­
ла устройства существуют три варианта функции, один из которых выбирается
на основе значения параметра s t y l e , передаваемого в сообщении от сервера ре­
инкарнации файловой системе (строки 28204-28206). Обратите внимание на то,
что поле drnap_f l ags остается неизменным. Если запись содержит константу
DМAP_МUTABLE, она сохраняет свой статус после вызова devc t l .
Третьей функцией файла drnap . с является bu i l d_map. Она вызывается функ­
цией fs_i n i t при первом запуске файловой системы до входа в основной цикл.
Первое, что она делает, - циклически перебирает все записи в локальной таб­
лице ini t_drnap и копирует развернутые макросы в глобальную таблицу drnap
для каждой записи, у которой в качестве члена drnap_op c l не указана функция
5.7. Реал изация файловой системы MINIX 3 657

no_dev. Это обеспечивает корректность инициализации записей. В противном


случае значения по умолчанию для неинициализированного драйвера задаются
в drnap. Оставшаяся часть функции bu i l d_map более интересна. В загрузочный
образ можно включать несколько драйверов дисковых устройств. По умолчанию
утилита make f i l e , расположенная в s r c / t o o l s , помещает в него драйверы
at_wi n i , Ь i o s_wini и f l oppy. К каждому драйверу добавляется метка, и при­
сваивание l abe l = в параметрах загрузки определяет, какой из них будет фак­
тически загружен в образ и активирован как дисковый драйвер по умолчанию.
Обращения к env_ge t_param в строках 28248 и 28250 используют библиотеч­
ные процедуры, которые, в конечном счете, получают строки параметров загруз­
ки l abe l и c ont ro l l er при помощи вызова sys_ge t i n f o ядра. Последней
вызывается функция bu i l d_map (строка 28267), которая изменяет в drnap за­
пись, соответствующую загрузочному устройству. Ключевым моментом здесь
является присваивание номеру процесса значения DRVR_PROC_NR, которое рав­
но 6. Это магическое число; драйвер, запись которого имеет этот номер, является
драйвером по умолчанию.
Теперь мы займемся файлом devi c e . с, содержащим процедуры, необходимые
для ввода-вывода на устройства в процессе работы.
Первая такая процедура - dev_open (строка 28334). Она вызывается другими
компонентами файловой системы (чаще всего функцией common_open из файла
ma i n . с ) , когда выясняется, что операция op en пытается получить доступ к спе­
циальному файлу устройства. Функции l o ad_ram и do_mount также обра­
щаются к процедуре dev_op en. Ее действие аналогично действию ряда других
процедур, которые мы здесь рассмотрим. Она определяет главный номер уст­
ройства, проверяет его допустимость и использует для установки указателя в за­
писи таблицы drnap , а затем вызывает функцию, на которую указывает запись,
в строке 28349:
r = ( * dp - >drnap_opc l ) ( DEV_O PEN , dev , proc , f l ags )

В случае диска вызывается функция gen_op c l , а в случае. терминального устрой­


ства - t ty_op c l . Возврат кода SUS PEND говорит о серьезной проблеме; вызов
open не должен завершаться подобным образом.
Следующий вызов, dev_c l o s e (строка 28357), проще. Предполагается, что обра­
щение к неисправному устройству невозможно, поэтому неудачное завершение
попытки открытия файла не причинит вреда. По этой причине сам код состоит
из единственной строки, в конце которой вызывается процедура * _op c l , так же
как при открытии устройства вызывалась процедура dev_open.
После получения файловой системой уведомления от драйвера устройства про­
исходит обращение к функции dev_s t at u s (строка 28366). Уведомление озна­
чает, что произошло какое-то событие, а указанная функция должна определить,
какое именно, и инициировать ответное действие. Источник уведомления опре­
деляется по номеру процесса, поэтому первое, что нужно сделать, - выполнить
поиск в таблице drnap и найти запись, соответствующую уведомляющему про­
цессу (строки 1837 1 - 1 8373). Не исключено, что уведомление ложное, поэтому
отсутствие искомой записи источника не является ошибкой. Если же источник
658 Глава 5. Файловые системы

обнаружен, выполняется цикл в строках 28378-28398. На каждой итерации про­


цессу драйвера посылается сообщение, запрашивающее его состояние. Ожида­
ются три варианта ответа. Сообщение DEV_REVIVE может быть получено, если
процесс, запросивший ввод-вывод, был приостановлен. В этом случае происходит
обращение к функции revi ve (файл p ip e . с, строка 2 6 1 46). Сообщение DEV_
I O_READY приходит при обращении к устройству вызовом s e l e c t , а DEV_NO_
STATUS ожидается в случае, если приходит одно или оба предыдущих сообще­
ния. По этой причине переменная get_mo re используется для повторения цик­
ла до тех пор, пока не будет получено сообщение DEV_NO_S TATUS.
Когда требуется выполнить фактический ввод-вывод на устройство, из функции
read_wr i t e (строка 25 1 24) или rw_Ы o c k (строка 2266 1 ) вызывается функция
dev_io (строка 28406); read_wr i t e обрабатывает символьные файлы, а rw_
Ы о с k - блочные. Функция dev_i o создает стандартное сообщение (см. табл. 3.3)
и посылает его указанному драйверу устройства, вызвав gen_i o или c t ty_i o
согласно содержимому поля dp - >dmap_dr i ver таблицы dmap. Пока функция
dev_io ожидает ответ от драйвера, файловая система также ждет. В ней нет
внутренней многозадачности. Как правило, ожидание длится недолго (например,
50 мс), и доступ к данным может оказаться невозможным, особенно если они за­
прошены с терминального устройства. В этом случае ответное сообщение может
содержать код SUS PEND; это позволит файловой системе продолжить работу,
приостановив лишь совершившее вызов приложение.
Процедура gen_op c l (строка 28455) вызывается для дисковых устройств -
дискет, жестких дисков и накопителей. Формируется сообщение, и, как для чте­
ния/записи, с помощью таблицы dmap определяется функция, которая должна
отправить его драйверу - gen_i o или c t ty_io. Функция gen_opc l использу­
ется также для закрытия дисковых устройств.
Открытие терминального устройства осуществляет функция t ty_ар е l ( стро­
ка 28482). Возможно, изменив некоторые флаги, она вызывает gen_op c l , и если
вызов сделал терминал управляющим для активного процесса, этот факт фикси­
руется в поле fp_t ty таблицы процессов.
Устройство / dev / t ty является фиктивным и не соответствует какому бы то ни
было реальному устройству. С его помощью пользователь обращается к собст­
венному терминалу, при этом не важно, какой именно физический терминал
применяется. Чтобы открыть или закрыть / dev / t ty, выполняется вызов функ­
ции c t ty_opc l (строка 285 1 8). Она определяет, изменял ли предыдущий вызов
c t ty_opc l содержимое поля fp_t ty записи таблицы процессов, чтобы указать
управляющий терминал.
Системный вызов s e t s i d требует от файловой системы некоторой работы, ко­
торая выполняется функцией do_s e t s i d (строка 28534). Она изменяет запись
текущего процесса в таблице процессов, указывая на то, что он является веду­
щим процессом сеанса и не имеет управляющего процесса.
Большая часть работы системного вызова i o c t l выполняется в файле dev i c e . с.
Здесь же этот вызов появляется потому, что тесно связан с интерфейсом драйве-
5.7. Реализаци я файл овой системы MINIX З 659

ров устройств. При обращении к i o c t l вызывается функция do_i o c t l (строка


28554), которая создает сообщение и посьшает его соответствующему драйверу.
Для управления терминальными устройствами программы, совместимые со
стандартом POSIX, должны использовать одну из функций, объявленных в фай­
ле inc l ude / t e rrni o s . h. С-библиотека преобразует такие функции в вызовы
i o c t l . Вызов i o c t l широко применяется не только для терминальных уст­
ройств (см. главу 3).
Следующая функция, gen_i o (строка 28575), является настоящей 4рабочей
лошадкой• этого файла. Какое бы действие ни выполнялось с файлом -open,
c l o s e , r ead, wr i t e или i o c t l , она всегда доводит его до завершения. По­
скольку устройство / dev / t ty не является физическим, перед отправкой ему
сообщения необходимо найти правильные главный и вспомогательный номера
устройства и подставить их в сообщение. Именно это делает функция c t ty_io
(строка 2865 2 ) . Вызов выполняется с использованием записи фактического
устройства в таблице drnap. В текущей конфигурации MINIX 3 результатом яв­
ляется обращение к функции gen_io.
Функция no_dev (строка 28677) вызывается из записей таблицы, для которых
не существует устройства, например, для сетевого устройства на компьютере без
сетевой поддержки. Функция возвращает значение ENODEV и предотвращает ава­
рийные ситуации при попытках доступа к несуществующим устройствам.
Последней функцией в файле devi c e . с является c l one_opc l (строка 2869 1 ).
После открытия некоторые устройства нуждаются в особой обработке. Они 4КЛо­
нируются•: после успешного открытия происходит подмена устройства новым
с уникальным вспомогательным номером. Такая возможность не используется
в описываемой здесь версии MINIX 3. Тем не менее она применяется при сете­

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


запись в таблице drnap , в поле drnap_opc l которой указана функция c l one_
opc l . К ней обращается сервер реинкарнации, задающий STYLE_CLONE. Когда
функция c l one_opc l открывает устройство, она действует идентично gen_
opc l , однако новый вспомогательный номер устройства может быть возвращен
в поле REP_S TATUS ответного сообщения. В этом случае, если возможно выде­
лить новый индексный узел, создается временный файл. Видимой записи в ката­
логе не создается: в этом нет необходимости, так как файл уже открыт.

В ремя
Каждому файлу сопоставлены три 32-разрядных числа, связанные со временем.
Первые два хранят время последнего доступа к файлу и момент его последнего
изменения. В третьем фиксируется время последнего изменения состояния са­
мого индексного узла. Это время меняется практически при каждом обращении
к файлу, за исключением вызовов read и ехе с . Все значения хранятся в индекс­
ном узле. При помощи системного вызова ut irne владелец файла или суперполь­
зователь может изменить время доступа и изменения. Это делается процедурой
do_u t irne (строка 288 18) из файла t irne . с, которая получает индексный узел
и записывает в него значения времени. Затем сбрасываются флаги, индицирующие,
660 Глава 5. Файловые системы

что требуется обновить время (строка 28848), с целью избежать затратного и не­
нужного здесь вызова c l oc k_t ime.
Как мы знаем из предыдущей главы, реальное время рассчитывается как сумма
времени, отсчитанного от последнего запуска системы и поддерживаемого таймер­
ным заданием, и реального времени запуска. Возврат значения реального времени
осуществляет вызов s t ime. Большую часть его работы делает менеджер процессов,
однако глобальная переменная boo t t ime, хранящая время запуска системы, под­
держивается файловой системой. Каждый раз при поступлении вызова s t ime
менеджер процессов посылает файловой системе сообщение, благодаря кото­
рому ее функция do_s t ime (строка 28859) обновляет переменную boo t t ime .

5 . 7 . 8 . Помержка допол н ител ьных


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

ядра, которые не включены в другие файлы.


Вызов do_ge t sy s i n f o предоставляет интерфейс вызова ядра sys_da t a c opy.
Он поддерживает информационный сервер (Information Server, IS) с целью от­
ладки. Вызов do_get sys i n f o позволяет информационному серверу запраши­
вать коп:ии структур данных файловой системы, чтобы в дальнейшем отображать
их пользователю.
Системный вызов dup дублирует дескриптор файла. Другими словами, он созда­
ет новый дескриптор, указывающий на тот же файл, что передан ему в качестве
аргумента. Существует разновидность вызова dup - dup2 . Обе версии обслужива­
ются одной функцией, do_dup. Она включена в MINIX 3 для поддержки старых
двоичных программ. На сегодняшний день оба вызова считаются устаревшими;
если какой-либо из них встречается в коде, написанном на языке С, С-библиоте­
ка текущей версии MINIX 3 выполняет системный вызов fcnt l .
Вызов f cnt l , обрабатываемый функцией do_ f cnt l , является предпочтитель­
ным методом запрашивания операций над открытым файлом. Запросы формиру­
ются с использованием флагов, описанных в стандарте POSIX и перечисленных
в табл. 5.10. Вызов принимает дескриптор файла, код запроса и дополнительные
аргументы, требуемые конкретным запросом. Например:
dup2 ( f d , f d2 ) ;

Эквивалентом этого устаревшего запроса является такой запрос:


dup2 ( f d , F_DUPFD , f d2 ) ;

Некоторые запросы просто считывают или устанавливают флаг; их код состоит


всего лишь из нескольких строк. Например, запрос F_S ETFD устанавливает бит,
принудительно закрывающий файл, когда процесс, являющийся его владельцем,
выполняет вызов ехе с . Запрос F_GETFD определяет, следует ли закрыть файл
5.7. Реал изаци я файловой системы MINIX 3 66 1

при выполнении ехе с . Запросы F_SETFL и F_GETFL позволяют устанавливать


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

Табл ица 5. 1 О . Параметры запроса системного вызова FCNTL согласно стандарту POSIX
Действие Значение
F_DU PFD Дублирование дескриптора
F_G EТFD Считывание флага закрытия файла при системном вызове ехес
F_S EТFD Установка флага закрытия файла при системном вызове ехес
F_G EТFL Считывание флагов состояния файла
F_SEТFL Установка флагов состояния файла
F_GEТLK Считывание состояния блокирования файла
F_SEТLK Блокирование чтения и записи в файл
F_SEТLКW Блокирование записи в файл
Функция do_f cn.t l также занимается обработкой блокирования файлов. Вызов
с командой F_GETLK, F_SETLK или F_SETLKW преобразуется в вызов l o ck_op,
рассмотренный в предыдущем разделе.
Следующий системный вызов, sync, копирует все блоки и индексные узлы, изме­
ненные со времени записи на диск. Его обработкой занимается функция do_sync.
Она просто выполняет поиск измененных записей во всех таблицах. Индексные
узлы должны быть обработаны первыми, поскольку функция rw_inode сохраня­
ет свои результаты в кэше блоков. После записи в кэш блоков всех модифициро­
ванных индексных узлов все модифицированные блоки сбрасываются на диск.
Системные вызовы f o rk, ехе с , exi t и s e t принадлежат менеджеру процессов,
однако их результаты должны быть доступны и здесь. При создании процесса
посредством f ork необходимо, чтобы ядро, менеджер процессов и файловая сис­
тема знали об этом. Эти «системные вызовы� поступают не от пользовательских
процессов, а от менеджера процессов. Процедуры do_f ork, do_exi t и do_s et
фиксируют соответствующую информацию в части таблицы процессов, принадле­
жащей файловой системе. Процедура do_exe c с помощью процедуры do_
c l o s e ищет и закрывает файлы, которые должны быть закрыты при вызове ехе с .
Последняя функция в файле rni s с . с не является системным вызовом, однако
обрабатывается похожим образом. Функция do_revi ve вызывается, когда драй­
вер устройства завершает работу, затребованную файловой системой, которую
он был не в состоянии выполнить ранее (например, предоставить пользова­
тельскому процессу введенные данные). Файловая система возобновляет про­
цесс и посылает ему ответное сообщение.
Существует системный вызов, поддержка которого осуществляется отдельными
заголовочным и исходным С-файлами. Это - вызов s e l e c t , поддерживаемый
файлами s e l e c t . h и s e l e c t . с. Вызов s e l ec t используется тогда, когда одно­
му процессу (к примеру, сетевому приложению или программе взаимодействия)
требуется работать с несколькими потоками ввода-вывода. Детальное его описа­
ние выходит за рамки темы данной книги.
662 Глава 5. Файловые системы

5. 7 9 Утил иты файловой системы


. .

Файловая система содержит набор утилит - процедур общего назначения, ис­


пользуемых в различных ее областях. Они собраны в файле u t i l i ty . с .
Функция c l ock_t ime посылает сообщения системному заданию, чтобы опреде­
лить текущее реальное время.
Функция f e t ch_name используется потому, что многие системные вызовы при­
нимают в качестве аргумента имя файла. Если имя короткое, оно включается
в сообщение, передаваемое от пользователя файловой системе. В противном слу­
чае сообщение содержит указатель на имя, находящееся в пользовательском
пространстве. Функция f e t c h_name обрабатывает оба варианта и возвращает
полученное имя файла.
Две функции предназначены для обработки общих классов ошибок. Функция
no_sys вызывается в случае, если файловая система получает вызов, не принадле­
жащий ей. Функция panic выводит сообщение и информирует ядро о катастрофи­
ческом событии. Аналогичные функции можно найти в файле pm / ut i l i ty . c ,
расположенном в исходном каталоге менеджера процессов.
Последние две функции, c onv2 и c onv4 , помогают MINIX 3 справиться с про­
блемой порядка следования байтов, различающегося между семействами про­
цессоров. Они вызываются при чтении и записи дисковых структур данных, на­
пример индексных узлов и битовых карт. Порядок следования байтов в системе,
создавшей диск, записан в суперблоке. Если он отличается от порядка, исполь­
зуемого локальным процессором, порядок следования меняется на противо­
положный. Остальная часть файловой системы не должна заботиться о том, как
байты расположены на диске.
Существует еще два файла, содержащие специальные утилиты для обслужива -
ния файловой системы. Файловая система может обратиться к системному зада­
нию, чтобы то установило для нее таймер, однако в случае, если требуется не­
сколько таймеров, файловая система может вести связанный список таймеров по
аналогии с тем, как это делает менеджер процессов (см. предшествующую гла­
ву). Это возможно благодаря поддержке файловой системы в файле t ime r s . с .
Наконец, в MINIX 3 реализован уникальный способ использования CD-ROM,
позволяющий загружать с компакт-диска демонстрационную версию операцион­
ной системы. Файлы MINIX 3 невидимы для операционных систем, поддержи­
вающих только стандартные файловые форматы CD-ROM.

5 . 7. 1 О. П роч ие компоненты M I N IX 3
Менеджер процессов, описанный в предыдущей главе, и файловая система, рас­
смотренная в этой главе, являются серверами пользовательского пространства,
предоставляющими поддержку, которая в традиционных операционных системах
интегрирована в ядро. Конечно же, в MINIX 3 существуют и другие серверные
процессы. Они находятся в пользовательском пространстве, имеют системные
привилегии и должны рассматриваться как компоненты операционной системы.
Рез ю ме 663

Ограниченный объем книги не позволяет рассмотреть их внутреннее устройст­


во, однако здесь мы как минимум упомянем несколько компонентов.
Один из них уже фигурировал в этой главе - это сервер реинкарнации, спо­
собный запускать обычные процессы и превращать их в системные. В текущей
версии MINIX 3 он применяется для запуска драйверов устройств, не входящих
в состав загрузочного образа. В будущих выпусках сервер реинкарнации сможет
останавливать и перезапускать драйверы, а также наблюдать за ними, чтобы
выполнять эти действия автоматически в случае выявления признаков сбоя.
Исходный код сервера реинкарнации находится в каталоге s rc / s e rve r s / rs / .
Вскользь был упомянут информационный сервер. Он используется для генера­
ции отладочных дампов по нажатию соответствующих функциональных клавиш
традиционной клавиатуры персонального компьютера. Исходный код сервера
находится в каталоге sr c / s e rvers / i s / .
Информационный сервер и сервер реинкарнации являются программами отно­
сительно небольшого размера. Существует еще один дополнительный сервер -
сетевой сервер ( INET) весьма внушительного объема. Его образ на диске сопос­
тавим с образом всей операционной системы MINIX 3. Он запускается сервером
реинкарнации аналогично драйверам устройств. Исходный код сетевого сервера
находится в каталоге s rc / s e rve r s / inet / .
Напоследок мы упомянем еще один компонент, который считается не сервером,
а драйвером устройства. Это - драйвер журнала. Поскольку операционная сис­
тема содержит большое число компонентов, работающих как независимые про­
цессы, желательно иметь единый способ обработки диагностических сообщений,
предупреждений и сообщений об ошибках. Решение MINIX 3 состоит в созда­
нии драйвера псевдоустройства / dev / k l og, способного получать сообщения
и обслуживать их запись в файл. Исходный код драйвера журнала находится
в каталоге s rc / drivers / l og / .

Р ез юм е
Со стороны файловая система представляется как коллекция файлов, каталогов
и инструментов для действий над ними. Можно считывать или записывать фай­
лы, создавать и уничтожать каталоги и перемещать файлы из одного каталога
в другой. В большинстве современных файловых систем поддерживается иерар­
хическая структура каталогов, когда в один каталог может быть вложен второй
и т. д., до бесконечности.
Если же смотреть изнутри, открывается совершенно другая картина. Разработ­
чики файловой системы должны заботиться о том, как выделяется место, и от­
слеживать, какой блок какому файлу соответствует. Мы увидели, что в разных
файловых системах структуры каталогов различаются. Надежность и произво­
дительность файловой системы тоже имеют существенное значение.
Исключительно важны как для пользователей, так и для разработчиков системы
вопросы безопасности и защиты. Мы обсудили известные уязвимости старых
664 Глава 5. Файловые системы

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

В опросы и задания
1 . В файловой системе NTFS для именования файлов используется кодировка
Unicode, поддерживающая 1 6-разрядные символы. В чем преимущество име­
нования файлов в Unicode по сравнению с ASCII?
2. Некоторые файлы начинаются с �магического• числа. Для чего оно исполь­
зуется?
3. В табл. 5.2 перечислены некоторые атрибуты файлов, однако в ней отсутству­
ет атрибут четности. Является ли четность полезным атрибутом для файла?
Если да, то как ее можно использовать?
4. Создайте пять различных путей к файлу / e t c / p a s swd. Подсказка: исполь­
зуйте в каталоге записи точка ( . ) и две точки ( . . ).
5. В системах, поддерживающих последовательный доступ, всегда имеется опе­
рация �перемотки• файлов. Нужна ли такая операция в системах, поддержи­
вающих файлы произвольного доступа?
6. В некоторых операционных системах предоставляется системный вызов rename,
позволяющий сменить имя файла. Есть ли разница между использованием
этого системного вызова и копированием файла с новым именем с последую­
щим удалением старого файла?
7. Рассмотрите дерево каталогов на рис. 5.5. Если / u s r / j im является рабочим
каталогом, как будет выглядеть абсолютный путь для файла с относительным
путем . . / a s t / x?
8. Предположим, у файловой системы нет единого корневого каталога, а вместо
этого у каждого пользователя имеется собственный корневой каталог. Делает
ли это файловую систему более гибкой? Обоснуйте ответ.
Вопросы и з адан и я 665

9. В файловой системе UNIX имеется вызов chroot , делающий указанный ката­


лог кор.невым. Оказывает ли это какое-либо влияние на безопасность? Если
да, то какое?
10. В системе UNIX имеется вызов, осуществляющий чтение записи каталога.
Поскольку каталоги представляют собой файлы, является ли создание подоб­
ного вызова необходимостью?
1 1 . Стандартный персональный компьютер способен работать не более чем с че­
тырьмя операционными системами одновременно. Существует ли возмож­
ность увеличить это значение? Какие последствия вызовет ваше предло­
жение?
12. Как говорилось в тексте, выделение непрерывных областей памят» под фай­
лы приводит к фрагментации диска. Является эта фрагментация внутренней
или внешней? Проведите аналогию с предыдущей главой.
13. На, рис. 5.8 показана структура файловой системы F АТ, использовавшейся
в операционной системе MS-DOS. В исходной версии она имела всего 4096 бло­
ков, поэтому таблица с 4096 ( 1 2 бит) записями была приемлема. Если это
решение перенести на системы с 2 32 блоками, сколько памяти заняла бы таб­
лица FAT?
14. Операционная система поддерживает только один каталог, но позволяет хра­
нить в нем произвольное количество файлов с именами любой длины. Мож­
но ли в такой системе имитировать иерархическую файловую систему? Как?
1 5 . Учет свободного дискового пространства может осуществляться с помощью
списков или битовых карт. Дисковые адреса состоят из D бит. При каком
условии для диска из В блоков, F из которых свободны, список займет мень­
ше места, чем битовая карта? Выразите ваш ответ в процентах от объема
диска для D 16.
=

16. Было предложено хранить первую часть каждого файла системы UNIX в том
же дисковом блоке, что и его индексный узел. Каковы преимущества такого
подхода?
17. Производительность файловой системы зависит от процента блоков, которые
удается в нем найти. Напишите формулу для среднего времени удовлетво­
рения запроса блока при частоте успешных обращений, равной h, если об­
служивание запроса с помощью кэша занимает 1 мс, а для считывания блока
с диска требуется 40 мс. Нарисуйте график этой зависимости для значений h
в интервале от О до 1 ,0.
1 8. В чем разница между жесткой связью и символической связью? Назовите
преимущества каждой из них.
19. Назовите три «ловушки�, которых необходимо избегать при резервном копи­
ровании файловой системы.
20. У гибкого диска 4000 цилиндров, каждый из которых содержит 8 штук
5 1 2-блочных дорожек. Время установки составляет 1 мс на одно перемеще­
ние между цилиндрами. Если не пытаться разместить блоки файла впритирку,
666 Глава 5. Файловые систем ы

среднее время установки при перемещении между двумя последовательными


блоками будет равно 5 мс. Если же файловая система предпримет попытку
объединить логически соседние блоки в кластеры, то среднее межблочное
расстояние может быть уменьшено до 2, а время установки - до 1 00 мкс.
Сколько времени потребуется для считывания 1 00-блочного файла в обоих
случаях, если задержка вращения составляет 1 О мс, а время переноса одного
блока равно 20 мкс?
2 1 . Полезно ли периодическое уплотнение дискового пространства? Поясните
ответ.
22. В чем разница между вирусом и червем? Как каждый из них размножается?
23. Закончив учебное заведение, вы получаете должность директора большого
университетского компьютерного центра, где только что отправили свою
древнюю операционную систему на заслуженный отдых и перешли на UNIX.
Вы начинаете работать. Через пятнадцать минут ваша ассистентка вбегает
в кабинет в панике: � какие-то студенты обнаружили алгоритм, которым
мы шифруем наши пароли, и выложили его в Интернете• . Какой будет ваша
реакция?
24. Две студентки с факультета кибернетики, Кэролин и Элинор, обсуждают ин­
дексные узлы. Кэролин утверждает, что память стала настолько дешевой, что
при открытии файла проще и быстрее считать новую копию индексного узла
в таблицу индексных узлов, чем искать этот индексный узел по всей таблице.
Элинор не согласна. Кто прав?
25. Схема защиты Морриса-Томпсона с п-разрядными случайными числами бы­
ла разработана, чтобы затруднить взломщику отгадывание паролей при помо­
щи подготовленного заранее словаря. Защищает ли такая схема от студентов,
пытающихся угадать пароль суперпользователя?
26. У факультета технической кибернетики есть локальная сеть с большим коли­
чеством машин, работающих под управлением операционной системы UNIX.
Пользователь на любой машине может ввести команду вида rna chine4 who,
и эта команда будет выполнена на компьютере rna ch i n e 4 , для чего пользова­
телю не нужно регистрироваться на удаленном компьютере. Это свойство
реализовано следующим образом. Ядро системы машины пользователя по­
сылает команду и ее UID удаленной машине. Надежна ли такая схема, если
ядрам системы можно доверять (например, в случае крупных миникомпьюте­
ров с разделением времени, оснащенных защитным аппаратным обеспечени­
ем)? Что, если одна из машин представляет собой персональный компьютер
студента, на который не установлена аппаратная защита?
27. При удалении файла его блоки, как правило, возвращаются в список свобод­
ных блоков, но их содержимое не стирается. Как вы полагаете, хорошо ли, если
операционная система будет очищать каждый блок перед тем, как его осво­
бодит? Рассмотрите в вашем ответе факторы безопасности и производитель­
ности, а также покажите, какой эффект окажет эта схема на каждый фактор.
Вопросы и задани я 667

28. В данной главе обсуждались различные механизмы защиты: списки манда­


тов, списки управления доступом и биты rwx. Какой из этих механизмов мо­
жет быть применен для каждой из следующих проблем (рассматривая UNIX,
представьте, что группы соответствуют таким категориям, как факультет, сту­
денты, секретари и т. д.):
1) Кен хочет, чтобы его файлы могли читать все, кроме его коллеги по офису;
2) Мич и Стив хотят вместе пользоваться некоторыми секретными файлами;
3) Линда хочет сделать открытыми некоторые из своих файлов.
29. Возможна ли атака с внедрением троянской программы в систему, защищен­
ную списками мандатов?
30. Размер таблицы f i lp сейчас задается константой NR_F I LPS, определенной
в файле f s / с ons t . h. Чтобы приспособить сетевую систему для работы
большего числа пользователей, может потребоваться увеличить константу
NR_PROC S в файле i nc l ude /rnini x / conf i g . h. Как в зависимости от NR_
PROC S нужно изменить NR_F I L P S ?
3 1 . Предположим, произошел технологический прорыв и появилась энергонеза­
висимая память, сохраняющая свое содержимое при исчезновении питания,
а по цене и производительности не отличающаяся от традиционной опера­
тивной памяти. Как это скажется на файловых системах?
32. Символические ссылки представляют собой файлы, косвенно указывающие
на другие файлы или каталоги. В отличие от обычных ссылок, реализованных
в MINIX 3, у символической ссылки есть собственные индексный узел и блок
данных. Блок данных содержит путь к объекту, на который направлена ссыл­
ка, а отдельный индексный узел позволяет ссылке принадлежать другому
пользователю и иметь другие разрешения доступа. Такая ссылка не обязана
находиться на том же устройстве, что и файл, на который она ссылается.
Символические ссылки не являются частью MINIX 3. Реализуйте их под­
держку в MINIX 3.
33. Хотя в настоящий момент размер файлов в MINIX 3 ограничен 32-разряд­
ным указателем, в будущем, с появлением 64-разрядных указателей, возник­
нет возможность работы с файлами объема больше 2 32 - 1 байт. В этом случае
могут понадобиться блоки с тройным уровнем косвенности. Включите в фай­
ловую систему их поддержку.
34. Покажите, как установка параметра ROBUST (в настоящий момент неисполь­
зуемого) делает систему более или менее устойчивой в случае сбоя. Резуль­
тат может быть любым, поскольку такое исследование для текущей версии
MINIX 3 не проводилось. Тщательно изучите, что происходит, когда изме­
ненный блок вытесняется из кэша. Учтите, что модификация блока может
быть дополнена модификацией индексного узла и битовой карты.
35. Разработайте механизм, позволяющий добавить поддержку �иноязычной�
файловой системы, чтобы, например, можно было смонтировать файловую
систему MS-DOS в каталог MINIX 3.
668 Глава 5. Файловые системы

36. Напишите две программы на языке С или в виде сценария интерпретатора, пе­
редающие и принимающие сообщение по тайному каналу в системе MINIX 3.
Подсказки: во-первых, бит разрешения видим, даже если другие способы
доступа к файлу запрещены, во-вторых, команда или системный вызов s l eep
гарантирует задержку фиксированной длительности, заданную аргументом.
Измерьте скорость обмена данными в простаивающей системе, затем создай­
те искусственную высокую нагрузку, запустив множество фоновых процес­
сов, и снова измерьте скорость обмена данными.
37. Реализуйте в MINIX 3 непосредственные файлы (маленькие файлы, храни­
мые в индексном узле и экономящие время доступа к диску).
Гл ава 6
Б и б л и о гр аф и я

В предыдущих пяти главах нами были затронуты разнообразные темы. Эта глава
призвана помочь читателям, желающим заняться углубленным изучением опе­
рационных систем. В пункте 6. 1 приведен список рекомендуемой литературы,
а в пункте 6.2 - еще один список, в котором в алфавитном порядке перечислены
все публикации, цитаты из которых использованы в этой книге.
Полезными источниками публикаций об операционных системах являются сбор­
ники докладов Симпозиума по принципам операционных систем ( Symposium
оп Operatiпg Systems Priпciples ), проводимого раз в два года, Международной
конференции по распределенным компьютерным системам (Iпternatioпal Соп­
fеrепсе оп Distributed Computiпg Systems), проводимой IEEE ежегодно, а также
Симпозиума U S ENIX по проектированию и реализации операционных сис­
тем. Кроме того, в журналах АСМ Traпsactioпs оп Computer Systems и Operatiпg
Systems Review периодически публикуются статьи, имеющие отношение к дан­
ной тематике.

6 . 1 . Рекоме ндуем ая л ите рату ра


Далее представлен список рекомендуемых публикаций по главам этой книги.

6 . 1 . 1 . Вводн ые и общие публ икаци и


+ Bovet апd Cesati, Uпderstaпdiпg the Liпux Kernel, Зrd Ed.
Возможно, эта книга является лучшим выбором для тех, кто желает разобрать­
ся в принципах внутренней организации ядра операционной системы Liпux.
+ Briпch Напsеп, Classic Operatiпg Systems.
Операционные системы существуют уже в течение достаточно дошого времени,
чтобы некоторые из них - те, которым удалось повлиять на место компьюте­
ров в мире, - успели стать классическими. В данной книге собраны 24 статьи
о наиболее значимых операционных системах, разделенных на следующие кате­
гории: с открытым доступом, пакетные, многозадачные, разделения времени,
для персональных компьютеров, распределенные. Книга рекомендуется всем,
кто интересуется историей операционных систем.
670 Глава 6 . Библиография

+ Brooks, The Mythical Man-Month: Essays on Software Engineering.


Остроумная, занимательная и содержательная книга на основе горького опы­
та ее автора рассказывает о том, как не нужно писать операционные системы.
Содержит множество полезных советов.
+ Corbato, �on Building Systerns That Will Fail• .
В своей лекции, приуроченной к вручению премии Тьюринга, автор принци­
па разделения времени рассуждает о многих вопросах, затрагиваемых Брук­
сом в The Mythical Man- Month. Он приходит к выводу о том, что все слож­
ные системы обречены, а чтобы иметь хотя бы небольшой шанс на успех,
абсолютно необходимо избегать сложности в проектировании, стремясь к про­
стоте и изящности.
+ Deitel et al, Operating Systerns, Зrd Ed.
Общий учебник по операционным системам. В дополнение к стандартному
материалу он содержит подробные исследования операционных систем Linux
и Windows ХР.
+ Dijkstra, � му Recollections of Operating Systern Design• .
Воспоминания одного из первых разработчиков операционных систем, нача­
ло которых уходит в те времена, когда термина �операционная система• еще
не существовало.
+ IEEE, Inforrnation Technology - PortaЫe Operating Systern Interface (POSIX),
Part 1 : Systern Application Prograrn Interface (API) [С language] .
Стандарт, некоторые разделы которого вполне доступны для понимания (на­
пример, приложение В, �Rationale and Notes•, объясняющее причины принятых
решений). Преимуществом стандартов является то, что они по определению
не содержат ошибок. Если опечатка в имени макроса оказывается незамечен­
ной в процессе редактуры, она перестает быть ошибкой и становится нормой.
+ Larnpson, �нints for Cornputer Systern Design•.
Автор - один из самых прогрессивных разработчиков инновационных операци­
онных систем в мире. За свой многолетний опыт он собрал множество советов,
рекомендаций и руководств, а затем изложил их в занимательной и содержа­
тельной статье. Как и книгу Брукса, эту статью настоятельно рекомендуется
прочитать каждому заинтересованному разработчику операционных систем.
+ Lewine, POSIX Prograrnrner's Guide.
Данная книга описывает стандарт POSIX в гораздо более доступной форме,
нежели официальный документ, а также включает обсуждения о преобразо­
вании устаревших программ к стандарту POSIX и разработке новых программ
для РОSIХ-подобного окружения. Материал сопровождается многочислен­
ными примерами кода, в том числе несколькими полноценными программа­
ми. В книге описаны все библиотечные функции и заголовочные файлы, тре­
буемые стандартом POSIX.
+ McKusick and Neville-Neil, The Desig11 and lrnplernentation of the FreeBSD
Operating Systern.
6 . 1 . Рекоме ндуемая л итература 67 1

Эта книга описывает внутреннюю работу FreeBSD современной операцион­


-

ной системы семейства UNIX. Она охватывает процессы, ввод-вывод, управле­


ние памятью, сетевые и практически все прочие возможности FreeBSD.
+ Milojicic, « Operating Systems: Now and in the Future� .
Представьте, что вы хотите задать шести ведущим мировым экспертам в об­
ласти операционных систем вопросы о сфере их деятельности и ее перспекти­
вах. Получите ли вы одни и те же ответы? Конечно, нет! Прочитайте, что они
рассказали автору этой книги.
+ Ray and Ray, Visual Quickstart Guide: UNIX, 2nd Ed.
Примеры из нашей книги гораздо легче освоить, если вы чувствуете себя уве­
ренно в обращении с операционными системами UNIX. Данная книга являет­
ся одним из многочисленных руководств для начинающих пользователей
UNI X. Несмотря на различия в реализации, с точки зрения пользователя
MINIX и UNIX выглядят схоже, поэтому эта или другая аналогичная книга
поможет читателю в освоении MINIX.
+ Russinovich and Solomon, Microsoft Windows Internals, 4th Ed.
Если прежде внутренняя работа Windows была для вас «тайной за семью пе­
чатями� , эта книга полностью раскроет тайну. В ней вы найдете исчерпываю­
щие сведения о процессах, управлении памятью, вводе-выводе, сетевых воз­
можностях, безопасности и других аспектах функционирования Windows.
+ Silberschatz et al, Operating System Concepts, 7th Ed.
Еще один учебник по операционным системам, в котором рассмотрены про­
цессы, управление памятью, файлы и распределенные системы. В качестве
примеров показаны операционные системы Linux и Windows ХР.
+ Stallings, Operating Systems, Sth Ed.
Учебник по операционным системам, в котором, помимо всех основных тем,
рассмотрены распределенные системы; есть также приложение, посвященное
теории очередей.
+ Stevens and Rago, Advanced Programming in the UNIX Environment, 2nd Ed.
В этой книге идет речь о написании на языке С программ, использующих интер­
фейс системных вызовов UNIX и стандартную библиотеку С. Представлен­
ные примеры протестированы на ядрах операционных систем FreeBSD 5.2. 1
и Linux 2.4.22, Solaris 9, Darwin 7.4.0, а также на базе FreeBSD/Mach операци­
онной системы Мае OS Х 1 0.3. Имеется детальное описание соотношения
указанных реализаций со стандартом POSIX.

6 . 1 . 2 . П роцессы
+ Andrews and Schneider, « Concepts and Notations for Concurrent Programming�.
Учебное пособие по процессам и взаимодействию между процессами, вклю­
чая активное ожидание, семафоры, мониторы, обмен сообщениями и прочие
методы. В данной статье также показано, как эти концепции поддерживаются
встроенными средствами различных языков программирования.
672 Глава 6. Библ иография

+ Ben-Ari, Principles of Concurrent and Distributed Programming.


Данная книга состоит из трех частей; первая включает главы, посвященные
взаимному исключению, семафорам, мониторам, проблеме обедающих фило­
софов, а также другим темам. Во второй части рассматривается распределен­
ное программирование и полезные языки для него. Третья часть посвящена
принципам реализации параллелизма.
+ Bic and Shaw, Operating System Principles.
Этот учебник по операционным системам включает четыре главы, посвящен­
ные процессам, в которых не только рассматриваются общие принципы, но
и имеется немало материала о реализации.
+ Milo et al., « Process Migration� .
По мере вытеснения суперкомпьютеров кластерными системами вопрос пере­
носа процессов с одних компьютеров на другие (например, с целью выравни­
вания нагрузки) становится все более важным. В данном обзоре авторы рас­
сматривают принципы переноса процессов, его достоинства и недостатки.
+ Silberschatz et al, Operating System Concepts, 7th Ed.
Главы 3-7 этой книги посвящены процессам и взаимодействию между ними,
включая планирование, критические секции, семафоры, мониторы и другие
классические проблемы взаимодействия между процессами.

6 . 1 . З . Ввод-вы вод
+ Chen et al., «RAID: Нigh Performance ReliaЫe Secondary Storage� .
Параллельное использование нескольких жестких дисков для ускорения вво­
да -вывода характерно для высокопроизводительных систем. Авторы рас­
сматривают эту концепцию и исследуют различные варианты ее организации
с точки зрения производительности, стоимости и надежности.
+ Coffman et al., «System Deadlocks�.
Краткий обзор взаимных блокировок, их причин, способов обнаружения и пре­
дотвращения.
+ Corbet et al., Linux Device Drivers, Зrd Ed.
Если вы действительно хотите понять, как работает ввод-вывод, попробуйте
написать драйвер устройства. Эта книга поможет вам сделать это для опера­
ционной системы Linux.
+ Geist and Daniel, «А Continuum of Disk Scheduling Algorithms� .
Описан обобщенный алгоритм планирования головки диска, а также пред­
ставлены многочисленные экспериментальные данные и результаты модели­
рования.
+ Holt, «Some Deadlock Properties of Computer Systems� .
Рассматриваются взаимные блокировки. Автор представляет модель н а осно­
ве направленных графов, с помощью которой можно анализировать некото­
рые ситуации взаимных блокировок.
6. 1 . Рекоме ндуемая литература 673

+ IEEE Computer Magazine, March 1994.


В этом номере опубликованы восемь статей о передовых технологиях ввода­
вывода, моделировании, высокопроизводительных накопителях данных, кэши­
ровании, вводе-выводе параллельных компьютеров и мультимедиа.
+ Levine, «Defining Deadlocks•.
Автор этой небольшой статьи поднимает интересные вопросы, касающиеся
традиционных определений и примеров взаимных блокировок.
+ Swift et al., « Recovering Device Drivers•.
Частота ошибок в драйверах устройств на порядок превышает этот показа­
тель для прочих компонентов кода операционной системы. Существуют ли
возможности повышения надежности? В этой статье рассматривается реше­
ние этой задачи с помощью теневых драйверов.
+ Tsegaye and Foss, «А Comparison of the Linux and Windows Device Driver
Architecture• .
Операционные системы Linux и Windows имеют принципиально различные
архитектуры драйверов устройств. Эти архитектуры, их сходства и различия
рассмотрены в данной публикации.
+ Wilkes et al., «The НР AutoRAID Нierarchical Storage System•.
Важной инновационной разработкой в области высокопроизводительных дис­
ковых систем является RAI D массив небольших дисков, совместно обра­
-

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


авторы описывают систему, созданную ими в лабораториях НР Labs.

6 . 1 . 4 . Управление памятью
+ Bic and Shaw, Operating System Principles.
Три главы этой книги посвящены управлению памятью, а также физической,
виртуальной и общей памяти.
+ Denning, « Virtual Memory•.
Классическая публикация по многим аспектам виртуальной памяти. Автор
был одним из пионеров в данной области и создателем концепции рабочего
множества.
+ Denning, « W orking Sets Past and Present•.
Полезный обзор множества алгоритмов управления памятью и замещения
страниц. Сопровождается объемной библиографией.
+ Denning, «The Locality Principle• .
Новый взгляд на историю принципа локальности и обсуждение его примене­
ния к различным проблемам, не относящимся к замещению страниц.
+ Halpem, «VIM: Taming Software with Hardware• .
В этой провокационной статье автор доказывает, что н а создание, отладку
и поддержку программного обеспечения с оптимизированным использованием
674 Глава 6. Библиография

памяти тратятся огромные денежные средства, причем это касается не только


системного программного обеспечения, но и компиляторов, а также прочих
программ. Автор приводит аргументы, согласно которым с макроэкономиче­
ской точки зрения выгоднее вкладывать деньги в приобретение дополнитель­
ной памяти и работать с простыми и более надежными программами.
+ Knuth, The Art of Computer Programming, Vol. 1 .
В этой книге рассматриваются и сравниваются между собой различные алго­
ритмы управления памятью - первого соответствия, наилучшего соответст­
вия и др.
+ Silberschatz et al, Operating System Concepts, 7th Ed.
Главы 8 и 9 данной книги посвящены управлению памятью, включая подкач­
ку, замещение страниц и сегментирование. Упоминается множество алгорит­
мов замещения страниц.

6 . 1 . 5 . Ф айловые систем ы
+ Denning, «The United States vs. Craig Neidorf� .
Когда молодой хакер получил и опубликовал информацию о принципах ра­
боты системы телефонной связи, ему было предъявлено обвинение в компью­
терном мошенничестве. Данная статья посвящена этому случаю, поднявшему
множество фундаментальных проблем, в том числе свободу слова. Статья
вызвала массу неоднозначных откликов, а сам автор впоследствии выступил
с опровержением.
+ Ghemawat et al., «The Google File System� .
Предположим, вы задались целью разместить дома весь Интернет, чтобы
всегда иметь любую информацию под рукой. Каким будет ваш первый шаг?
Вероятно, купить много персональных компьютеров, скажем, 200 ООО штук.
Никаких особых возможностей от этих компьютеров не потребуется. Ваш
второй шаг - прочитать эту статью и узнать, как этот подход реализован в сис­
теме Google.
+ Hafner and Markoff, Cyberpunk: Outlaws and Hackers on the Computer Frontier.
Три замечательные истории о молодых хакерах, взламывающих компьютеры
по всему миру, рассказанные компьютерным репортером газеты New York
Times и его соавтором. Ранее из-под пера этого репортера вышла история об
интернет-черве.
+ Harbron, File Systems: Structures and Algorithms.
Книга, посвященная проектированию файловой системы, приложениям и про­
изводительности. Содержит описания структур и алгоритмов.
+ Harris et al., Gray Hat Hacking: The Ethical Hacker's Handbook.
В этой книге рассматриваются юридические и этические аспекты тестирова­
ния компьютерных систем на наличие уязвимостей, а также техническая ин­
формация об их возникновении и обнаружении.
6.2. Алфави тн ый с п исок литературы 675

+ McKusick et al., «А Fast File System for UNIX•.


В BSD 4.2 файловая система UNIX была реализована заново. В данной пуб­
ликации рассматривается архитектура новой файловой системы и ее произ­
водительность.
+ Satyanarayanan, «The Evolution of Соdю> .
С развитием мобильных компьютеров необходимость интеграции и синхрони­
зации мобильных и фиксированных файловых систем становится все более
насущной. Первой системой, решающей данную задачу, стала Coda. Ее разви­
тию и функционированию посвящена данная статья.
+ Silberschatz et al Operating System Concepts, 7th Ed.
Главы 1 0 и 1 1 этой книги посвящены файловым системам. В них рассмотре­
ны операции над файлами, методы доступа, семантика согласованности, ката­
логи, защита, реализация, а также ряд других вопросов.
+ Stallings, Operating Systems, 5th Ed.
Глава 16 этой книги содержит немало полезной информации о безопасной
среде. Особое внимание уделено хакерам, вирусам и прочим угрозам.
+ Uppuluri et al., «Preventing Race Condition Attacks on File Systems• .
В некоторых ситуациях предполагается, что две операции выполняются про­
цессом атомарно, то есть непрерывно. Если же другому процессу удается
вторгнуться и выполнить какие-либо действия между этими операциями, без­
опасность системы может оказаться под угрозой. Данная статья рассматривает
эту проблему и предлагает решение.
+ Yang et al., «Using Model Checking to Find Serious File System Errors• .
Ошибки в файловой системе могут привести к потере данных, поэтому е е от­
ладка представляет значительную важность. В этой статье описан формаль­
ный метод, помогающий обнаружить ошибки в файловой системе раньше,
чем они нанесут вред, и представлены результаты его применения к реальной
файловой системе.

6 . 2 . Алфавитн ы й список л итерату ры


1 . Anderson, Т.Е., Bershad, B.N., Lazowska, E.D., and Levy, Н.М.: «Scheduler Acti­
vations: Effective Kernel Support for the User-Level Management of Parallelism,•
АСМ Trans. on Computer Systems, vol. 10, рр. 53-79, Feb. 1992.
2. Andrews, G.R" and Schneider, F . B . : « Concepts and Notations for Concurrent
Programming,• Computing Surveys, vol. 15, рр. 3-43, March 1983.
3. Aycock, ] . , and Barker, К.: «Viruses 1 0 1 , • Proc. Tech. Symp. on Сотр. Sci.
Education, АСМ, рр. 1 52 - 1 56, 2005.
4. Bach, M.J .: The Design of the UNIX Operating System, Upper Saddle River, NJ:
Prentice Hall, 1987.
676 Глава 6. Библиография

5. Bala, К., Kaashoek, M.F., and Weihl, W.: «Software Prefetching and Caching for
Translation Lookaside Buffers,» Proc. First Symp. on Oper. Syst. Design and Imp­
lementation, USENIX, рр. 243--,-254, 1 994.
6. Basili, V.R., and Perricone, В.Т.: «Software errors and Complexity: An Empirical
Investigation,» Commun. of the АСМ, vol. 27, рр. 43-52, Jan. 1984.
7. Bays, С.: «А Comparison of Next-Fit, First-Fit, and Best-Fit, » Commun. of the
АСМ, vol. 20, рр. 1 9 1 - 192, March 1977.
8. Ben-Ari, М: Principles of Concurrent and Distributed Programming, Upper Saddle
River, NJ: Prentice Hall, 1990.
9. Bic, L.F., and SHAW, А. С.: Operating System Principles, Upper Saddle River, NJ:
Prentice Hall, 2003.
10. Boehm, H.-J.: «Threads Cannot Ье Implemented as а Library,» Proc. 2004 АСМ
SIGPLAN Conf. on Prog. Lang. Design and Impl., АСМ, рр. 2 6 1 -268, 2005.
1 1 . Bovet, D.P., and CESAТI, М.: Understanding the Linux Kernel, 2nd Ed., Sebastopol,
СА, O'Reilly, 2002.
12. Brinch Hansen, Р.: Operating System Principles Upper Saddle River, NJ: Prentice
Hall, 1973.
13. Brinch Hansen, Р.: Classic Operating Systems, New York: Springer-Verlag, 200 1 .
1 4 . Brooks, F . Р . , Jr.: The Mythical Man-Month: Essays o n Software Engineering,
Anniversary Ed., Boston: Addison-Wesley, 1995.
15. Cerf, V.G.: «Spam, Spim, and Spit,» Commun. of the АСМ, vol. 48, рр. 39-43,
April 2005.
16. Chen, Н, Wagner, D., and Dean, D.: «Setuid Demystified,» Proc. 1 1th USENIX
Security Symposium, рр. 171 - 1 90, 2002.
17. Chen, Р.М., Lee, Е.К., Gibson, G.A., Katz, R.Н., and Patterson, D.A.: «RAID: Нigh
Performance ReliaЫe Secondary Storage,» Computing Surveys, vol. 26, рр. 145-
185, June 1994.
18. Cheriton, D.R.: «An Experiment Using Registers for Fast Message- Based Inter­
process Communication,» Operating Systems Review, vol. 18, рр. 12-20, Oct. 1984.
19. Chervenak, А., Vellanski, V., and Kurmas, Z . : «Protecting File Systems: А Survey
of Backup Techniques,» Proc. 1 5th Symp. on Mass Storage Systems, I EEE, 1998.
20. Chou, А., Yang, J. -F ., Chelf, В., and Hallem, S.: «An Empirical Study of Operating
System Errors,» Proc. 18th Symp. on Oper. Syst. Prin., АСМ, рр. 73-88, 200 1 .
2 1 . Coffman, E.G., Elphick, M.J ., and Shoshani, А.: «System Deadlocks,» Computing
Surveys, vol. 3, рр. 67-78, June 197 1 .
22. Corbato', F.J .: «On Building Systems That Will Fail,» Commun. of the АСМ, vol.
34, рр. 72�8 1 , Sept. 199 1 .
23. Corbato', FJ., Merwin-Daggett, М., and Daley, R.C: «An Experimental Time-Sharing
System,» Proc. AFIPS Fall Joint Computer Conf., AFIPS, рр. 335-344, 1962.
24. Corbato', FJ., Saltzer, J.H., and Clingen, С.Т.: «MULТI CS - The First Seven
Years,» Proc. AFIPS Spring Joint Computer Conf., AFIPS, рр. 57 1 -583, 1972.
6. 2. Алфавитн ь1й список литературы 677

25. Corbato', FJ., and Vyssotsky, V.A.: •lntroduction and Overview of the MULТICS
System,• Proc. AFIPS Fall Joint Computer Conf., AFIPS, рр. 1 85 - 1 96, 1965.
26. Corbet, ]., Rublni, А., and Kroah-Hartrnan, G. : Linux Device Drivers, 3rd Ed.
Sebastopol, СА: O'Reilly, 2005.
27. Courtois, Р.]., Heyrnans, F., and Parnas, D.L.: • Concurrent Control with Readers
and Writers, • Cornmun. of the АСМ, vol. 10, рр. 667-668, Oct. 197 1 .
28. Daley, R . C . , and Dennis, ] . В . : •Virtual Mernory, Processes, and Sharing in
MULТICS,• Cornrnun. of the АСМ, vol. 1 1 , рр. 306-3 12, Мау 1968.
29. Deitel, Н.М., Deitel, Р. ]., and Choffnes, D. R.: Operating Systerns, 3rd Ed., Upper
Saddle River, NJ: Prentice- Hall, 2004.
30. Denning, D.: •The United states vs. Craig Neidorf,• Cornrnun. of the АСМ, vol. 34,
рр. 22 -43, March 199 1 .
3 1 . Denning, P.J.: •The Working Set Model for Prograrn Behavior,• Cornrnun. of the
АСМ, vol. 1 1 , рр. 323-333, 1968а.
32. Denning, P.J.: •Thrashing: Its Causes and Prevention, • Proc. AFIPS National
Cornputer Conf., AFIPS, рр. 9 1 5-922, 1968Ь.
33. Denning, P.J .: •Virtual Mernory,• Cornputing Surveys, vol. 2, рр. 1 53- 189, Sept.
1970.
34. Denning, P.J .: •Working Sets Past and Present,• IEEE Trans. on Software Engi­
neering, vol. SE-6, рр. 64-84, Jan. 1980.
35. Denning, P.J .: •The Locality Principle,• Cornrnun. of the АСМ, vol. 48, рр. 19-24,
July 2005.
36. Dennis, J.B., and Van Horn, Е.С.: • Prograrnrning Sernantics for Multiprograrnrned
Cornputations,• Cornrnun. of the АСМ, vol. 9, рр. 143 - 1 55, March 1 966.
37. DiВona, С., Ockrnan, S., and Stone, М. eds.: Open Sources: Voices frorn the Open
Source Revolution, Sebastopol, СА: O' Reilly, 1999.
38. Dijkstra, E.W.: • Co-operating Sequential Processes, • in Prograrnrning Languages,
Genuys, F. (Ed.), London: Acadernic Press, 1965.
39. Dijkstra, E.W.: •The Structure of ТНЕ Multiprograrnrning System,• Cornrnun. of
the АСМ, vol. 1 1 , рр. 34 1 -346, Мау 1 968.
40. Dijkstra, E.W.: •Му Recollections of Operating Systern Design,» Operating Systerns
Review, vol. 39, рр. 4-40, April 2005.
4 1 . Dodge, С., Irvine, С., and Nguyen, Т.: •А Study of Initialization in Linux and
OpenBSD,• Operating Systerns Review, vol. 39, рр. 79-93 April 2005.
42. Engler, D., Chen, D.Y., and Chou, А.: • Bugs as Inconsistent Behavior: А General
Approach to Inferring Errors in Systerns Code, • Proc. 18th Syrnp. on Oper. Syst.
Prin., АСМ, рр. 57-72, 200 1 .
4 3 . Engler, D.R., Kaashoek, M.F., and О'Тооlе, ]. Jr.: •Exokemel: An Operating Systern
Architecture for Application-Level Resource Managernent,• Proc. 15th Syrnp. on
Oper. Syst. Prin., АСМ, рр. 2 5 1 -266, 1995.
678 Глава 6. Библиография

44. Fabry, R . S . : « CapaЬility- Based Addressing, • Commun. of the А С М , vol. 1 7 ,


рр. 403-4 12, July 1974.
45. Feeley, M.J., Morgan, W.E., Pighin, F.H., Karlin, A.R., Levy, Н.М., and Thekkath,
С.А.: «lmplementing Global Memory Management in а Workstation CLuster,•
Proc. 15th Symp. on Oper. Syst. Prin., АСМ, рр. 2 0 1 - 2 1 2 , 1 995.
46. Feustal, Е.А.: «The Rice Research Computer - А Tagged Architecture,• Proc.
AFIPS Conf. 1972.
47. Fotheringham, ].: «Dynamic Storage Allocation in the Atlas Including an Automatic
Use of а Backing Store,• Commun. of the АСМ, vol. 4, рр. 435 -436, Oct. 196 1 .
48. Garfinkel, S.L., and Shelat, А.: «Remembrance of Data Passed: А Study of Disk
Sanitization Practices,• IEEE Security & Privacy, vol. 1, рр. 17-27, Jan.-Feb. 2003.
49. Geist, R., and Daniel, S.: «А Continuum of Disk Scheduling Algorithms,• АСМ
Trans. on Computer Systems, vol. 5, рр. 77-92, Feb. 1987.
50. Ghemawat, S., GoЬioff, Н., and Leung., S. -T.: «The Google File System, • Proc.
19th Symp. on Oper. Syst. Prin., АСМ, рр. 29-43, 2003.
5 1 . Graham, R.: «Use of Нigh-Level Languages for System Programming,• Project
МАС Report ТМ- 13, М.1.Т., Sept. 1970.
52. Hafner, К., and Markoff, J.: Cyberpunk: Outlaws and Hackers on the Computer
Frontier, New York: Simon and Schuster, 199 1 .
53. Halpern, М.: «VIM: Taming Software with Hardware,• IEEE Computer, vol. 36,
рр. 2 1 -25, Oct. 2003.
54. Harbron, T.R.: File Systems: Structures and Algorithms, Upper Saddle River, NJ:
Pentice Hall, 1988.
55. Harris, S., Harper, А., Eagle, С., Ness, ]., and Lester, М.: Gray Hat Hacking: The
Ethical Hacker's Handbook, New York: McGraw-Hill Osborne Media, 2004.
56. Hauser, C., JacoЬi, С., Theimer, М., Welch, В., and Weiser, М.: «Using Threads in
Interactive Systems: А Case Study,• Proc. 14th Symp. on Oper. Syst. Prin., АСМ,
рр. 94- 1 05, 1993.
57. Hebbard, В. et al.: «А Penetration Analysis of the Michigan Terminal System•
Operating Systems Review, vol. 14, рр. 7 -20, Jan. 1980.
58. Herborth, С.: UNIX Advanced: Visual Quickpro Guide, Berkeley, СА: Peachpit
Press, 2005.
59. Herder, J.N.: «Towards а True Microkernel Operating System, • M.S. Thesis, Vrije
Universiteit, Amsterdam, Feb. 2005.
60. Hoare, C.A.R.: « Monitors, An Operating System Structuring Concept,• Commun.
of the АСМ, vol. 17, рр. 549-557, Oct. 1974; Erratum in Commun. of the АСМ,
vol. 18, р. 95, Feb. 1975.
61. Holt, R.C: «Some Deadlock Properties of Computer Systems,• Computing Surveys,
vol. 4, рр. 179- 196, Sept. 1972.
62. Huck, ]., and Hays, J.: «Architectural Support for Translation ТаЫе Management
in Large Address Space Machines,• Proc. 20th Annual Int'l Symp. on Computer
Arch., АСМ, рр. 39-50, 1993.
6. 2. Алфавитный с п исок литературы 679

63. Hutchinson, N. C., Manley, S., Federwisch, М., Harris, G., Hitz, D, Kleiman, S, and
O' Malley, S.: •Logical vs. Physical File System Backup,• Proc. Third USENIX
Symp. on Oper. Syst. Design and Implementation, USENIX, рр. 239-249, 1999.
64. IEEE: Information technology - PortaЫe Operating System Interface (POSIX),
Part 1: System Application Program Interface (API ) [ С Language] , New York:
IEEE, 1990.
65. Jacob, В., and Mudge, Т.: •Virtual Memory: Issues of Implementation, • IEEE
Computer, vol. 31, рр. 33-43, June 1998.
66. Johansson, ]., and Riley, S: Protect Your Windows Network: From Perimeter to
Data, Boston: Addison-Wesley, 2005.
67. Kernighan, B.W., and Ritchie, D . M . : The С Programming Language, 2nd Ed.,
Upper Saddle River, NJ: Prentice Hall, 1988.
68. Кlein, D.V.: • Foiling the Cracker: А Survey of, and Improvements to, Password
Security,• Proc. UNIX Security Workshop 11, USENIX, Aug. 1990.
69. Кleinrock, L.: Queueing Systems, Vol. 1 , New York: John Wiley, 1975.
70. Knuth, D.E.: The Art of Computer Programming, Volume 1 : Fundamental Algo­
rithms, 3rd Ed., Boston: Addison-Wesley, 1997.
7 1 . Lampson, B.W.: •А Scheduling Philosophy for Multiprogramming Systems, •
Commun. o f the АСМ, vol. 1 1 , рр. 347-360, Мау 1968.
72. Lampson, B.W.: •А Note on the Confinement ProЬlem,• Commun. of the АСМ,
vol. 10, рр. 6 1 3-615, Oct. 1973.
73. Lampson, B.W.: •Hints for Computer System Design, • IEEE Software, vol. 1,
рр. 1 1 -28, Jan. 1984.
74. Ledin, G., Jr.: •Not Teaching Viruses and Worms is Harmful,• Commun. of the
АСМ, vol. 48, р. 144, Jan. 2005.
75. Leschke, Т.: •Achieving Speed and Flexibllity Ьу Separating Management from
Protection: Embracing the Exokernel Operating System, • Operating Systems
Review, vol. 38, рр. 5 - 1 9, Oct. 2004.
76. Levine, G.N.: •Defining Deadlocks,• Operating Systems Review vol. 37, рр. 54-64,
Jan. 2003а.
77. Levine, G.N.: •Defining Deadlock with FungiЬle Resources,• Operating Systems
Review, vol. 37, рр. 5- 1 1 , July 2003Ь.
78. Levine, G.N . : •The Classification of Deadlock Prevention and Avoidance is
Erroneous, • Operating Systems Review, vol. 39, 47-50, April 2005.
79. Lewine, D.: POSIX Programmer's Guide, Sebastopol, СА: O' Reilly & Associates,
1 99 1 .
80. Li, К., and Hudak, Р.: • Memory Coherence i n Shared Virtual Memory Systems,•
АСМ Trans. on Computer Systems, vol. 7, рр. 32 1 -359, Nov. 1989.
8 1 . Linde, R.R.: •Operating System Penetration,• Proc. AFIPS National Computer
Conf., AFIPS, рр. 361 -368, 1975.
680 Глава 6. Библиография

82. Lions, ].: Lions' Commentary on Unix 6th Edition, with Source Code, San Jose,
СА: Peer-to-Peer Communications, 1996.
83. Marsh, B.D., Scott, M.L., LeЬlanc, T.J ., and Markatos, Е.Р.: «First- Class User­
Level Threads,� Proc. 13th Symp. on Oper. Syst. Prin" АСМ, рр. 1 1 0- 1 2 1 , 199 1 .
84. McHugh, J.A.M., and Deek, F.P.: «An Incentive System for Reducing Malware
Attacks, � Commun. of the АСМ, vol. 48, рр. 94-99, June 2005.
85. McKusick, М.К., Joy, W.N., Leffler, S.J., and Fabry, R.S.: «А Fast File System for
UNIX,� АСМ Trans. on Computer Systems, vol. 2, рр. 1 8 1 - 1 97, Aug. 1984.
86. McKusick, М.К., and Neville-Neil, G.V.: The Design and Implementation of the
FreeBSD Operating System, Addison-Wesley: Boston, 2005.
87. Milo, D " Douglis, F" Paindaveine, У, Wheeler, R" and Z hou, S.: « Process
Migration, � АСМ Computing Surveys, vol. 32, рр. 24 1 -299, July-Sept. 2000.
88. Milojicic, D.: «Operating Systems: Now and in the Future,� IEEE Concurrency,
vol. 7, рр. 1 2 -2 1 , Jan.-March 1999.
89. Moody, G.: Rebel Code Cambridge, МА: Perseus, 200 1 .
90. Morris, R " and Thompson, К.: « Password Security: А Case Нistory,� Commun. of
the АСМ, vol. 22, рр. 594-597, Nov. 1 979.
9 1 . Mullender, S.J" and Tanenbaum, A. S.: «lmmediate Files,� Software - Practice
and Experience, vol. 14, рр. 365-368, April 1984.
92. Naughton, ].: А Brief History of the Future, Woodstock, NY: Overlook Books,
2000.
93. Nemeth, Е" Snyder, G" Seebass, S" and Hein, Т. R.: UNIX System Administation,
3rd Ed" Upper Saddle River, NJ, Prentice Hall, 2000.
94. Organick, Е.1.: The Multics System, Cambridge, МА: М.1.Т. Press, 1972.
95. Ostrand, T.J " Weyuker, E.J" and Bell, R. M.: «Where the Bugs Are,� Proc. 2004
АСМ Symp. on Softw. Testing and Analysis, АСМ, 86-96, 2004.
96. Peterson, G.L.: «Myths about the Mutual Exclusion ProЬlem,� Information Proces­
sing Letters, vol. 12, рр. 1 1 5- 1 1 6, June 198 1 .
97. Prechelt, L.: «An Empirical Comparison of Seven Programming Languages,� IEEE
Computer, vol. 33, рр. 23-29, Oct. 2000.
98. Ray, D.S" and Ray, E.J .: Visual Quickstart Guide: UNIX, 2nd Ed" Berkeley, СА:
Peachpit Press, 2003.
99. RosenЬlum, М" and Ousterhout, J.К.: «The Design and Implementation of а Log­
Structured File System,� Proc. 13th Symp. on Oper. Syst. Prin" АСМ, рр. 1 - 15,
199 1 .
100. Russinovich, М.Е" and Solomon, D.A.: Microsoft Windows Internals, 4th Ed"
Redmond, WА: Microsoft Press, 2005.
101. Saltzer, J.H.: « Protection arrd Control of Information Sharing in M U LTICS,�
Commun. of the АСМ, vol. 17, рр. 388-402, July 1974.
102. Saltzer, J.H" and Schroeder, M.D.: «The Protection of Information in Computer
Systems,� Proc. IEEE, vol. 63, рр. 1278- 1308, Sept. ·1975.
6 . 2. Алфави тн ы й с п исок литературы 68 1

1 03. Salus, Р.Н.: А Quarter Century of UNIX, Boston: Addison-Wesley, 1994.


1 04. Sandhu, R.S.: •Lattice-Based Access Control Models,• Computer, vol. 26, рр. 9- 19,
Nov. 1993.
105. Satyanarayanan, М.: •The Evolution of Coda,• АСМ Trans. on Computer Systems,
vol. 20, рр. 85- 1 24, Мау 2002.
1 06. Seawright, L.H., and MacКinnon, R.A.: •VM/370 - А Study of Multiplicity and
Usefulness,• I В М Systems Journal, vol. 1 8, рр. 4 - 1 7, 1979.
1 07. Silberschatz, А., Galvin, Р.В., and Gagne, G.: Operating System Concepts, 7th Ed.,
New Уork: John Wiley, 2004.
1 08. Stallings, W.: Operating Systems, 5th Ed., Upper Saddle River, NJ: Prentice Hall,
2005.
1 09. Stevens, W.R., and Rago, S. А.: Advanced Programming in the UNIX Environment,
2nd Ed., Boston: Addison-Wesley, 2005.
1 1 0. Stoll, С.: The Cuckoo's Egg: Tracking а Spy through the Maze of Computer
Espionage, New York: DouЬleday, 1989.
1 1 1 . Swift, М.М., Annamalai, М., Bershad, B.N., and Levy, Н.М.: •Recovering Device
Drivers,• Proc. Sixth Symp. on Oper. Syst. Design and Implementation, USENIX,
рр. 1 - 16, 2004.
1 1 2. Tai, К.С., and Carver, R.H.: •VP: А New Operation for Semaphores,• Operating
Systems Review, vol. 30, рр. 5- 1 1 , July 1996.
1 13. Talluri, М., and Hill, M.D.: •Surpassing the TLB Performance of Superpages with
Less Operating System Support,• Proc. Sixth Int'l Conf. on Architectural Support
for Progr. Lang. and Operating Systems, АСМ, рр. 1 7 1 - 182, 1994.
1 14. Talluri, М., Hill, M.D., and Khalidi, У.А.: •А New Page ТаЫе for 64-Ьit Address
Spaces,• Proc. 1 5th Symp. on Oper. Syst. Prin., АСМ, рр. 184-200, 1995.
1 1 5. Tanenbaum, A.S.: Modern Operating Systems, 2nd Ed., Upper Saddle River: NJ,
Prentice Hall, 200 1 .
1 1 6. Tanenbaum, A. S., Van Renesse, R., Staveren, Н. Van, Sharp, G.J ., Mullender, S.J.,
Jansen, J., and Rossum, G. Van: •Experiences with the Amoeba Distributed
Operating System, • Commun. of the АСМ, vol. 33, рр. 46-63, Dec. 1990.
1 1 7. Tanenbaum, A. S., and Van Steen, M . R.: Distributed Systems: Principles and
Paradigms, Upper Saddle River, NJ, Prentice Hall, 2002.
1 1 8. Teory, T.J .: •Properties of Disk Scheduling Policies in Multiprogrammed Computer
Systems,• Proc. AFIPS Fall Joint Computer Conf., AFIPS, рр. 1 - 1 1 , 1972.
1 19. Thompson, К.: • UNIX Implementation,• Bell System Technical Journal, vol. 57,
рр. 193 1 - 1 946, July-Aug. 1978.
120. Treese, W.: •The State of Security on the Internet,• NetWorker, vol. 8, рр. 13-15,
Sept. 2004.
1 2 1 . Tsegaye, М., and FOSS, R.: •А Comparison of the Linux and Windows Device
Driver Architectures,• Operating Systems Review, vol. 38, рр. 8-33, April 2004.
682 Глава 6. Библиография

122. Uhlig, R" Nagle, D" Stanley, Т, Mudge, Т" Secrest, S" and Brown, R: «Design
Tradeoffs for Software-Managed TLBs,• АСМ Trans. on Cornputer Systerns, vol. 12,
рр. 175-205, Aug. 1994.
123. Uppuluri, Р" Joshi, U" and Ray, А.: «Preventing Race Condition Attacks on File
Systerns,• Proc. 2005 АСМ Syrnp. on Applied Cornputing, АСМ, рр. 346-353,
2005.
124. Vahalia, U.: UNIX Internals - The New Frontiers, 2nd Ed" Upper Saddle River,
NJ: Prentice Hall, 1996.
1 25. Vogels, W.: « File Systern Usage in Windows NT 4.0,• Proc. АСМ Syrnp. on
Operating Systern Principles, АСМ, рр. 93- 1 09, 1999.
126. Waldspurger, С.А" and Weihl, W.E.: « Lottery Scheduling: FlexiЬle Proportional­
Share Resource Managernent, • Proc. First Syrnp. on Oper. Syst. Design and
Irnplernentation, USENIX, рр. 1 - 1 1 , 1 994.
127. Weiss, А.: «Spyware Ве Gone,• NetWorker, vol. 9, рр. 18-25, March 2005.
128. Wilkes, J" Golding, R" Staelin, С, and Sullivan, Т.: «The НР AutoRAID Нierarchical
Storage Systern,• АСМ Trans. on Cornputer Systerns, vol. 1 4 , рр. 1 08- 1 36,
Feb. 1996.
129. Wulf, W.A" Cohen, E.S" Corwin, W. M " Jones, А. К" Levin, R" Pierson, С" and
Pollack, FJ.: «HYDRA: The Kernel of а Multiprocessor Operating Systern,• Corn­
rnun. of the АСМ, vol. 17, рр. 337-345, June 1974.
130. Yang, J" Twohey, Р" Engler, D. and Musuvathi, М.: «Using Model Checking to
Find Serious File Systern Errors,• Proc. Sixth Syrnp. on Oper. Syst. Design and
lrnplernentation, USENIX, 2004.
13 1 . Zekauskas, MJ" Sawdon, W.A" and Bershad, B.N.: «Software Write Detection for
а Distributed Shared Mernory, • Proc. First Syrnp. on Oper. Syst. Design and
Irnplernentation, USENIX, рр. 87- 1 00, 1994.
132. Zwicky, E.D.: «Torture-Testing Backup and Archive Prograrns: Things You Ought
to Кnow but ProbaЬly Would Rather Not,• Prof. Fifth Conf. on Large Installation
Systerns Adrnin" USENIX, рр. 1 8 1 - 1 90, 199 1 .
П ри л ожение А
Ус та н о в к а M I N IX 3
В данном приложении рассматриваются вопросы установки операционной систе­
мы MINX 3. Для полной установки требуются компьютер с процессором Pentium
(или совместимым), не менее 16 Мбайт оперативной памяти, 1 Гбайт свободного
дискового пространства, C D - R O M с интерфейсом I D E и жесткий диск с ин­
терфейсом IDE. Минимальная установка (без исходных файлов команд) требует
8 Мбайт оперативной памяти и 50 Мбайт дискового пространства. Поддержка
Serial АТА, USB и SСSI-дисков в настоящий момент отсутствует. За информа­
цией о CD-RO M с интерфейсом USB обращайтесь на сайт www . minixЗ.org.

А . 1 . П одготовка к установке
Если в вашем распоряжении имеется компакт-диск (например, приложенный
к этой книге), вы можете пропустить шаги 1 и 2, однако желательно осведомить­
ся о наличии более новой версии операционной системы на сайте www . minixЗ.org.
Если вы хотите использовать MINIX 3 на симуляторе, сначала обратитесь к пунк­
ту А.5. Если вы не располагаете IDЕ-устройством CD-RO M, получите специаль­
ный загрузочный USВ-образ CD-RO M или воспользуйтесь симулятором.
1. Загрузка образа компакт-диска MINIX 3 из Интернета.
Загрузите образ компакт-диска MINIX 3 с веб-сайта www . minixЗ .org.
2. Создание загрузочного компакт-диска MINIX 3.
Разархивируйте загруженный файл. Вы получите поразрядный файл образа
компакт-диска с расширением . iso и данное руководство. Запишите его на ком­
пакт-диск. Это и будет загрузочный компакт-диск.
Если вы пользуетесь программой Easy CD Creator 5, в меню File (Файл ) выбе­
рите команду Record CD from CD image (З аписать CD из образа) и в появив­
шемся диалоговом окне измените расширение файлов с cif на iso.
При использовании Nero Express 5 выберите команду Disk lmage or Saved Project
(Образ диска или сохраненный проект ) и измените тип на l mage Files (Файлы
образов), далее выберите файл образа и щелкните на кнопке Open (Открыть ).
Выберите устройство CD-RW и щелкните на кнопке Next (Далее).
684 Приложение А. У становка M I N IX 3

Если вы используете Windows ХР и не имеете программы для записи компакт­


дисков, вы можете найти бесплатную программу на сайте alexfeinman .brinkster.netj
isorecorder.htm и использовать ее для создания образа диска.
3. Определение типа имеющейся у вас Ethernet-плaты.
MINIX 3 поддерживает несколько типов Ethernet-плaт для работы в сети че­
рез локальные сети, ADSL и кабель. Это Intel Pro/1 00, Realtek 8029 и 8 1 39,
AMD LANCE и несколько микросхем 3 Corn. Во время установки будет задан
вопрос, какие Ethernet-плaты у вас имеются (если имеются); ответ можно уз­
нать из документации. Есть и альтернативный способ - если вы используете
Windows, запустите диспетчер устройств следующим образом:
+ в Windows 2000 перейдите по цепочке Пуск � Настройка � Панел ь управле ­
ния � Система � Оборудование � Дис петчер устройств (Start � Settings � Control
Panel � System � Hardware � Device Manager);
+ в Windows ХР перейдите по цепочке Пуск � Панел ь у п равлен ия � Система �
Оборудование � Дис петчер устрой ств (Start � Control Panel � System � Hard ­
ware � Device Manager).
На значке Система в панели управления требуется щелкнуть дважды, во всех
остальных случаях достаточно одного щелчка. Раскройте список Се тевые
плат ы (Network Adapters), щелкнув на значке + (плюс) слева от списка, и запи­
шите названия установленных сетевых плат. Если у вас нет сетевой платы,
поддерживаемой MINIX 3, вы все равно сможете запустить систему, но без
поддержки Ethernet.
4. Создание раздела на жестком диске.
Вы можете загружать компьютер с CD-ROM, если вам это нравится, и MINIX 3
будет работать, но чтобы делать что-либо полезное, вы должны создать раздел
на вашем жестком диске. Перед созданием раздела убедитесь, что не забыли
сделать резервные копии на внешних носителях (на CD-ROM или DVD) на
случай ошибок в процессе установки.
Если вы не являетесь экспертом по разбиению дисков на разделы, строго ре­
комендуется прочитать соответствующее интерактивное руководство по ад­
ресу www. minixЗ.org/doc/partitions. htm l. Узнав, как создавать разделы, создайте
раздел размером не менее 50 Мбайт (или 1 Гбайт, чтобы установить все ис­
ходные файлы). Если вы не знаете, как создавать разделы, но у вас установле­
на программа для управления разделами, например Partition Magic, исполь­
зуйте ее для создания раздела. Также убедитесь, что хотя бы один основной
раздел (то есть главная загрузочная запись) свободен. Сценарий установки
MINIX 3 проведет вас через все этапы создания раздела для MINIX в свободном
пространстве, которое может быть как на первом, так и на втором IDЕ-диске.
Если вы используете Windows 95, 98, МЕ или 2000 и ваш диск содержит один
FАТ-раздел, вы можете использовать программу presz 1 3 4 .exe, имеющуюся на
CD-ROM (она также доступна на сайте zeleps.com) для уменьшения его размера,
А. 2 . Загрузка 685

чтобш оставить место под MINIX. Во всех остальных случаях читайте упомя­
нутое интерактивное руководство по адресу www . minixЗ .org/doc/partitions . html.
Если размер вашего диска превышает 1 28 Гбайт, раздел MINIX 3 должен
быть полностью размещен в первых 1 28 гигабайтах (из-за механизма адреса­
ции дисковых блоков).
ВНИ МАН И Е ������

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

А. 2 . Загрузка
На данном этапе у вас должен иметься некоторый объем свободного пространст­
ва на вашем диске. Если вы еще не освободили требуемого пространства, сделай­
те это сейчас. Раздел, предназначенный для MINIX 3, должен существовать.
1 . Загрузка с CD-ROM.
Вставьте CD-ROM в накопитель и загрузите с него компьютер. Если компью­
тер имеет не менее 16 Мбайт оперативной памяти, выберите вариант Regular
(Обычная загрузка), если есть только 8 Мбайт - вариант Small ( Ко мпактная
загрузка). Если компьютер загружается с жесткого диска вместо CD-ROM,
запустите еп� снова, войдите в программу настройки BIOS и установите та­
кой порядок загрузки, при котором сначала идет обращение к CD-ROM, а по­
том к жесткому диску.
2. Вход в качестве пользователя root.
Когда появится запрос на вход в систему, войдите как пользователь root .
После успешного входа в ы увидите приглашение оболочки ( # ). С этого мо­
мента вы работаете в полнофункциональной операционной системе MINIX 3.
Если вы введете следующую команду, то сможете увидеть, какое программ­
ное обеспечение доступно:
ls / u s r / b i n / 1 mo re

Нажмите пробел для прокрутки списка. Чтобы узнать, что делает программа
f oo, наберите команду man f o o . Страницы руководств также доступны по
адресу http ://www . m i n ixЗ .org/manpages/.
3. Запуск сценария установки.
Для запуска сценария установки MINIX 3 на жесткий диск введите команду
s e t up. После этой и всех последующих команд не забудьте нажимать клавишу
Enter. Когда сценарий установки выведет на экран строки с текстом, нажмите
клавишу Enter для продолжения. Если экран внезапно погаснет, нажмите кла­
виши Ctrl+FЗ для <1:программной прокрутки• (может понадобиться только на
очень старых компьютерах). Обозначение Сtrl+кла в иша указывает на то, что
нужно нажать клавишу Ctrl и, удерживая ее, нажать указанную клавишу.
686 При ложение А. Установка M I N IX 3

А. З . Установка на жестки й диск


Перечисленные в этом разделе шаги соответствуют приглашениям н а экране.
1. Select keyboard type ( Выберите тип кл авиатуры ) .
Когда система попросит выбрать вашу национальную клавиатуру, укажите ее.
На этом и других шагах предлагаются значения по умолчанию, указанные
в квадратных скобках. Если вы согласны со значением по умолчанию, просто
нажмите клавишу Eпter. В большинстве шагов стандартные значения - луч­
ший выбор для начинающих. Тип клавиатуры us-swap меняет клавиши Caps
Lock и Ctrl, как принято в UNIX -системах.
2. Select your Ethernet chip ( Выберите ваш тип Ethernet- п лaты).
На данном этапе нужно выбрать драйвер, который будет установлен для ва­
шей сетевой платы (или его отсутствие).
3. Basic minimal or full distribution ( М инимальная или полная установка ) .
Если дискового пространства не хватает, выберите вариант М для минималь­
ной установки, которая включает все бинарные файлы, но устанавливаются
только исходные тексты системы. Исходные тексты программ не устанавли­
ваются. 50 Мбайт достаточно для системы с примитивной конфигурацией.
Если имеется 1 Гбайт или больше, выберите вариант F для полной установки.
4. Create or select а partition for M I N IX 3 ( Создан ие или выбор раздела дл я M I N IX 3).
Вначале будет задан вопрос, являетесь ли вы экспертом по созданию разде­
лов в MINIX 3. Если да, запустится программа part, чтобы дать вам полные
полномочия для редактирования главной загрузочной записи. Если вы не
эксперт, нажмите клавишу Enter для выбора варианта, предлагаемого по умол­
чанию - автоматической пошаговой процедуры форматирования раздела
диска под MINIX 3.
1) Select а disk to install M I N IX 3 ( Выбор диска дл я установки M I N IX 3).
IDЕ-контроллер может иметь до 4 дисков. Сценарий установки просмат­
ривает каждый из них. Игнорируйте любые сообщения об ошибках. Когда
появится список дисков, выберите один и подтвердите выбор. Если у вас
два жестких диска, вы решили установить MINIX 3 на второй из них и есть
проблемы с загрузкой с него, попробуйте найти решение по адресу http ://
www . minix3 .org/org/doc/using2disks . html.
2) Select а disk region ( Выбор раздела диска ) .
Выберите раздел диска, в который будет установлена система M INIX 3 .
У вас есть три варианта:
+ Select а free region ( Выбрать незанятое п ространство ) ;
+ Select а partition to overwrite ( Переза писать су ществу ющ ий раздел ) ;
+ Delete а partition to free up space and merge with adjacent free space ( Удалить
раздел для освобожден и я простран ства и объеди нить с су ществу ющим сво­
бодным местом ) .
А . З . Установка на жесткий диск 687

Для первых двух вариантов введите номер раздела. Для третьего варианта
введите команду de l e t e, а при появлении запроса введите номер раздела.
Этот раздел запишется заново, а его предыдущее содержимое будет поте­
ряно навсегда.
3) Confirm your choices (Подтвердите свой выбор).
В данный момент мы достигли того этапа, после выполнения которого
«пути назад• уже нет. Появится запрос о том, желаете ли продолжать.
Если да, то все данные в выбранной области будут потеряны. Если вы увере­
ны, введите команду y e s и нажмите клавишу Enter. Для выхода из сцена­
рия установки без изменения таблицы разделов нажмите клавиши Ctrl+C.
5. Reinstall Choice ( Переустановка).
Если вы выбрали существующий раздел MINIX 3, на этом шаге вам будет
предоставлен выбор между полной установкой (Full install), которая сотрет все
на разделе, и переустановкой ( Reinstall), которая не затронет ваш существую­
щий раздел / home . Это означает, что вы можете поместить свои персональ­
ные файлы в / home и заменить старую систему новой версией MINIX 3 без
потери своих файлов.
6. Select the size of /home (Выберите размер/hоmе ) .
Выбранный раздел будет разделен на три подраздела: r oot, / u s r и / home.
Последний предназначен для ваших персональных файлов. Определитесь,
сколько дискового пространства должно быть выделено для ваших файлов.
Выбор нужно будет подтвердить.
7. Select а Ыосk size (Выбор размера блока).
Поддерживаются блоки размерами 1 , 2, 4 и 8 Кбайт, но для использования
размера более 4 Кбайт нужно изменить константу и перекомпилировать систе­
му. Если объем оперативной памяти составляет 16 Мбайт или более, выберите
стандартное значение (4 Кбайт); в противном случае задайте 1 Кбайт.
8. Wait for bad Ыосk detection ( Проверка п оврежденных блоков).
Сценарий установки будет сканировать каждый раздел для поиска повреж­
денных блоков. Это может занять несколько минут, возможно 10 минут и бо­
лее на больших разделах; проявите терпение. Если вы абсолютно уверены,
что неисправных блоков нет, можно прекратить сканирование любого из раз­
делов, нажав клавиши Ctrl+C.
9. Wait for files to Ье copied (Ко п ирован ие файлов).
Когда закончится сканирование, файлы будут автоматически скопированы
с CD-RO M на жесткий диск. Вы увидите каждый копируемый файл. Когда
копирование завершится, система M INIX 3 окажется установленной. Вы­
ключите систему, набрав команду shut down. Всегда останавливайте MINIX 3
этим способом для предотвращения потери данных, так как MINIX 3 хра­
нит некоторые файлы на виртуальном диске и копирует их на жесткий диск
только при завершении работы.
688 П риложение А. Установка M I N IX 3

А . 4 . Тести рование
В этом разделе рассказывается, как проверить установленную систему, заново
собрать систему после модификации и затем загрузить ее. Для начала загрузите
вашу новую систему MINIX 3. К примеру, если вы используете контроллер О,
диск О, раздел 3, введите команду boot с О dОрЗ и войдите как пользователь root .
По некоторым причинам номер диска, видимый BIOS (и используемый монито­
ром загрузки), может не совпадать с применяемым MINIX 3. Сначала попробуйте
задействовать номер, предложенный сценарием установки. Это хороший момент
для создания пароля пользователя root (справку вы можете получить с помо­
щью команды man pas swd) .
1 . Компиляция набора тестов.
Для проверки MINIX 3 наберите в командной строке следующую пару команд:
cd / u s r / s rc / t e s t
make

Дождитесь, пока не завершатся все 40 компиляций, а затем выйдите из систе­


мы, нажав клавиши Ctrl+D.
2. Запуск набора тестов.
Для тестирования системы войдите как пользователь Ьin (обязательно) и по­
следовательно введите две команды для запуска тестовых программ:
cd /usr/ src / t e s t
. / run

Все они должны запуститься корректно, но могут выполняться 20 (и более)


минут на быстрой машине и не менее часа на медленной.
ПРИ М ЕЧ АН И Е ��
����­

Если необходимо компилировать набор программ как root, а выполнять, как Ьiп, проверьте,
что бит setuid установлен корректно.
3. Повторная сборка всей операционной системы.
Если все тесты работают правильно, вы можете собрать систему заново пря­
мо сейчас. После установки новой системы это вряд ли необходимо, но если
вы планируете модифицировать систему, вам нужно знать, как собрать ее по­
вторно. Кроме того, это хороший способ ознакомиться с тем, как работает
система. Для просмотра доступных параметров введите две команды:
cd / u s r / s r c / t o o l s
make

Сделайте новый загрузочный образ следующими тремя командами:


su
make c l ean
t ime make image

Вы только что собрали операционную систему, включая ядро и пользователь�


скую часть. Это было недолго, не правда ли? Если у вас имеется накопитель
А. 5 . Ис п ользо вание симулятора 689

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


использования, вставив отформатированную дискету и набрав команду
make f dbo o t

Когда система запросит у вас путь, наберите f d O . Такой подход в настоя­


щее время не работает с U S В -накопителями для гибких дисков, поскольку
в MINIX 3 для них нет драйверов. Для обновления загрузочного образа, уста­
новленного на жесткий диск, введите команду
make hdboo t

4. Остановка и перезагрузка новой системы.


Для загрузки новой системы сначала завершите работу с помощью команды
shut down. Она сохранит некоторые файлы и возвратит вас в монитор загруз­
ки MINIX 3. Для получения информации о работе монитора загрузки набери­
те в нем команду help. За более подробными сведениями обратитесь на сайт
http://www . minix3 .org/man pages/man 8/boot . 8 . html. Теперь вы можете извлечь
компакт-диск или дискету и выключить компьютер.
5. Способы последующей загрузки.
Если ваш компьютер оснащен дисководом для гибких дисков, то простейший
путь загрузить MINIX 3 - вставить новую загрузочную дискету и включить
питание, что займет всего лишь несколько секунд. Альтернативный способ -
загрузиться с компакт-диска MINIX 3, войти как пользователь Ь i n и ввести
команду shut down, чтобы вернуться в монитор загрузки MINIX 3. Затем для
загрузки с файла образа операционной системы на контроллере О, диске О,
в разделе О следует ввести следующую команду:
boot c O d O p O

Конечно, если вы установите MINIX 3 на диск О в раздел 1 , команда должна


выглядеть так:
boot c O dO p l

Третий вариант загрузки - сделать раздел с M INIX 3 активным и исполь­


зовать монитор загрузки для запуска M INIX 3 или любой другой операци­
онной системы. Подробности вы найдете по адресу www . minixЗ .org/manpages/
man B/boot . 8 . html.
Наконец, четвертый способ - установить мультизагрузчик, например LILO
или GRU B ( www .gnu .org/software/gru b). После этого с легкостью можно будет
загружать любую из ваших операционных систем. Обсуждение мультизагруз­
чиков выходит за рамки темы данного руководства; дополнительную инфор­
мацию можно получить по адресу http://www . minixЗ . org/doc.

А . 5 . И спол ьзование си мул ятора


Принципиально иным подходом к использованию системы MINIX 3 является ее
запуск поверх существующей операционной системы, а не непосредственно на
690 При ложение А. Установка M I N IX 3

�голом• компьютере. Для этой цели имеется ряд виртуальных машин, симулято­
ров и эмуляторов. Вот наиболее популярные:
+ VMware (www .vmware . com);
+ Bochs (www . bochs.org);
+ QEMU (www .qemu . org).
Ознакомьтесь с соответствующей документацией. Запуск программы на симуля­
торе аналогичен ее запуску на реальном компьютере, поэтому следует вернуться
к пункту А. 1 , получить компакт-диск с последней версией операцио н ной систе­
мы и продолжить работу согласно данному руководству.
П ри л ожение Б
Сп и с о к фай л ов M I N IX 3
н а ко м п а кт- ди с ке

З а голово ч н ы е фай лы
00000 inc l ude / an s i . h
00200 inc lude / e rrno . h
00900 inc l ude / f cnt l . h
00100 inc lude / l imi t s . h
00700 inc l ude / s igna l . h
00600 inc lude / s t r ing . h
01000 inc lude / t ermi o s . h
01300 i n c l u de / t imers . h
00400 inc lude / u n i s t d . h
04400 inc lude / ibm/ i n t e rrupt . h
043 0 0 inc lude / ibm / p o r t i o . h
04500 inc lude / ibm / po r t s . h
03500 inc lude / m i n i x / c a l lnr . h
03600 inc lude /minix / c om . h
02 3 0 0 inc l ude / m i n i x / c o n f i g . h
02 6 0 0 inc lude /min i x / c ons t . h
04100 inc lude / m i n i x / devi o . h
042 0 0 inc lude /min i x / dmap . h
022 00 inc lude / m i n i x / i o c t l . h
03000 inc lude / m i n ix / ipc . h
02 5 0 0 inc l u d e / m i n i x / sy s ! c o n f i g . h
03200 inc lude / m i n i x / sy s l ib . h
03400 inc l ude / m i n i x / sysut i l . h
02800 i n c l u d e / m i n i x / type . h
01800 inc lude / sy s / d i r . h
02100 inc lude / sy s / i o c ! d i s k . h
02000 inc lude / sy s / i o c t l . h
01600 i n c l u de / sy s / s i g c ontext . h
01700 inc lude / sy s / s t a t . h
01400 inc lude / sy s / type s . h
01900 inc lude / sy s / wa i t . h

Д рай в ер ы
1 0 8 0 0 drive r s / dr ivers . h
1 2 1 0 0 driver s / a t wini / a t wini . c
1 2 0 0 0 drive r s / a t ! w in i / a t wini . h
692 При л ожение Б. С п исок фай л о в M I N IX 3 на компакт-диске

11000 drivers / l ibdr ive r / dr iver . c


10800 drivers / l ibdr ive r / driver . h
1 14 0 0 drive r s / l ibdr ive r / drvl ib . c
10900 driver s / l ibdr ive r / drvl ib . h
11600 drive r s / memo ry / memo ry . c
15900 drivers / t ty / c on s o l e . c
15200 driver s / t t y / k eyboard . c
13600 drive r s / t ty / t ty . c
13400 drivers / t ty / t ty . h

Sl д po
1 0 4 0 0 kerne l / c l o c k . c
0 4 7 0 0 kerne l / c o n f i g . h
0 4 8 0 0 kerne l / c on s t . h
0 8 0 0 0 kerne l / except i on . c
0 5 3 0 0 kerne l / g l o . h
0 8 1 0 0 kerne l / i 8 2 5 9 . c
0 5 4 0 0kerne l / ipc . h
0 4 6 0 0 kerne l / kerne l . h
0 8 7 0 0 kerne l / k l ib . s
0 8 8 0 0 kerne l / k l iЬ3 8 6 . s
0 7 1 0 0 kerne l / ma i n . c
0 6 2 0 0 kerne l / mpx . s
0 6 3 0 0 k e rne l / mpx3 8 6 . s
0 5 7 0 0 kerne l / pr i v . h
0 7 4 0 0 kerne l / proc . c
0 5 5 0 0 kerne l / proc . h
0 8 3 0 0 kerne l / pr o t e c t . c
0 5 8 0 0 kerne l / prot e c t . h
0 5 1 0 0 kerne l / pro t o . h
0 5 6 0 0 kerne l / s c ons t . h
0 6 9 0 0 kerne l / s t a r t . c
0 9 7 0 0 kerne l / sy s t em . c
0 9 6 0 0 kerne l / sy s t em . h
1 0 3 0 0 kerne l / sy s t em / do ехес . с
1 0 2 0 0 kerne l / sy s t em / do s e t a l a rm . c
0 6 0 0 0 kerne l / t aЫ e . c
0 4 9 0 0 ke rne l / type . h
0 9 4 0 0 kernel / u t i l i t y . c

Ф айло в ая систем а
21600 s e rvers / f s / bu f . h
22400 s erver s / f s / cache . c
21000 s e rvers / f s / c o n s t . h
28300 s erve r s / f s / devi c e . c
28100 s e rvers / f s / dmap . c
21700 s e rver s / f s / f i l e . h
23700 s e rver s / f s / f i l edes . c
21500 s erve rs / f s / fproc . h
20900 s erver s / f s / f s . h
Менедже р про цессов 693

21400 s e rvers / f s / g l o . h
22900 s erve r s / f s / inode . c
21900 s e rvers / f s / inode . h
27000 s e rve r s / f s / l i nk . c
23800 s e rve r s / f s / l o c k . c
21800 s e rve r s / f s / l o c k . h
24000 s e rvers / f s / ma i n . c
26700 s e rve rs / f s / mount . c
24500 s e rve r s / f s / open . c
22000 s e rvers / f s / param . h
26300 s e rvers / f s / p a t h . c
25900 s e rve r s / f s / p ipe . c
27800 s e rvers / f s / p r o t e c t . c
21200 s e rvers / f s / p r o t o . h
25000 s e rvers / f s / read . c
27500 s e rvers / f s / s t ad i r . c
23300 s e rvers / f s / super . c
22100 s e rve r s / f s / super . h
22200 s e rver s / f s / t aЫ e . c
28800 s e rvers / f s / t ime . c
21100 s e rvers / f s / type . h
25600 s e rvers / f s / wr i t e . c

М енеджер про ц ессо в


193 00 s e rve r s / pm / break . c
17100 s e rve r s / pm / c on s t . h
187 00 s e rvers / pm / exec . c
18400 s e rve r s / pm / f orkex i t . c
20400 s e rve r s / pm / g e t s e t . c
17500 s e rvers / pm / g l o . h
18000 s e rvers / pm / ma in . c
20500 s e rve r s / pm / mi s c . c
17600 s e rve r s / pm / mpro c . h
17700 s e rve r s / pm/ param . h
17000 s e rve r s / pm / pm . h
17300 s e rve r s / pm / p ro t o . h
19500 s e rve r s / pm / s igna l . c
17800 s e rve r s / pm / t a Ы e . c
20300 s e rve r s / pm / t ime . c
20200 s e rvers / pm/ t ime r s . c
172 00 s e rvers / pm/ type . h
Ал ф а витн ы й у к а з ате л ь

А н
ACL, 593 НТТР, 62
ANS I, 1 58, 353
АТА, 329

1/0, 1 9
в
ШЕ, 313
BIOS, 323, 4 1 6 шт, 1 90, 2 1 7
BSD, 32
IEEE, 32
ЮРL, 1 75
с IPC, 92
C-list, 595 I P L, 547
CMS, 70 IS, 140, 397, 660
cookie, 582 ISA, 1 9
СР/М, 33 i-узел , 6 1
CRC, 3 1 7
C-Threads, 90
CTSS, 30
J
JVM, 7 1

D
DDOS, 58 1 L
DMA, 258 LAMP, 39
DOS, 34, 58 1 LBA, 330
LBA, 48, 33 1
Е LDT, 2 1 7, 461
LFS, 576
ЕСС, 255
ЕШЕ, 326 lock file, 295
EOF, 308 LRU, 445, 6 1 1
LSI, 33

F
FAT, 551
м
FCFS, 3 1 7 М.1.Т. , 30
FIFO, 443 Мае OS Х, 34
FMS, 27 M B R, 142, 547
FORTRAN, 25 M FT, 4 1 8, 553, 558
FS, 139 MINIX, 36
FTP, 62 ммu, 427
Motif, 35
G MPI, 1 1 3
GDT, 1 90, 2 1 6, 461 MRU, 6 1 1
GID, 42 MS-DOS, 34
GUI, 34 M U LТICS, 30
Алф а витн ы й у казатель 695

N А
NFU, 447 абсолютный путь, 543
NRU, 442 аварийный сигнал, 4 1
NТFS, 532, 558 автоконфигурирование, 258
автономный режим, 26
адаптер
о Ethernet, 292
OS/360, 28 ввода-вывода, 323
понятие, 254
сетевой, 1 53
р
адрес
PFF, 453 виртуальный, 427
PID, 50, 145 линейный, 463
РМ, 139, 467 физический, 176
POSIX, 32 адресация блоков
линейная, 330
PPI D, 145
логическая, 3 1 4
PSW, 1 75, 4 1 9
адресное пространство, 40
Р- Threads, 90
активное ожидание, 97, 257
активный раздел, 1 42
R алгоритм
wsclock, 451
RAID, 3 1 5
банкира
RAM, 4 1 4
для нескольких видов ресурсов, 283
ROM, 3 4 , 323, 4 1 4 для одного вида ресурсов, 281
R S , 1 40, 656 быстрого соответствия, 426
второго шанса, 444
s замещения страниц, 440
наилучшего соответствия, 425
SATA, 327
наихудшего соответствия, 425
SLED, 3 1 5 первого соответствия, 424
S P, 67 планирования, 1 1 8
SPOOL, 30 вытесняющий, 120
SSF, 3 1 8 карусельного, 128
System V, 32 невытесняющий, 1 20
приоритетного, 129
циклического, 1 28
т следующего соответствия, 424
TLB, 436 старения, 447
TSS, 196, 2 1 7 часов, 445
элеваторный, 3 1 8
аппаратная прокрутка, 365
u аппаратное прерывание, 1 49
UART, 343 архивация
U I D , 42 инкрементная, 565
UNIX, 32 полная, 565
USB, 297 архитектура
компьютера, 2 1
UTC, 235
набора команд, 1 9
теговая, 596
w асинхронная передача, 262
wsclock, 45 1 асинхронное событие, 240
ассоциативная память, 436
атака
х отказа в обслуживании, 579, 58 1
Х Windows, 34 с черного хода, 584
696 Алфавитн ы й указатель

атомарное действие, 103 взаимное исключение, 94


атрибут взаимодействие между процессами, 4 1 , 92, 168
внутреннего состояния процесса, 84 видеоконтроллер, 341
нерезидентный, 558 видеопамять, 34 1
символа, 342 виртуальная консоль, 39 1
файла, 538 виртуальная машина, 18, 22, 69
аутентификация виртуальная память, 420, 426
пользователя, 586 виртуальное адресное пространсцю, 427
сервера, 1 1 1 виртуальный адрес, 427
виртуальный диск, 142
Б вирус, 5 8 1
включаемый файл, 157
базовая система ввода-вывода, 4 1 6
внешняя фрагментация, 460, 478
базовый регистр, 4 1 9
внутренняя фрагментация, 454, 478
бездисковая рабочая станция, 187
вредоносная программа, 580
безопасное состояние, 28 1
временная метка, 207
безопасность, 578
библиотека совместного доступа, 459 время
библиотечная процедура, 544 всеобщее скоординированное, 235
бит оборота, 122
грязный, 435 отклика, 123
защиты, 434 реальное, 236
изменения, 435 вспомогательный номер устройства, 266
обращения, 435 встраиваемый модуль, 583
ожидания активизации, 102 второстепенное устройство, 62
присутствия/отсутствия, 429 выгружаемый ресурс, 2 7 1
состояния, 257 выделенное устройство, 262
битовая карта, 422 вызов
блок системный, 39, 222
загрузочный, 185, 547, 604 супервизора, 65
косвенный, 552 ядра, 65, 138, 222
недостающий, 568 вытесняющее планирование, 120
страничный, 428
управления
г
памятью, 427
процессом, 86 гарантированное планирование, 132
блокировка гибкая система реального времени, 134
двухфазная, 285 главная загрузочная запись, 142, 547
процесса, 84 главная таблица файлов, 553, 558
файлов, 6 1 7 главный номер устройства, 266
блочное кэширование, 62 главный раздел, 548
блочное устройство ввода-вывода, 253 глобальная таблица дескрипторов, 190, 2 1 6, 461
блочный кэш, 5 7 1 глобальное замещение страниц, 45 1
блочный специальный файл, 45, 535 глобальный дескриптор, 190, 2 1 6
буферизация, 262 гонки, 94
буферный кэш, 57 1 графический интерфейс пользователя, 34
группа
в вторжения, 584
грязный бит, 435
ввод-вывод, 252
аппаратное обеспечение, 252
отображаемый на память, 256, 257 д
программное обеспечение, 2 6 1 дамп
вектор прерываний, 8 7 логический, 566
взаимная блокировка, 1 0 6 , 270 физический, 565
планирования, 273 двоичный семафор, 105
ресурсов, 273 двухфазная блокировка, 285
Алфавитный указатель 697

демон защита
печати, 93, 268 от дурака, 537
понятие, 8 1 , 1 4 1 от несанкционированного доступа, 578
дескриптор понятие, 578
глобальный, 190, 2 1 6 защищенный режим, 1 68
локальный, 2 1 6 злоумышленник, 580
прерывания, 87, 190 зомби, 479
сегмента, 47 1
файла, 44, 6 1 6
шлюза прерывания, 196
и
джиттер, 1 24 идентификатор
ДИСК группы, 42
виртуальный, 142 пользователя, 42
загрузочный, 142 процесса, 50
добавление соли, 587 иерархия памяти, 4 1 4
добровольная блокировка файлов, 6 1 7 ИМЯ
домен защиты, 59 1 пути, 43
доступ файла, 43
последовательный, 537 инверсия приоритета, 100
произвольный, 537 инвертированная таблица страниц, 438
доступность системы, 579 индексный узел, 6 1 , 552
дочерний процесс, 4 1 инициализируемая переменная, 177
драйвер устройства, 139, 254, 263
инкрементная архивация, 565
дружественный интерфейс, 34
инкрементная резервная копия, 565
интерпретатор команд, 40
Е интерфейс
единообразное именование, 2 6 1 графический, 34
дружественный, 34
жестких дисков с интегрированной
ж
электроникой, 325
жесткая связь, 546, 554 командной строки, 34
жесткая система реального времени, 1 34 передачи сообщений, 1 1 3
жидкокристаллический дисплей, 34 1 системного вызова, 32
информационный сервер, 140, 397
3 исключение, 149, 205
зависание, 1 1 5
заголовок к
процедуры, 536
канал, 45
сектора, 255
ввода-вывода, 255
файла, 192, 475, 535
секретный, 599
заголовочный файл, 1 57
канонический режим, 345
загрузочный блок, 185, 547, 604
карта
загрузочный диск, 142
активных сигналов, 148
загрузочный образ, 142, 1 86, 498
;,�адание активных уведомлений, 148
программное, 25 источников прерываний, 1 48
с наименьшим временем завершения, 126 клавиш, 368
системное, 1 38, 222 карусельное планирование, 128
таймерное, 138 каталог, 42, 54 1
замещение страниц, 427 корневой, 43
глобальное, 45 1 рабочий, 43, 544
локальное, 45 1 спулера, 93
опережающее, 450 спулинга, 268
по запросу, 449 страничный, 463
запрос-отзыв, 588 текущий, 544
зарезервированный суффикс, 162 квант, 1 28
698 Алфавитный указатель

клиентский процесс, 73 мандат, 595


клик, 474 маска, 595
ключ, 534 машина
ключевое поле, 534 виртуальная, 18, 22, 69
КОД расширенная, 22
исправления ошибок, 255 машинный язык, 1 9
общий, 470, 476 меандр, 234
опроса, 358 межпроцессное взаимодействие, 4 1
кодовая страница, 346 менеджер
команда тигров, 584 памяти, 4 1 4
консоль процессов, 139
виртуальная, 39 1 метаданные, 535, 538
оператора, 68 метка
контекст, 1 77 адреса удаленных данных, 2 1
контроллер временная, 207
прерываний, 196 локальная, 1 99
устройства, 254 механизм планирования, 135
контрольная сумма, 255 микроархитектурный уровень, 1 9
конфиденциальность данных, 579 микрокомпьютер, 3 3
корзина, 564 микропрограмма, 1 9
корневая файловая система, 44 микропроцессор, 3 3
корневой каталог, 43 многозадачность, 2 9 , 7 9
косвенный блок, 552 многопроцессорная система, 7 8
коэффициент использования процессора, 1 2 2 многоуровневая операционная система, 6 7
критическая секция, 9 4 мода, 63
куча, 420 модель
кэш процессов, 78
блочный, 57 1 рабочего набора, 449
буферный, 57 1 модуль
со сквозной записью, 573 встраиваемый, 583
кэширование, 62
программы, 5 1
монитор
л виртуальной машины, 69
легковесный процесс, 89 загрузки, 185
лидер сеанса, 380 обращений, 590
линейная адресация блоков, 330 понятие, 1 06
линейный адрес, 463 монолитная операционная система, 65
ловушка, 223 монтирование, 2 6 1
логическая адресация блоков, 3 1 4 мьютекс, 1 0 5
логическая бомба, 582 мэйнфрейм, 25
логический дамп, 566
логический раздел, 548 н
локальная метка, 199
невыгружаемый ресурс, 2 7 1
локальная таблица дескрипторов, 2 1 6, 461
невытесняющее планирование, 1 20
локальное замещение страниц, 45 1
недостающий блок, 568
локальность обращений, 436, 449
неканонический режим, 345
локальный дескриптор, 2 1 6
непериодические события, 1 34
локальный режим, 35 1
непосредственный файл, 559
лотерейное планирование, 132
нерезидентный атрибут, 558
номер
м страничного блока, 434
магическое число, 535, 604, 622 устройства
макрос проверки поддерживаемых вспомогательный, 266
функций, 1 58, 173 главный, 266
Алфавитный указатель 699

переменная
о инициализируемая, 177
оболочка, 46 условная, 1 07
обработка ошибок, 2 6 1 перехват
обработчик исключения, 53
прерываний, 2 1 5 клавиатурного ввода, 58 1
сигнала, 484 сигнала, 484
образ периодические события, 134
загрузочный, 142, 1 86, 498 пиксел, 34 1
памяти, 40 планирование
системный, 186 в системах реального времени, 1 34
общие права, 597 вытесняющее, 120
общий код, 470, 476 гарантированное, 132
объединение пространств данных и кода, 468 карусельное, 1 28
объект, 593 лотерейное, 132
объявление, 158 механизм, 1 35
оверлей, 426 невытесняющее, 1 2 0
ограничительный регистр, 4 1 9 политика, 135
одноразовый пароль, 587 приоритетное, 1 29
однородный ресурс, 2 7 1 справедливое, 133
операционная система, 1 8 циклическое, 128
многоуровневая, 67 планировщик, 1 1 8
монолитная, 65 допуска, 1 26
распределенная, 35 памяти, 127
сетевая, 35 процессора, 127
опережающее замещение страниц, 450 планируемая система реального времени, 1 34
определение функции, 1 58 поврежденный блок, 320
опрос, 257 подкачка, 29
отказ в обслуживании, 579, 581 подтверждение приема, 1 1 1
открытый исходный код, 39 поиск с перекрытием, 3 1 4
отладочный дамп, 179 поколения компьютеров
относительный путь, 544 второе, 25
ошибка отсутствия страницы, 429 первое, 24
четвертое, 33
п политика планирования, 135
полное имя файла, 43
пакетная обработка, 25 пользовательский режим, 20, 139
палмтоп, 4 1 5 порт ввода- вывода, 256
память последовательный доступ, 537
ассоциативная, 436 постоянная память, 34
виртуальная, 420, 426 почтовый ящик, 1 1 2
иерархия, 4 1 4 право доступа, 59 1
постоянная, 34 преамбула
сжатие, 420 битового потока, 255
уплотнение, 420 управляющей последовательности, 366
папка, 54 1 прерывание, 1 95, 2 1 5
параметр загрузки, 1 86, 325 аппаратное, 149
пароль, 587 программное, 149
первым пришел - первым обслужен, 124 префикс расширенных клавиш, 394
переадресация, 418 префиксный символ, 349
передача приватность, 579
асинхронная, 262 примитив
синхронная, 262 взаимодействия между процессами, 223
сообщений, 1 1 О сообщения, 223
переключение принцип наименьшего уровня привилегий, 598
контекста, 128, 1 77 принципал, 593
процессов, 128 приоритетное планирование, 129
700 Алфавитн ы й указатель

проблема
р
изоляции, 599
инверсии приоритета, 1 00 рабочая станция
обедающих философов, 1 1 3 бездисковая, 1 87
ограниченности буфера, 1 0 1 рабочий каталог, 43, 544
переадресации, 4 1 8 рабочий набор, 449
производителя и потребителя, 1 0 1 раздел
читателей и писателей, 1 1 7 активный, 142
пробуксовка, 449 главный, 548
программа диска, 62, 142
вредоносная, 580 логический, 548
начальной загрузки, 142 расширенный, 548
троянская, 582 фиксированный, 4 1 6
шпионская, 582 разделение
программная независимость от устройств, 2 6 1 времени, 30
программная прокрутка, 365 пространств данных и кода, 468
программное прерыв;щие, 149 процессора, 248
программный поток, 89 разделяемое устройство, 262
произвольный доступ, 537 рандеву, 1 1 3, 147
прокрутка, 365 раскладка клавиатуры, 368
аппаратная, 365 распределенная общая память, 456
программная, 365 распределенная операционная система, 35
промежуточное программное обеспечение, 32 распределенная система, 32
пропорциональность, 123 распределенный отказ в обслуживании, 581
пропускная способность, 122 расширение имени файла, 532
пространство расширенная машина, 22
адресное, 40 расширенный раздел, 548
дисковое, 37 расщепление данных, 3 1 6
оперативной памяти, 23
реальное время, 236
прототип функции, 158
регистр
процедура
базовый, 4 1 9
библиотечная, 544
ограничительный, 4 1 9
понятие, 1 5 1
сегментный, 47 1
процесс, 40
устройства, 1 9
блокировка, 84
режим
дочерний, 4 1
автономный, 26
завершение, 82
без обработки, 345
клиентский, 73
защищенный, 1 68
концепция, 78
канонический, 345
легковесный, 89
локальный, 35 1
модель, 78
ограниченный возможностями неканонический, 345
ввода-вывода, 1 1 9 однократного срабатывания, 234
ограниченный вычислительными пользовательский, 20, 139
возможностями, 1 1 9 с обработкой, 345
реализация, 86 с прерыванием, 352
серверный, 73 супервизора, 20
системный, 140 тактового меандра, 234
создание, 80 ядра, 20
состояние, 84 резервная копия
прямой доступ к памяти, 258 инкрементная, 565
псевдоним, 555 полная, 565
псевдопараллелизм, 78 ресурс, 270
псевдотерминал, 356 выгружаемый, 2 7 1
путь, 6 1 3 невыгружаемый, 2 7 1
абсолютный, 543 однородный, 2 7 1
относительный, 544 роль, 594
Ал фави тны й указатель 70 1

системное уведомление, 472


с системный вызов, 39, 222
самое короткое задание - первое, 1 2 5 системный образ, 1 86
связующее программное обеспечение, 3 2 системный процесс, 140
связь сквозной кэш, 573
жесткая, 546, 554
служба, 140
между файлами, 554
событие асинхронное, 240
символьная, 555
состояние
сеанс, 380
безопасное, 2 8 1
сегмент, 457
зомби, 479
данных, 52
спам, 58 1
состояния задания, 1 96, 2 1 7
специальный файл, 45
стека, 5 2
блочный, 45
текста, 5 2
символьный, 45
сегментация, 456
список
сегментный регистр, 4 7 1
мандатов, 595
секретный канал, 599
управления доступом, 593
семафор, 103
справедливое планирование, 133
двоичный, 105
файловый, 295 спулинг, 29, 268
сервер, 139 старение, 132, 447
информационный, 1 40, 397 стек, 52
реинкарнации, 83, 140, 656 стеклянный телетайп, 344
сетевой, 140 степень многозадачности, 1 27
файловый, 3 1 сторожевой таймер, 238
серверный процесс, 73 страница
сетевая операционная система, 35 кодовая, 346
сетевой адаптер, 153 пространства виртуальных адресов, 428
сетевой сервер, 140 страничный блок, 428
сжатие памяти, 420 страничный каталог, 463
сигнал, 53 стробирование, 335
аварийный, 4 1 субъект, 593
перехват, 484 суперблок, 548, 604
синхронизации, 240 супервизор, 20
сигнатура суперпользователь, 42
загрузочного блока, 604 суффикс, 162
ядра, 184
символ
т
заполнения, 348
префиксный, 349 таблица
символьная связь, 555 дескрипторов, 182
символьное устройство ввода-вывода, 253 глобальная, 190, 2 1 6, 461
символьный специальный файл, 45 локальная, 2 1 6, 461
синхронизация, 1 05 прерываний, 87, 190, 2 1 7
синхронная передача, 262 сегментов, 47 1
система индексных узлов, 6 1
многопроцессорная, 78 подразделов, 548
операционная, 18 процессов, 40, 86
пакетной обработки, 25 раэделов, 142
разделения времени, 30 размещения файлов, 5 5 1
распределенная, 32 свободных участков, 499
реального времени, 1 34 страниц, 430
гибкая, 134 инвертированная, 438
жесткая, 134 многоуровневая, 432
планируемая, 134 таймер
файловая, 1 39, 531 понятие, 233
системное задание, 1 38, 222 сторожевой, 238
702 Алф а ви тный указател ь

таймерное задание, 138 файл ( продолжение)


такт часов, 234 произвольного доступа, 537
тактовый меандр, 234 специальный, 45
теговая архитектура, 596 файловая система, 42, 1 39, 530, 53 1
текущий каталог, 544 корневая, 44
телетайп, 344 с журнальной структурой, 576
терминал, 339, 345 файловый семафор, 295
тик, 234 файловый сервер, 3 1
траектория ресурсов, 282 файловый указатель, 6 1 6
троянская программа, 582 физический адрес, 1 7 6
тупик, 270 физический дамп, 565
фиксированный раздел, 4 1 6
у фрагментация
внешняя, 460, 478
уведомление
внутренняя, 454, 478
процесса, 498
системное, 472
узел индексный, 552 ц
указатель позиции в файле, 6 1 6 целостность данных, 579
уплотнение памяти, 420 циклическое планирование, 1 28
управление
заданиями, 53, 379
ч
нагрузкой, 453
управляющая последовательность, 366 часы, 233
уровень червь, 581
микроархитектурный, 1 9 число магическое, 535, 604
привилегий, 1 8 2 чистильщик, 577
условная переменная, 1 07
устойчивость, 530 ш
устройство шлюз вызова, 465
ввода-вывода
шпионская программа, 582
блочное, 253
символьное, 253
второстепенное, 62 з
выделенное, 262 элеваторный алгоритм, 3 1 8
разделяемое, 262 эхопечать, 347

ф я
файл, 530 ядро
атрибуты, 538 MINIX, 138
блокировки, 295 минимальное, 73
блочный, 535 язык
включаемый, 1 57 ассемблера, 19, 25
заголовочный, 1 57 машинный, 19
непосредственный, 559 программирования, 24
последовательного доступа, 537 ярлык, 555
Ко м п а кт - ди с к M I N IX 3

С и стем ные требован и я


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

Ап паратное обеспечение
Операционная система MINIX 3 предъявляет следующие требования к аппарат­
ному обеспечению:
+ персональный компьютер с процессором Pentium или другим совместимым
с ним процессором;
+ 16 Мбайт или более оперативной памяти;
+ 200 Мбайт или более свободного пространства на жестком диске;
+ драйвер CD-ROM с интерфейсом IDE;
+ жесткий диск с интерфейсом IDE.
Диски с интерфейсами Serial АТА, USB и SCSI не поддерживаются. За альтер­
нативными конфигурациями обратитесь на сайт http ://www . minixЗ.org.

П рограмм ное обеспечение


MINIX 3 является операционной системой. Если в ы желаете сохранить сущест­
вующую операционную систему и данные (рекомендуется), реализовав на ком­
пьютере возможность двойной загрузки, необходимо создать на жестком диске
раздел для M INIX 3. Вы можете воспользоваться следующими средствами:
+ Partition Magic (http://www . powerquest.com/partitionmagic );
+ Partition Resizeг (http ://www .zeleps.com).
Инструкции ищите на сайте http ://www . minixЗ .org/partitions . html.

Установка
Установка может быть полностью выполнена без подключения к Интернету, однако
некоторые специальные документы доступны только на сайте http://www. minixЗ.org.
Исчерпывающие инструкции по установке имеются на компакт-диске в формате
Adobe Acrobat PDF.

Поддержка п родукта
За дополнительной технической информацией о программном обеспечении MINIX,
содержащемся на данном диске, обратитесь на официальный веб-сайт MINIX по
адресу http ://www . minixЗ .org.
Эндрю Таненбаум, Альберт Вудхалл
Операционные системы
Разработка и реализация (+CD)
Классика CS
3-е издание
Перевел с английского А . Кузнецов

Заведующий редакцией А. Кривцов


Руководитель проекта П. Маннинен
Веду�ций редактор О. Некруткина
Научный редактор А . Жданов
Технический редактор Л. Родионова
Литературный редактор А. Жданов
Художник Л. А дуевская
Корректор В. Листова
Верстка Р. Гришанов

Подписано в печать 26. 1 2.06. Формат 70х 1 00/ 1 6. Усл. п. л. 56,76. Тираж 3000. Заказ 36 25.
ООО «Питер Пресс», 198206, Санкт-Петербург, Петергофское шоссе, 73, лит. А29.
Налоговая льгота - общероссийский классификатор продукции ОК 005-93, том 2; 95 3005 - литература учебная.
Отпечатано по технологии CtP в ОАО «Печатный двор» им. А. М. Горького.
1 97 1 1 0, Санкт-Петербург, Чкаловский пр., 1 5 .
В бол ь ш и н стве се р ьезн ых к н и г, посвя ще н н ых о пера цион н ы м с и сте м а м , тео р и и
уделя ется го ра здо бол ь ше в н и м а н ия , ч е м п р а ктике. В это м с м ы сле труды классика
IТ-л ите ратуры Эндрю Та н е н баума сове р ш е н н о у н и к ал ь н ы . Тео р и я в н их все гда
пода ется в хо р о ш е м а ка де м ическом стиле , а п р а ктические зада н ия соста вле н ы
та к и м обра з о м , чтобы ч итател ь м о г почувствовать себя разработч и к о м , р е ш а ю щ и м
реал ь н ы е зада ч и .

Эта леге н да р н а я к н и га ( в о все м м и ре ее з н а ют под н а з ва н и е м « O p e rati n g Syste m s :


Desi g n a n d l m p l e m e n tati o n 11) посвя ще н а тео р и и и п р а ктике разра ботк и
опера цио н н ых систе м . В н е й ск рупул е з н о о п и с ы в а ются п р о цессы и м ежп р о цессное
вза и м оде й ств ие, семафо р ы , м о н ито р ы , п е реда ч а сообще н и й , алго р итм ы ра боты
пла н и ровщика, ввод/вы вод, р а з ре ш е н и е туп и ко в ых ситуа ци й , дра й ве р ы устро й ств,
алго р итм ы упра вле н и я п а м ятью, разра ботка фа йловых систе м , а та кже
затра гиваются воп росы безо п а с н ости и за щиты да н н ых. Н о , в то же в р е м я ,
обсуждается и к о н к ретн а я U N IХ-со в м е сти м а я о п е р а ц и о н н а я с и сте м а M I N IX
и даже п р и водится ее и сходн ы й код (его в ы н а й дете на ком п а кт-диске).
Это позволя ет н е тол ько изучать о с н о в о п ол а га ю щи е п р и н ци п ы , н о и н а бл юдать,
ка к о н и п р и м е н я ются в реал ьн ых о п е р а ци о н н ых систе ма х.

C D - R O M П РИ Л А ГАЕТСЯ

ISBN 978-5-469-0 1 403-4

1 1 1 11 1
9 785469 01 4034

Вам также может понравиться