- •Морис Дж. Бах Архитектура операционной системы 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 Упражнения
- •Приложение системные операции
- •Библиография
11.4 Гнезда
В предыдущем разделе было показано, каким образом взаимодействуют между собой процессы, протекающие на разных машинах, при этом обращалось внимание на то, что способы реализации взаимодействия могут быть различаться в зависимости от используемых протоколов и сетевых средств. Более того, эти способы не всегда применимы для обслуживания взаимодействия процессов, выполняющихся на одной и той же машине, поскольку в них предполагается существование обслуживающего (серверного) процесса, который при выполнении системных функций open или read будет приостанавливаться драйвером. В целях создания более универсальных методов взаимодействия процессов на основе использования многоуровневых сетевых протоколов для системы BSD был разработан механизм, получивший название "sockets" (гнезда) (см. [Berkeley 83]). В данном разделе мы рассмотрим некоторые аспекты применения гнезд (на пользовательском уровне представления).
Рисунок 11.18. Модель с использованием гнезд
Структура ядра имеет три уровня: гнезд, протоколов и устройств (Рисунок 11.18). Уровень гнезд выполняет функции интерфейса между обращениями к операционной системе (системным функциям) и средствами низких уровней, уровень протоколов содержит модули, обеспечивающие взаимодействие процессов (на рисунке упомянуты протоколы TCP и IP), а уровень устройств содержит драйверы, управляющие сетевыми устройствами. Допустимые сочетания протоколов и драйверов указываются при построении системы (в секции конфигурации); этот способ уступает по гибкости вышеупомянутому потоковому механизму. Процессы взаимодействуют между собой по схеме клиент-сервер: сервер ждет сигнала от гнезда, находясь на одном конце дуплексной линии связи, а процессы-клиенты взаимодействуют с сервером через гнездо, находящееся на другом конце, который может располагаться на другой машине. Ядро обеспечивает внутреннюю связь и передает данные от клиента к серверу.
Гнезда, обладающие одинаковыми свойствами, например, опирающиеся на общие соглашения по идентификации и форматы адресов (в протоколах), группируются в домены (управляемые одним узлом). В системе BSD 4.2 поддерживаются домены: "UNIX system" — для взаимодействия процессов внутри одной машины и "Internet" (межсетевой) — для взаимодействия через сеть с помощью протокола DARPA (Управление перспективных исследований и разработок Министерства обороны США) (см. [Postel 80] и [Postel 81]). Гнезда бывают двух типов: виртуальный канал (потоковое гнездо, если пользоваться терминологией Беркли) и дейтаграмма. Виртуальный канал обеспечивает надежную доставку данных с сохранением исходной последовательности. Дейтаграммы не гарантируют надежную доставку с сохранением уникальности и последовательности, но они более экономны в смысле использования ресурсов, поскольку для них не требуются сложные установочные операции; таким образом, дейтаграммы полезны в отдельных случаях взаимодействия. Для каждой допустимой комбинации типа домен-гнездо в системе поддерживается умолчание на используемый протокол. Так, например, для домена "Internet" услуги виртуального канала выполняет протокол транспортной связи (TCP), а функции дейтаграммы — пользовательский дейтаграммный протокол (UDP).
Существует несколько системных функций работы с гнездами. Функция socket устанавливает оконечную точку линии связи.
sd = socket(format, type, protocol);
Format обозначает домен ("UNIX system" или "Internet"), type — тип связи через гнездо (виртуальный канал или дейтаграмма), а protocol — тип протокола, управляющего взаимодействием. Дескриптор гнезда sd, возвращаемый функцией socket, используется другими системными функциями. Закрытие гнезд выполняет функция close.
Функция bind связывает дескриптор гнезда с именем:
bind(sd, address, length);
где sd — дескриптор гнезда, address — адрес структуры, определяющей идентификатор, характерный для данной комбинации домена и протокола (в функции socket). Length — длина структуры address; без этого параметра ядро не знало бы, какова длина структуры, поскольку для разных доменов и протоколов она может быть различной. Например, для домена "UNIX system" структура содержит имя файла. Процессы-серверы связывают гнезда с именами и объявляют о состоявшемся присвоении имен процессам-клиентам.
С помощью системной функции connect делается запрос на подключение к существующему гнезду:
connect(sd, address, length);
Семантический смысл параметров функции остается прежним (см. функцию bind), но address указывает уже на выходное гнездо, образующее противоположный конец линии связи. Оба гнезда должны использовать одни и те же домен и протокол связи, и тогда ядро удостоверит правильность установки линии связи. Если тип гнезда — дейтаграмма, сообщаемый функцией connect ядру адрес будет использоваться в последующих обращениях к функции send через данное гнездо; в момент вызова никаких соединений не производится.
Пока процесс-сервер готовится к приему связи по виртуальному каналу, ядру следует выстроить поступающие запросы в очередь на обслуживание. Максимальная длина очереди задается с помощью системной функции listen:
listen(sd, qlength)
где sd — дескриптор гнезда, а qlength — максимально-допустимое число запросов, ожидающих обработки.
Рисунок 11.19. Прием вызова сервером
Системная функция accept принимает запросы на подключение, поступающие на вход процесса-сервера:
nsd = accept(sd, address, addrlen);
где sd — дескриптор гнезда, address — указатель на пользовательский массив, в котором ядро возвращает адрес подключаемого клиента, addrlen — размер пользовательского массива. По завершении выполнения функции ядро записывает в переменную addrlen размер пространства, фактически занятого массивом. Функция возвращает новый дескриптор гнезда (nsd), отличный от дескриптора sd. Процесс-сервер может продолжать слежение за состоянием объявленного гнезда, поддерживая связь с клиентом по отдельному каналу (Рисунок 11.19).
Функции send и recv выполняют передачу данных через подключенное гнездо. Синтаксис вызова функции send:
count = send(sd, msg, length, flags);
где sd — дескриптор гнезда, msg — указатель на посылаемые данные, length размер данных, count — количество фактически переданных байт. Параметр flags может содержать значение SOF_OOB (послать данные out-of-band — "через таможню"), если посылаемые данные не учитываются в общем информационном обмене между взаимодействующими процессами. Программа удаленной регистрации, например, может послать out-of-band сообщение, имитирующее нажатие на клавиатуре терминала клавиши "delete". Синтаксис вызова системной функции recv:
count = recv(sd, buf, length, flags);
где buf — массив для приема данных, length — ожидаемый объем данных, count количество байт, фактически переданных пользовательской программе. Флаги (flags) могут быть установлены таким образом, что поступившее сообщение после чтения и анализа его содержимого не будет удалено из очереди, или настроены на получение данных out-of-band. В дейтаграммных версиях указанных функций, sendto и recvfrom, в качестве дополнительных параметров указываются адреса. После выполнения подключения к гнездам потокового типа процессы могут вместо функций send и recv использовать функции read и write. Таким образом, согласовав тип протокола, серверы могли бы порождать процессы, работающие только с функциями read и write, словно имеют дело с обычными файлами.
Функция shutdown закрывает гнездовую связь:
shutdown(sd, mode)
где mode указывает, какой из сторон (посылающей, принимающей или обеим вместе) отныне запрещено участие в процессе передачи данных. Функция сообщает используемому протоколу о завершении сеанса сетевого взаимодействия, оставляя, тем не менее, дескрипторы гнезд в неприкосновенности. Освобождается дескриптор гнезда только в результате выполнения функции close.
Системная функция getsockname получает имя гнездовой связи, установленной ранее с помощью функции bind:
getsockname(sd, name, length);
Функции getsockopt и setsockopt получают и устанавливают значения различных связанных с гнездом параметров в соответствии с типом домена и протокола.
Рассмотрим обслуживающую программу, представленную на Рисунке 11.20. Процесс создает в домене "UNIX system" гнездо потокового типа и присваивает ему имя sockname. Затем с помощью функции listen устанавливается длина очереди поступающих сообщений и начинается цикл ожидания поступления запросов. Функция accept приостанавливает свое выполнение до тех пор, пока протоколом не будет зарегистрирован запрос на подключение к гнезду с означенным именем; после этого функция завершается, возвращая поступившему запросу новый дескриптор гнезда. Процесс-сервер порождает потомка, через которого будет поддерживаться связь с процессом-клиентом; родитель и потомок при этом закрывают свои дескрипторы, чтобы они не становились помехой для коммуникационного траффика другого процесса. Процесс-потомок ведет разговор с клиентом и завершается после выхода из функции read. Процесс-сервер возвращается к началу цикла и ждет поступления следующего запроса на подключение.
#include ‹sys/types.h›
#include ‹sys/socket.h›
main()
int sd, ns;
char buf[256];
struct sockaddr sockaddr;
int fromlen;
sd = socket(AF_UNIX, SOCK_STREAM, 0);
/* имя гнезда — не может включать пустой символ */
bind(sd, "sockname", sizeof("sockname") - 1);
listen(sd, 1);
for (;;)
ns = accept(sd, &sockaddr, &fromlen);
if (fork() == 0) /* потомок */
close(sd);
read(ns, buf, sizeof(buf));
printf("сервер читает %s'",buf);
exit();
close(ns);
Рисунок 11.20. Процесс-сервер в домене "UNIX system"
#include ‹sys/types.h›
#include ‹sys/socket.h›
main()
int sd, ns;
char buf[256];
struct sockaddr sockaddr;
int fromlen;
sd = socket(AF_UNIX, SOCK_STREAM, 0);
/* имя в запросе на подключение не может включать пустой символ */
if (connect(sd, "sockname", sizeof("sockname") - 1) == -1) exit();
write(sd, "hi guy", 6);
Рисунок 11.21. Процесс-клиент в домене "UNIX system"
На Рисунке 11.21 показан пример процесса-клиента, ведущего общение с сервером. Клиент создает гнездо в том же домене, что и сервер, и посылает запрос на подключение к гнезду с именем sockname. В результате подключения процесс-клиент получает виртуальный канал связи с сервером. В рассматриваемом примере клиент передает одно сообщение и завершается.
Если сервер обслуживает процессы в сети, указание о том, что гнездо принадлежит домену "Internet", можно сделать следующим образом:
socket(AF_INET, SOCK_STREAM, 0);
и связаться с сетевым адресом, полученным от сервера. В системе BSD имеются библиотечные функции, выполняющие эти действия. Второй параметр вызываемой клиентом функции connect содержит адресную информацию, необходимую для идентификации машины в сети (или адреса маршрутов посылки сообщений через промежуточные машины), а также дополнительную информацию, идентифицирующую приемное гнездо машины-адресата. Если серверу нужно одновременно следить за состоянием сети и выполнением локальных процессов, он использует два гнезда и с помощью функции select определяет, с каким клиентом устанавливается связь в данный момент.
