- •Морис Дж. Бах Архитектура операционной системы unix предисловие
- •Глава 1. Общий обзор особенностей системы
- •1.1 История
- •1.2 Структура системы
- •1.3 Обзор с точки зрения пользователя
- •1.3.1 Файловая система
- •1.3.2 Среда выполнения процессов
- •1.3.3 Элементы конструкционных блоков
- •1.4 Функции операционной системы
- •1.5 Предполагаемая аппаратная среда
- •1.5.1 Прерывания и особые ситуации
- •1.5.2 Уровни прерывания процессора
- •1.5.3 Распределение памяти
- •1.6 Выводы
- •Глава 2. Введение в архитектуру ядра операционной системы
- •2.1 Архитектура операционной системы uniх
- •2.2 Введение в основные понятия системы
- •2.2.1 Обзор особенностей подсистемы управления файлами
- •2.2.2 Процессы
- •2.2.2.1 Контекст процесса
- •2.2.2.2 Состояния процесса
- •2.2.2.3 Переходы из состояния в состояние
- •2.2.2.4 «Сон» и пробуждение
- •2.3 Структуры данных ядра
- •2.4 Управление системой
- •2.5 Выводы и обзор последующих глав
- •2.6 Упражнения
- •Глава 3. Буфер сверхоперативной памяти (кеш)
- •3.1 Заголовки буфера
- •3.2 Структура области буферов (буферного пула)
- •3.3 Механизм поиска буфера
- •3.4 Чтение и запись дисковых блоков
- •3.5 Преимущества и неудобства буферного кеша
- •3.6 Выводы
- •3.7 Упражнения
- •Глава 4. Внутреннее представление файлов
- •4.1 Индексы
- •4.1.1 Определение
- •4.1.2 Обращение к индексам
- •4.1.3 Освобождение индексов
- •4.2 Структура файла обычного типа
- •4.3 Каталоги
- •4.4 Превращение составного имени файла (пути поиска) в идентификатор индекса
- •4.5 Суперблок
- •4.6 Назначение индекса новому файлу
- •4.7 Выделение дисковых блоков
- •4.8 Другие типы файлов
- •4.9 Выводы
- •4.10 Упражнения
- •Глава 5. Системные операции для работы с файловой системой
- •5.1 Open
- •5.2 Read
- •5.3 Wriте
- •5.4 Захват файла и записи
- •5.5 Указание места в файле, где будет выполняться ввод-вывод — lseeк
- •5.6 Closе
- •5.7 Создание файла
- •5.8 Создание специальных файлов
- •5.9 Смена текущего и корневого каталога
- •5.10 Cмена владельца и режима доступа к файлу
- •5.11 Stat и fstат
- •5.12 Каналы
- •5.12.1 Системная функция pipе
- •5.12.2 Открытие поименованного канала
- •5.12.3 Чтение из каналов и запись в каналы
- •5.12.4 Закрытие каналов
- •5.12.5 Примеры
- •5.14 Монтирование и демонтирование файловых систем
- •5.14.1 Пересечение точек монтирования в маршрутах поиска имен файлов
- •5.14.2 Демонтирование файловой системы
- •5.15 Linк
- •5.16 Unlinк
- •5.16.1 Целостность файловой системы
- •5.16.2 Поводы для конкуренции
- •5.17 Абстрактные обращения к файловым системам
- •5.18 Сопровождение файловой системы
- •5.19 Выводы
- •5.20 Упражнения
- •Глава 6. Структура процессов
- •6.1 Состояния процесса и переходы между ними
- •6.2 Формат памяти системы
- •6.2.1 Области
- •6.2.2 Страницы и таблицы страниц
- •6.2.3 Размещение ядра
- •6.2.4 Пространство процесса
- •6.3 Контекст процесса
- •6.4 Сохранение контекста процесса
- •6.4.1 Прерывания и особые ситуации
- •6.4.2 Взаимодействие с операционной системой через вызовы системных функций
- •6.4.3 Переключение контекста
- •6.4.4 Сохранение контекста на случай аварийного завершения
- •6.4.5 Копирование данных между адресным пространством системы и адресным пространством задачи
- •6.5 Управление адресным пространством процесса
- •6.5.1 Блокировка области и снятие блокировки
- •6.5.2 Выделение области
- •6.5.3 Присоединение области к процессу
- •6.5.4 Изменение размера области
- •6.5.5 Загрузка области
- •6.5.6 Освобождение области
- •6.5.7 Отсоединение области от процесса
- •6.5.8 Копирование содержимого области
- •6.6 Приостановка выполнения
- •6.6.1 События, вызывающие приостанов выполнения, и их адреса
- •6.6.2 Алгоритмы приостанова и возобновления выполнения
- •6.7 Выводы
- •6.8 Упражнения
- •Глава 7. Управление процессами
- •7.1 Создание процесса
- •7.2 Сигналы
- •7.2.1 Обработка сигналов
- •7.2.2 Группы процессов
- •7.2.3 Посылка сигналов процессами
- •7.3 Завершение выполнения процесса
- •7.4 Ожидание завершения выполнения процесса
- •7.5 Вызов других программ
- •7.6 Код идентификации пользователя процесса
- •7.7 Изменение размера процесса
- •7.8 Командный процессор shell
- •7.9 Загрузка системы и начальный процесс
- •7.10 Выводы
- •7.11 Упражнения
- •Глава 8. Диспетчеризация процессов и ее временные характеристики
- •8.1 Планирование выполнения процессов
- •8.1.1 Алгоритм
- •8.1.2 Параметры диспетчеризации
- •8.1.3 Примеры диспетчеризации процессов
- •8.1.4 Управление приоритетами
- •8.1.5 Планирование на основе справедливого раздела
- •8.1.6 Работа в режиме реального времени
- •8.2 Системные операции, связанные со временем
- •8.3 Таймер
- •8.3.1 Перезапуск часов
- •8.3.2 Внутренние системные тайм-ауты
- •8.3.3 Построение профиля
- •8.3.4 Учет и статистика
- •8.3.5 Поддержание времени в системе
- •8.4 Выводы
- •8.5 Упражнения
- •Глава 9. Алгоритмы управления памятью
- •9.1 Свопинг
- •9.1.1 Управление пространством на устройстве выгрузки
- •9.1.2 Выгрузка процессов
- •9.1.2.1 Выгрузка при выполнении системной функции fork
- •9.1.2.2 Выгрузка с расширением
- •9.1.3 Загрузка (подкачка) процессов
- •9.2 Подкачка по запросу
- •9.2.1 Структуры данных, используемые подсистемой замещения страниц
- •9.2.1.1 Функция fork в системе с замещением страниц
- •9.2.1.2 Функция exec в системе с замещением страниц
- •9.2.2 "Сборщик" страниц
- •9.2.3 Отказы при обращениях к страницам
- •9.2.3.1 Обработка прерываний по отказу из-за недоступности данных
- •9.2.3.2 Обработка прерываний по отказу системы защиты
- •9.2.4 Замещение страниц на менее сложной технической базе
- •9.3 Система смешанного типа со свопингом и подкачкой по запросу
- •9.4 Выводы
- •9.5 Упражнения
- •Глава 10. Подсистема управления вводом-выводом
- •10.1 Взаимодействие драйверов с программной и аппаратной средой
- •10.1.1 Конфигурация системы
- •10.1.2 Системные функции и взаимодействие с драйверами
- •10.1.2.1 Open
- •10.1.2.2 Closе
- •10.1.2.3 Read и Writе
- •10.1.2.4 Стратегический интерфейс
- •10.1.2.5 Ioctl
- •10.1.2.6 Другие функции, имеющие отношение к файловой системе
- •10.1.3 Программы обработки прерываний
- •10.2 Дисковые драйверы
- •10.3 Терминальные драйверы
- •10.3.1 Символьные списки
- •10.3.2 Терминальный драйвер в каноническом режиме
- •10.3.3 Терминальный драйвер в режиме без обработки символов
- •10.3.4 Опрос терминала
- •10.3.5 Назначение операторского терминала
- •10.3.6 Драйвер косвенного терминала
- •10.3.7 Вход в систему
- •10.4 Потоки
- •10.4.1 Более детальное рассмотрение потоков
- •10.4.2 Анализ потоков
- •10.5 Выводы
- •10.6 Упражнения
- •Глава 11. Взаимодействие процессов
- •11.1 Трассировка процессов
- •11.2 Взаимодействие процессов в версии V системы
- •11.2.1 Сообщения
- •11.2.2 Разделение памяти
- •11.2.3 Семафоры
- •11.2.4 Общие замечания
- •11.3 Взаимодействие в сети
- •11.4 Гнезда
- •11.5 Выводы
- •11.6 Упражнения
- •Глава 12. Многопроцессорные системы
- •12.1 Проблемы, связанные с многопроцессорными системами
- •12.2 Главный и подчиненный процессоры
- •12.3 Семафоры
- •12.3.1 Определение семафоров
- •12.3.2 Реализация семафоров
- •12.3.3 Примеры алгоритмов
- •12.3.3.1 Выделение буфера
- •12.3.3.2 Wait
- •12.3.3.3 Драйверы
- •12.3.3.4 Фиктивные процессы
- •12.4 Система tunis
- •12.5 Узкие места в функционировании многопроцессорных систем
- •12.6 Упражнения
- •Глава 13. Распределенные системы
- •13.1 Периферийные процессоры
- •13.2 Связь типа newcastlе
- •13.3 "Прозрачные" распределенные файловые системы
- •13.4 Распределенная модель без передаточных процессов
- •13.5 Выводы
- •13.6 Упражнения
- •Приложение системные операции
- •Библиография
4.1.2 Обращение к индексам
Ядро идентифицирует индексы по имени файловой системы и номеру индекса и выделяет индексы в памяти по запросам соответствующих алгоритмов. Алгоритм iget назначает индексу место для копии в памяти (Рисунок 4.3); он почти идентичен алгоритму getblk для поиска дискового блока в буферном кеше. Ядро преобразует номера устройства и индекса в имя хеш-очереди и просматривает эту хеш-очередь в поисках индекса. Если индекс не обнаружен, ядро выделяет его из списка свободных индексов и блокирует его. Затем ядро готовится к чтению с диска в память индекса, к которому оно обращается. Ядро уже знает номера индекса и логического устройства и вычисляет номер логического блока на диске, содержащего индекс, с учетом того, сколько дисковых индексов помещается в одном дисковом блоке. Вычисления производятся по формуле
номер блока = ((номер индекса — 1) / число индексов в блоке) + начальный блок в списке индексов
где операция деления возвращает целую часть частного. Например, предположим, что блок 2 является начальным в списке индексов и что в каждом блоке помещаются 8 индексов, тогда индекс с номером 8 находится в блоке 2, а индекс с номером 9 — в блоке 3. Если же в дисковом блоке помещаются 16 индексов, тогда индексы с номерами 8 и 9 располагаются в дисковом блоке с номером 2, а индекс с номером 17 является первым индексом в дисковом блоке 3.
алгоритм iget
входная информация: номер индекса в файловой системе
выходная информация: заблокированный индекс
do
if (индекс в индексном кеше)
if (индекс заблокирован)
sleep (до освобождения индекса);
continue; /* цикл с условием продолжения */
/* специальная обработка для точек монтирования (глава 5) */
if (индекс в списке свободных индексов) убрать из списка свободных индексов;
увеличить счетчик ссылок для индекса;
return (индекс);
/* индекс отсутствует в индексном кеше */
if (список свободных индексов пуст) return (ошибку);
убрать новый индекс из списка свободных индексов;
сбросить номер индекса и файловой системы;
убрать индекс из старой хеш-очереди, поместить в новую;
считать индекс с диска (алгоритм bread);
инициализировать индекс (например, установив счетчик ссылок в 1);
return(индекс);
Рисунок 4.3. Алгоритм выделения индексов в памяти
Если ядро знает номера устройства и дискового блока, оно читает блок, используя алгоритм bread (глава 2), затем вычисляет смещение индекса в байтах внутри блока по формуле:
((номер индекса – 1) mod (число индексов в блоке)) * размер дискового индекса
Например, если каждый дисковый индекс занимает 64 байта и в блоке помещаются 8 индексов, тогда индекс с номером 8 имеет адрес со смещением 448 байт от начала дискового блока. Ядро убирает индекс в памяти из списка свободных индексов, помещает его в соответствующую хеш-очередь и устанавливает значение счетчика ссылок равным 1. Ядро переписывает поля типа файла и владельца файла, установки прав доступа, число указателей на файл, размер файла и таблицу адресов из дискового индекса в память и возвращает заблокированный в памяти индекс.
Ядро манипулирует с блокировкой индекса и счетчиком ссылок независимо один от другого. Блокировка — это установка, которая действует на время выполнения системного вызова и имеет целью запретить другим процессам обращаться к индексу пока тот в работе (и возможно хранит противоречивые данные). Ядро снимает блокировку по окончании обработки системного вызова: блокировка индекса никогда не выходит за границы системного вызова. Ядро увеличивает значение счетчика ссылок с появлением каждой активной ссылки на файл. Например, в разделе 5.1 будет показано, как ядро увеличивает значение счетчика ссылок тогда, когда процесс открывает файл. Оно уменьшает значение счетчика ссылок только тогда, когда ссылка становится неактивной, например, когда процесс закрывает файл. Таким образом, установка счетчика ссылок сохраняется для множества системных вызовов. Блокировка снимается между двумя обращениями к операционной системе, чтобы позволить процессам одновременно производить разделенный доступ к файлу; установка счетчика ссылок действует между обращениями к операционной системе, чтобы предупредить переназначение ядром активного в памяти индекса. Таким образом, ядро может заблокировать и разблокировать выделенный индекс независимо от значения счетчика ссылок. Выделением и освобождением индексов занимаются и отличные от open системные операции, в чем мы и убедимся в главе 5.
Возвращаясь к алгоритму iget, заметим, что если ядро пытается взять индекс из списка свободных индексов и обнаруживает список пустым, оно сообщает об ошибке. В этом отличие от идеологии, которой следует ядро при работе с дисковыми буферами, где процесс приостанавливает свое выполнение до тех пор, пока буфер не освободится. Процессы контролируют выделение индексов на пользовательском уровне посредством запуска системных операций open и close и поэтому ядро не может гарантировать момент, когда индекс станет доступным. Следовательно, процесс, приостанавливающий свое выполнение в ожидании освобождения индекса, может никогда не возобновиться. Ядро скорее прервет выполнение системного вызова, чем оставит такой процесс в «зависшем» состоянии. Однако, процессы не имеют такого контроля над буферами. Поскольку процесс не может удержать буфер заблокированным в течение выполнения нескольких системных операций, ядро здесь может гарантировать скорое освобождение буфера, и процесс поэтому приостанавливается до того момента, когда он станет доступным.
В предшествующих параграфах рассматривался случай, когда ядро выделяет индекс, отсутствующий в индексном кеше. Если индекс находится в кеше, процесс (A) обнаружит его в хеш-очереди и проверит, не заблокирован ли индекс другим процессом (B). Если индекс заблокирован, процесс A приостанавливается и выставляет флаг у индекса в памяти, показывая, что он ждет освобождения индекса. Когда позднее процесс B разблокирует индекс, он «разбудит» все процессы (включая процесс A), ожидающие освобождения индекса. Когда же наконец процесс A сможет использовать индекс, он заблокирует его, чтобы другие процессы не могли к нему обратиться. Если первоначально счетчик ссылок имел значение, равное 0, индекс также появится в списке свободных индексов, поэтому ядро уберет его оттуда: индекс больше не является свободным. Ядро увеличивает значение счетчика ссылок и возвращает заблокированный индекс.
Если суммировать все вышесказанное, можно отметить, что алгоритм iget имеет отношение к начальной стадии системных вызовов, когда процесс впервые обращается к файлу. Этот алгоритм возвращает заблокированную индексную структуру со значением счетчика ссылок, на 1 большим, чем оно было раньше. Индекс в памяти содержит текущую информацию о состоянии файла. Ядро снимает блокировку с индекса перед выходом из системной операции, поэтому другие системные вызовы могут обратиться к индексу, если пожелают. В главе 5 рассматриваются эти случаи более подробно.
алгоритм iput /* разрешение доступа к индексу в памяти */
входная информация: указатель на индекс в памяти
выходная информация: отсутствует
заблокировать индекс если он еще не заблокирован;
уменьшить на 1 счетчик ссылок для индекса;
if (значение счетчика ссылок == 0)
if (значение счетчика связей == 0)
освободить дисковые блоки для файла (алгоритм free, раздел 4.7);
установить тип файла равным 0;
освободить индекс (алгоритм ifree, раздел 4.6);
if (к файлу обращались или изменился индекс или изменилось содержимое файла)
скорректировать дисковый индекс;
поместить индекс в список свободных индексов;
снять блокировку с индекса;
Рисунок 4.4. Освобождение индекса
