close

Вход

Забыли?

вход по аккаунту

?

Kaluzhnay

код для вставкиСкачать
Министерство образования и науки российской федерации
Федеральное государственное автономное
образовательное учреждение высшего образования
САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ
АЭРОКОСМИЧЕСКОГО ПРИБОРОСТРОЕНИЯ
В. П. Калюжный, Л. А. Осипов
Инструментальные средства
информационных систем
Учебное пособие
Допущено учебно-методическим объединением вузов
Российской Федерации по университетскому политехническому
образованию в качестве учебного пособия для студентов высших
учебных заведений, обучающихся по направлению подготовки
бакалавра 09.04.02 «Информационные системы и технология»
Санкт-Петербург
2015
УДК 004.7(075.8)
ББК 32.973.202я73
К17
Рецензенты:
доктор технических наук, профессор С. А. Яковлев;
доктор технических наук, профессор В. С. Павлов
Утверждено
редакционно-издательским советом университета
в качестве учебного пособия
Калюжный, В. П.
К17 Инструментальные средства информационных систем: учебное пособие / В. П. Калюжный, Л. А. Осипов. – СПб.: ГУАП,
2015. – 138 с.: ил.
ISBN 978-5-8088-1030-3
изложены основы организации универсальных операционных систем.
Рассмотрены методы построения объединенных компьютерных сетей, в том
числе их логической и физической интеграции. Особое внимание в пособии
уделено примерам, заданиям и вопросам, что делает возможным его использование в качестве методической основы для лабораторного практикума по дисциплине.
Пособие предназначено для студентов направления 230200 (Информационные системы) по специальностям: 230201 (Информационные системы и технологии в бизнесе); 230203 (Информационные системы в дизайне); 230204
(Информационные системы в медиаиндустрии).
УДК 004.7(075.8)
ББК 32.973.202я73
ISBN 978-5-8088-1030-3
© Санкт-Петербургский государственный
университет аэрокосмического
приборостроения (ГУАП), 2015
© В. П. Калюжный, Л. А. Осипов, 2015
СПИСОК СОКРАЩЕНИЙ
ОЗУ – оперативное запоминающее устройство
ОС – операционная система
ПК – персональный компьютер
ЦП – центральный процессор
API – application programming interface
CPU – central processing unit
DMA – direct memory access
DNS – domain name system
FTP – file transfer protocol
GNU – GNU’s not UNIX
HDD – hard (magnetic) disk drive
NFS – network file system
PID – process identifier
POSIX – portable operating system interface for unix
RAM – random access memory
rlogin – remote LOGIN
SNMP – simple network management protocol
TSL – test and set lock
TCP/IP (Transmission Control Protocol / Internet Protocol) – набор
используемых в Интернете сетевых протоколов, поддерживающий
связь между объединенными сетями и разными операционными системами
ARP (Address Resolution Protocol) – протокол разрешения адресов) – сетевой протокол, предназначенный для преобразования IPадресов (адресов сетевого уровня) в MAC-адреса (адреса канального
уровня) в сетях TCP/IP; определён в RFC 826
RARP (Reverse Address Resolution Protocol) – обратный протокол преобразования адресов – выполняет обратное отображение
адресов, т. е. преобразует аппаратный адрес в IP-адрес
IP (Internet Protocol) – межсетевой протокол, основа стека протоколов TCP/IP
3
MAC-адрес (Media Access Control) – управление доступом к носителю – это уникальный идентификатор, сопоставляемый с различными типами оборудования для компьютерных сетей
ICMP (Internet Control Message Protocol) – межсетевой протокол
управляющих сообщений
UDP (User Datagram Protocol) – протокол пользовательских дейтаграмм
TCP (Transmission Control Protocol) – протокол управления передачей
ASCII – американский стандартный код
LAN – локальная сеть
FTP – протокол передачи файлов
Telnet – протокол эмуляции терминала
Internet – глобальная сеть
internet – технология Интернет
Ethernet – сетевая технолоия (ENET)
InterNIC (Internet Network Information Center) – информационный центр Internet
TTL (Time to Live) – время жизни пакета
MTU (Maximum Transfare Unit) – максимальная единица передачи
4
Предисловие
Современные информационные системы строятся на основе двух
основных ресурсов. Это – информационные сети и операционные
системы. Эти ресурсы тесно связаны на программном уровне. В самом деле – логическая основа современных сетей – стек протоколов
TCP/IP, реализован в ядре любой универсальной операционной системы. В первом разделе пособия рассматриваются общие вопросы
организации универсальных операционных систем. Во втором разделе – UNIX – подобные операционные системы, поскольку именно они открывают возможность практического изучения основных
принципов организации универсальных операционных систем.
В третьем разделе дано описание основных инструментов, позволяющих практически изучать UNIX – подобные операционные системы, а также приводятся задания и примеры, дающие возможность
выполнить это на практике. В четвертом разделе кратко рассмотрен
сам стек TCP/IP. Первый, второй и четвертый разделы снабжены перечнем вопросов, которые могут быть полезны для усвоения материала пособия. Пятый раздел посвящен вопросам организации взаимодействия между сетевыми прикладными программами и также
содержит примеры.
5
1. Универсальные операционные системы
1.1. Функции ядра
В состав ядра операционной системы (ОС) входят функции, выполняющие внутрисистемные задачи организации вычислительных процессов: переключение контекстов, загрузка-выгрузка страниц, обработка прерываний. Эти функции недопустимы для приложений. Другой класс функций ядра служит для поддержки
приложений, создавая для них прикладную программную среду.
Приложения могут обращаться к ядру с запросами, т. е. системными вызовами для выполнения тех или иных задач, например чтение – открытие файлов, вывод графической информации на дисплей и тому подобное. Функции ядра, которые могут вызываться
приложениями, образуют интерфейс прикладного программирования. Функции, выполняемые модулями ядра, являются наиболее
часто используемыми функциями ОС, поэтому скорость их выполнения определяет производительность всей системы в целом. Для
обеспечения высокой скорости работы ОС все модули ядра (или их
большая часть) постоянно находятся в оперативной памяти, т. е. являются резидентными. Ядро является движущей силой всего вычислительного процесса, поэтому особенно важно обеспечить его
надежность (защищенность). Часто ядро оформлено в виде модуля
специального формата, отличающегося от формата пользовательских приложений. Современные ОС состоят не только из программ,
а используют также аппаратные средства самого процессора.
1.2. Вспомогательные модули операционной системы
Утилиты – это программы, решающие отдельные задачи управления и сопровождения компьютерной системы, такие как: программы сжатия дисков, архивации данных и тому подобное.
Системные обрабатывающие программы: текстовые и графические редакторы, компиляторы, компоновщики.
Программы, предоставляющие пользователю дополнительные
услуги: специальные программы пользовательского интерфейса,
игры.
Библиотеки процедур различного назначения, упрощающие
разработку приложений.
Как и обычные приложения, для выполнения своих задач утилиты и обрабатывающие программы, а также библиотеки ОС, об6
ращаются к функциям ядра посредством системных вызовов. Разделение ОС на ядро и вспомогательные модули обеспечивает легкую
расширяемость ОС. Например, чтобы добавить новую высокоуровневую функцию, достаточно разработать новый модуль, при этом
не требуется модифицировать ответственные функции, образующие ядро системы. Внесение изменений в функции ядра может оказаться гораздо сложнее. В некоторых случаях такое изменение может потребовать его полной перекомпиляции. Модули ОС, которые
оформлены в виде утилит, системных обработывающих программ и
библиотек, обычно загружаются в оперативную память только для
выполнения своих функций, иначе говоря, являются транзитными.
Постоянно в оперативной памяти находятся только самые необходимые коды ОС, относящиеся к ее ядру. Такая организация экономит оперативную память компьютера. Важным свойством архитектуры ОС, построенной на ядре, является возможность защиты кодов и данных ОС за счет использования ядра в привилегированном
режиме.
1.3. Ядро в привилегированном режиме
Для надежного выполнения кода вычислительного процесса ОС
должна иметь по отношению к приложениям привилегии, иначе некорректно работающее приложение может разрушить часть ее кода.
Поэтому ядро ОС должно обладать особыми полномочиями по отношению к приложениям, это нужно также для того, чтобы играть
роль «арбитра» в споре приложений за ресурсы системы. Ни одно из
приложений не должно иметь возможности без ведома ядра получить дополнительную область памяти, занимать процессор дольше
разрешенного времени, управлять совместно используемыми внешними устройствами. Такие привилегии невозможно обеспечить без
специальных средств аппаратной поддержки. Аппаратура должна поддерживать как минимум два режима работы: пользовательский режим и привилегированный режим (режим ядра). Приложение ставится в подчиненное положение за счет запрета выполнения
в привилегированном режиме некоторых критических команд, связанных с переключением процессора с задачи на задачу, управлением устройствами ввода-вывода, доступа к механизмам распределения и защиты памяти. Выполнение некоторых команд в пользовательском режиме запрещается безусловно (например, команда
перехода из пользовательского режима в привилегированный и обратно). Тогда как другие команды могут быть запрещены для вы7
полнения в определенных условиях. Например, команды ввода-вывода могут быть запрещены при доступе к контроллеру жесткого
диска, который хранит данные, общие для ОС и приложений. Условие выполнения критической команды находится под полным контролем ОС. Контроль обеспечивается за счет запрета выполнения
определенных команд, в пользовательском режиме. Аналогичным
способом обеспечиваются привилегии ОС при доступе к памяти. Например, выполнение команды доступа к памяти для приложения
разрешается, если команда обращается к области, отведенной для
данного приложения, и запрещается, если такое обращение происходит в области, где расположены коды самой ОС. Полный контроль
ОС над доступом к памяти достигается за счет того, что команды
конфигурирования механизма защиты памяти (например, команды, управляющие указателем таблицы дескриптеров памяти) разрешается выполнять только в привилегированном режиме. Важно,
что ОС ограничивает обращение не только к своим областям, но и
к областям, используемым другими приложениями. В этом случае
говорят, что каждая программа выполняется в своем адресном пространстве. Это свойство позволяет локализовать некорректно работающее приложение и обеспечить таким образом стабильность работы всей ОС.
Структура ядра ОС
Обычно ядро состоит из следующих слоев (рис. 1).
Средства аппаратной поддержки ОС. К операционной системе
относят не все аппаратные средства компьютера, а только средства
ее аппаратной поддержки, которые прямо участвуют в организации
вычислительного процесса:
– средства поддержки привилегированного режима;
– система прерываний;
– средства переключения контекстов процессов;
– средства защиты областей памяти.
Машинно-зависимые компоненты ОС. Это – микрокод, способный взаимодействовать с аппаратурой процессора и со слоем базовых механизмов ядра. Этот слой экранирует ядро от особенностей
аппаратуры. Слоистая структура ядра делает всю ОС расширяемой
(открытой), следовательно, изменение какого-либо из слоев ядра не
затрагивает другие его части.
Слой базовых механизмов ядра – выполняет основные функции
ядра, к которым относят:
– переключение контекстов процессора;
8
Интерфейс
системных вызовов
Менеджеры ресурсов
Базовые механизмы
Машинно-зависимые
компоненты
Аппаратная
поддержка
Аппаратура
Рис. 1
– загрузку-выгрузку страниц памяти;
– низкоуровневые операции ввода-вывода.
А также ведет основные информационные структуры ядра.
В этом слое не принимаются никакие решения относительно действий ядра в какой-либо ситуации, а выполняются лишь те директивы, которые приняты на более высоких уровнях.
Менеджеры ресурсов (диспетчеры) – состоят из модулей, решающих задачи по управлению основными ресурсами ОС.
Основные ресурсы:
– процессор, а точнее время, которое выделяется на определенные задачи;
– память;
– файловая система;
– устройства ввода-вывода.
Обычно в данном слое работают диспетчеры процессов, основной
задачей которых является учет имеющихся ресурсов, освобождение и их распределение между потребителями (процессами). Каждый из менеджеров ведет учет ресурсов строго определенного типа
и планирует их распределение в соответствии с запросами приложений. Например, менеджер виртуальной памяти управляет перемещениями страниц из оперативной памяти на диск и обратно. Этот
менеджер должен отслеживать интенсивность обращения к страницам, время пребывания в памяти, состояние процессов, использу9
ющих данные, и многие другие параметры, на основании которых
он время от времени принимает решения: какие страницы выгрузить, а какие загрузить. Все менеджеры используют табличные
структуры. Для исполнения принятых решений менеджер обращается к нижестоящему слою базовых механизмов с запросами о загрузке-выгрузке конкретной страницы. Внутри слоя существуют
тесные взаимные связи, отображающие тот факт, что для выполнения процессу нужен доступ единовременно к нескольким ресурсам,
а именно: процессору, области памяти, к определенному файлу или
устройству ввода-вывода. Например, при создании процесса менеджер процессов обращается к менеджеру памяти, который должен
выделить процессу определенную область памяти для его кодов
и данных.
Интерфейс системных вызовов – является самым верхним слоем ядра, он взаимодействует непосредственно с приложениями,
системными утилитами и образует прикладной программный интерфейс (API). Функции API (обслуживание системных вызовов)
предоставляют доступ к ресурсам системы в удобной и компактной форме. Для осуществления таких действий системные вызовы
обычно обращаются за помощью к функциям менеджеров ресурсов,
причем для выполнения одного системного вызова может потребоваться несколько таких обращений.
1.4. Процесс и модель процесса
Модель процесса базируется на двух независимых концепциях:
группирование ресурсов и выполнение программы. Процесс – это
абстрактное понятие, описывающее работу программы (это не просто программа). В этой модели все функционирующее на компьютере программное обеспечение (иногда включая ОС) организовано
в виде набора последовательных процессов. Процесс – выполняемая программа, включающая текущее значение счетчика команд,
регистров и переменных (состояние). С позиции данной абстрактной модели у каждого процесса есть свой собственный центральный процессор. Процесс – это активность определенного рода.
У процесса есть программа, входные, выходные данные, а также состояние (стек, аккумулятор, состояние регистров). Один процессор
может переключаться между различными процессами, используя
алгоритм планирования для определения моментов переключения
от одного процесса к другому.
10
1.5. Создание процессов
В универсальных ОС создание и прерывание процессов происходит по мере необходимости. Есть четыре основных события, приводящие к созданию процессов:
– инициализация системы;
– выполнение изданного работающим процессом системного вызова на создание нового процесса;
– запрос пользователя на создание процесса;
– инициирование пакетного задания.
Обычно при загрузке ОС создаются множество процессов. Некоторые из них являются высокоприоритетными, т. е. обеспечивающими взаимодействие с пользователем и выполняющие заданную
работу. Остальные процессы являются фоновыми, они не связаны
с конкретными пользователями, но выполняют особые функции.
Например, фоновый процесс может быть предназначен для обработки приходящей почты, активизируясь только по мере появления
писем. Такие фоновые процессы, связанные с почтой, новостями,
web-страницами, выводом на печать, называются демонами. Процессы могут создаваться не только в процессе загрузки системы, но
и позже. Например, новый процесс может быть создан по просьбе
выполняющегося в данный момет процесса. Во всех перечисленных
случаях, новый процесс формируется одинаково, а именно: текущий процесс выполняет системный запрос на создание нового процесса. В роли текущего процесса может выступать процесс, запущенный пользователем, системный процесс, инициируемый клавиатурой, мышью и тому подобное. В любом случае этот процесс всего
лишь выполняет системный запрос, а также прямо или косвенно содержит информацию о программе, которую нужно запустить в этом
процессе. В Unix-подобных ОС существует только один системный
запрос, направленный на создание нового процесса, это – fork. Этот
запрос создает дубликат выполняемого процесса. После выполнения fork двум процессам (родительскому и дочернему) соответствуют одинаковые образы памяти, строки окружения и одни и те же
открытые файлы. Далее обычно дочерний процесс выполняет системный вызов для изменения своего образа памяти и запуска новой программы.
11
1.6. Завершение процесса
Рано или поздно процесс завершает свое существование. Чаще
всего это происходит благодаря одному из следующих событий:
– обычный выход;
– выход по ошибке;
– выход по неисправимой ошибке;
– уничтожение другим процессом.
В первом случае роцесс выполняет системный запрос, чтобы сообщить ОС о том, что он выполнил свою работу.
Второй причиной может стать неустранимая ошибка. Например,
пользователь вводит команду компиляции файла, которого не существует. В этом случае компилятор просто завершает свою работу.
Интерактивные процессы, рассчитанные на работу с экраном (графический режим), обычно не завершают работу при получении неправильных параметров, а просят пользователя ввести правильные
параметры, происходит диалог.
В третьем случае ошибка вызвана самим процессом. Чаще всего
она связана с ошибкой в программе. Например, выполнение недопустимой команды, обращение к несуществующей области памяти
и тому подобное. В некоторых ОС, например Linux, процесс может
информировать ОС о том, что он сам обрабатывает некоторые ошибки. В этом случае процессу посылается сигнал и он сам должен обработать ошибку.
В четвертом случае выполняется системный запрос на уничтожение определенного процесса (kill – Unix, Terminate Process –
Windows). В обоих случаях процесс, который уничтожает другой
процесс, должен обладать соответствующими полномочиями. В некоторых ОС при завершении процесса, преднамеренно или нет, –
все процессы, созданные этим процессом, завершаются.
1.7. Иерархия процессов
В некоторых ОС, в частности Linux, родительские и дочерние
процессы остаются связанными. Дочерний процесс также может
в свою очередь создавать процессы, формируя иерархию процессов.
В Unix все эти дальнейшие потомки образуют группу процессов.
Сигнал, посылаемый пользователем с клавиатуры, доставляется
всем членам группы, взаимодействующим с клавиатурой в данный
момент. Обычно это все активные процессы, созданные в текущем
12
окне. Каждый из процессов может перехватить сигнал, игнорировать его или выполнить другое действие, предусмотренное по умолчанию. В образе загрузки Unix-подобных ОС присутствует специальный процесс – init. При запуске он считывает файл, в котором
имеется информация о количестве терминалов. Терминал (консоль) – это устройство ввода и вывода. Затем процесс развивается
таким образом, чтобы каждому терминалу соответствовал один
процесс. Если пароль правильный – процесс входа в систему запускает оболочку для обработки команд пользователя, которые в свою
очередь могут запускать процессы. Таким образом, все процессы
в системе принадлежат одному дереву, начинающемуся с процесса
init. В Windows не существует понятия иерархии процессов, все они
равноправны. Единственным инструментом, напоминающим подобие иерархии, в Windows является возможность создания процесса со специальным маркером, который называется дескриптором.
Дескриптор (маркер) позволяет контролировать дочерний процесс.
Но маркер можно передать другому процессу, нарушая иерархию.
В Unix это невозможно.
1.8. Состояния процессов
Несмотря на то, что процесс является самостоятельной сущностью со своим счетчиком команд и внутренним состоянием, существует необходимость взаимодействия с другими процессами. Например, выходные данные одного процесса могут быть входными
данными для другого. В зависимости от относительных скоростей
процессов, может получиться так, что процесс уже готов к запуску,
но входных данных для него еще нет. В этом случае процесс блокируется до поступления входных данных. Также возможна ситуация, когда процесс, готовый и способный работать, останавливается, поскольку ОС решила предоставить процессор другому процессу. Эти две ситуации являются принципиально разными: в первом
случае приостановка выполнения является внутренней проблемой
(например, невозможно обработать командную строку, пока она не
введена); во втором случае проблема является технической (нехватка процессорного времени). Диаграмма состояний процессов представлена на рис. 2.
Действие – это работающий процесс, в этот конкретный момент использующий процессор. Готовый к работе процесс временно приостановлен, чтобы позволить выполниться другому процессу.
13
Действие
1
2
3
Блокировка
Готовность
4
Рис. 2
Блокировка или заблокирование процесса – процесс не может быть
запущен прежде, чем произойдет некоторое внешнее событие.
1-й переход – процесс блокируется, ожидая входных данных.
2-й переход – планировщик выбирает другой процесс.
3-й переход – планировщик выбирает этот процесс.
4-й переход – доступны входные данные.
Первые два состояния похожи. В обоих случаях процесс может
быть запущен. Только во втором случае процессор недоступен. Третье состояние отличается тем, что запустить процесс невозможно,
независимо от того загружен процессор или нет.
Переход 1 происходит тогда, когда процессор обнаруживает, что
продолжение работы невозможно. В некоторых ОС процесс должен
выполнить системный запрос, например, block или pause, чтобы
оказаться в заблокированном состоянии. В других системах (Unix)
процесс автоматически блокируется, если он не в состоянии считать
из специального файла или канала данные. Переходы 2 и 3 вызываются частью ОС, называемой планировщиком процессов. Переход 2
происходит, если планировщик решил предоставить процессор другому процессу. Переход 3 происходит, когда все остальные процессы
уже исчерпали свое процессорное время и процессор снова передается первому процессу. Переход 4 происходит с появлением внешнего
события, ожидавшегося процессом, например, прибытие входных
данных. Если в этот момент не запущен никакой другой процесс, то
срабатывает переход 3 и процесс запускается. В противном случае
процессу придется некоторое время находиться в состоянии готовности, пока не освободится процессор.
14
1.9. Переключение процессов
Для реализации процессов ОС ведет информационную структуру, которая называется таблицей процессов. В ней столько строк –
сколько процессов и столько столбцов – сколько имеется параметров, описывающих данный процесс. Эти параметры можно разделить на три группы.
1-я группа параметров относится к управлению процессами.
2-я группа описывает используемую процессом память.
3-я группа – параметры, которые описывают используемые файлы (ресурсы файловой системы).
Во время работы в ОЗУ содержатся коды ядра и коды процесса,
который выполняется в данный момент. Если необходимо перейти
к другому процессу – приходится загружать эти коды в оперативную память, но это можно сделать, только используя диск (обращение к диску). Но диск для ОС является внешним устройством, а это
значит, что без прерывания здесь не обойтись. Однако прерывания
могут потребоваться не обязательно тогда, когда необходимо переходить к другому процессу. Например, выполняющемуся процессу может понадобиться внешнее устройство, и он может попросить
ядро предоставить его ему и поэтому он посылает запрос. Другой
случай: работающий процесс может быть на время приостановлен,
если при определенных условиях какое-либо внешнее устройство
попросит ОС сделать это. Если ОС требуется запустить новый процесс, то обращение к диску как к внешнему устройству неизбежно,
поэтому начинается прерывание.
На начальной стадии этого процесса работают аппаратные средства процессора.
Аппаратно запоминается счетчик команд.
Запоминается слово состояния программы, а также содержимое
одного или нескольких базовых регистров.
Завершают свою работу аппаратные средства тем, что считывают вектор прерывания диска (это начальный адрес процедуры прерываний) и запускают эту программу.
С этого момента и почти до конца всего прерывания используются программные средства. Сначала вызывается процедура на ассемблере, которая устанавливает временный стек и считывает основные параметры, которые являются столбцами в таблице процессов,
а также заполняет соответствующую строку для данного процесса, который был остановлен. Такие действия, как установка стека,
модификация регистров и их считывание – невозможно выразить
15
на языке высокого уровня, поэтому все это делается на ассемблере.
После того, как строка в таблице модифицирована – программа на
ассемблере передает управление программе прерываний, написанной на языке высокого уровня. Далее процедура на языке высокого уровня главным образом буферизует данные и подготавливает их
для обмена с внешним устройством. После завершения этой части
программа на языке высокого уровня вызывает программу на языке ассемблера, но прежде, планировщик, используя данные из таблицы процессов, может выбрать и подготовить для запуска другой
процесс.
Содержимое таблицы процессов (ее столбцы) можно сгруппировать в трех частях.
1-я часть столбцов – управление процессом.
– Регистры (содержимое).
– Счетчик команд.
– Слово состояния программы.
– Указатель стека.
– Состояние процесса.
– Приоритет.
– Параметр планирования.
– Идентификатор процесса (его PID).
– Родительский процесс (PPID).
– Принадлежность к группе процессов (если она есть).
– Сигналы.
– Время начала процесса.
– Используемое процессорное время.
– Процессорное время дочернего процесса.
– Время следующего аварийного сигнала.
2-я часть столбцов – управление памятью.
– Указатель на текстовый сегмент.
– Указатель на сегмент данных.
– Указатель на сегмент стека.
3-я часть столбцов – управление файлами.
– Корневой каталог.
– Рабочий каталог.
– Дескрипторы файла (описатели).
– Идентификатор файла (номер).
– Идентификатор группы.
После того, как планировщик (менеджер процессов) закончил
свою работу – управление передается программе на ассемблере, которая загружает регистры и карту памяти для вновь запускаемого
16
процесса. Если ОС хотела запустить новый процесс – загружается
карта памяти и регистры для нового процесса (если ОС имела в виду
новый процесс), или старого процесса (если ОС имела в виду старый
процесс).
Динамика переключения между процессами опирается на алгоритм прерываний и включает в себя следующие действия.
– Аппаратное обеспечение сохраняет в стеке счетчик команд
и т. п.
– Аппаратное обеспечение загружает новый счетчик команд из
таблицы векторов прерываний.
– Процедура на ассемблере сохраняет регистры.
– Процедура на ассемблере устанавливает новый стек.
– Запускается программа обработки прерываний на языке высокого уровня (она считывает и буферизует входные и выходные данные).
– Планировщик (программа на языке высокого уровня) выбирает следующий процесс (когда происходит переключение между процессами).
– Программа на языке высокого уровня передает управление
процедуре на ассемблере.
– Процедура на ассемблере запускает новый процесс.
1.10. Потоки и модель потока
В универсальных ОС каждому процессу соответствует адресное
пространство и одиночный управляющий поток. Фактически это
и определяет процесс. Тем не менее, часто встречаются ситуации,
в которых предпочтительно иметь несколько квазипараллельных
управляющих потоков в одном адресном пространстве, как если бы
они были различными процессами (но разделяющими одно адресное пространство). Модель процесса базируется на двух независимых концепциях: группирование ресурсов и выполнение программы. Иногда их необходимо разделять и здесь появляется понятие
потока. У потока общее адресное пространство. У потока есть счетчик команд, отслеживающий выполнение действий; есть регистры,
в которых хранятся текущие переменные; стек, содержащий протокол выполнения команд, где на каждую процедуру, вызванную,
но еще не вернувшуюся, отведена часть стека. Хотя поток должен
выполняться внутри процесса, следует разделять эти понятия. Процессы используются для группирования ресурсов, а потоки являются
объектами, поочередно выполняемыми на центральном процессоре.
17
Концепция потоков добавляет к модели процесса возможность одновременного выполнения в одном и том же процессе нескольких
независимых программ. Несколько потоков, работающих параллельно в одном процессе, аналогичны нескольким процессам, идущим квазипараллельно на одном компьютере. В первом случае потоки разделяют адресное пространство, открытые файлы и другие
ресурсы. Во втором случае процессы совместно пользуются физической памятью, дисками, принтерами и другими ресурсами.
1.11. Межпроцессорное взаимодействие. Состояние состязания
Межпроцессорное взаимодействие может приводить к состоянию состязания. Процессам часто бывает необходимо взаимодействовать между собой. Например, выходные данные одного процесса могут потребоваться другому. Поэтому необходимо правильно
организовать взаимодействие между ними, не прибегая к прерываниям.
Проблему можно разделить на три части.
1. Передача данных от одного процесса к другому.
2. Два процесса могут столкнуться между собой в критических
ситуациях (например, каждому из них в определенный момент может потребоваться последний мегабайт оперативной памяти).
3. Согласование действий между процессами: если например,
процесс А должен поставить данные, а процесс В выводить их на печать, то В должен ждать и не начинать печатать, пока не поступят
данные от процесса А.
Два из перечисленных пунктов относятся и к потокам. Первая
проблема (передача данных) в случае потоков проблемой не является, поскольку у потоков общее адресное пространство. Процессы,
работающие совместно, могут сообща использовать общее хранилище данных. Каждый из них может считывать оттуда данные и записывать туда информацию. Это так же может быть участок в основной памяти или файл общего доступа.
Рассмотрим пример сетевого принтера (рис. 3).
Если процессу требуется вывести на печать файл, он перемещает
имя файла в специальный каталог – директорий спулера. Другой
процесс – демон печати периодически проверяет наличие файлов,
которые необходимо напечатать, затем печатает файл и удаляет его
имя из каталога. Каталог спулера состоит из сегментов, в каждом
из которых может храниться имя файла. Также есть две совместно
используемые переменные (out – которая указывает на следующий
18
Процесс А
4
5
6
7
Директорий спулера
Progr a
Progr b
Progr c
Out = 4
…
In = 7
Процесс В
Рис. 3
файл для печати; in – обозначает следующий свободный сегмент).
Две переменные можно хранить в одном файле (состоящем из двух
слов), который доступен всем процессам. Пусть, например, в данный момент сегменты 0–3 пусты (эти файлы уже напечатаны), а сегменты 4–6 заняты (эти файлы ждут своей очереди на печать). Более или менее одновременно процессы А и В решают поставить свои
файлы для печати в очередь. Процесс А считывает значение переменной in и сохраняет ее в собственную локальную переменную free
slot. После этого происходит прерывание по таймеру и процессор переключается на процесс В. Этот процесс в свое время тоже считывает значение переменной in и сохраняет ее в собственную локальную переменную в следующий свободный слот. В данный момент
оба процесса считают, что следующий свободный сегмент – 7. Процесс В сохраняет в каталоге спулера имя файла и заменяет переменную in на 8. Затем начинает заниматься своими задачами. Наконец,
управление опять переходит к процессу А и он продолжает с того
момента, на котором остановился. Он обращается к переменной free
slot и считывает ее значение и записывает в 7-й сегмент имя файла
(удаляя имя файла, которое туда занес процесс В). Затем он изменяет значение переменной на 8. После всех этих действий структура каталога не нарушена, но файл процесса B не будет напечатан.
Cитуация, в которой два или более процесса считывают или записывают данные одновременно, и конечный результат зависит от того, какой из них был первым, называется состоянием состязания.
1.12. Критические области
Основным решением проблем, связанных с совместным использованием памяти, файлов и тому подобное, является запрет одновременной записи и чтения разделяемых (общих) данных более чем
19
одним процессом. Иными словами, необходимо взаимное исключение. Это означает, что в момент, когда один процесс использует разделяемые данные, другому процессу это делать запрещено. В рассмотренном примере эта ситуация возникла из-за того, что процесс
В начал работу с одной из совместно используемых переменных до
того, как процесс А ее закончил. Часть программы, в которой есть
обращение к совместно используемым данным, называется критической областью. Если удастся избежать одновременного нахождения процессов в критической области, можно избежать состязаний.
Несмотря на то, что это требование исключает состязания, оно не
достаточно для правильной совместной работы квазипараллельных
процессов и эффективного использования данных. Для этого необходимо выполнение четырех условий.
– Два процесса не должны одновременно находиться в критических областях.
– В программе не должно быть предложений о скорости или количестве процессов.
– Процесс, находящийся в критической области не может, или
не должен блокировать другой процесс.
– Недопустима ситуация, в которой процесс неопределенно долго ждет попадания в критическую область.
1.13. Запрещение прерываний и переменные блокировки
Рассмотрим возможность аппаратного решения этой проблемы.
Самое простое решение состоит в запрещении всех прерываний
при входе процесса в критическую область и разрешение прерываний при выходе из критической области. Если прерывания запрещены, то запрещены и прерывания по таймеру. Поскольку процессор переключается с одного процесса на другой только по прерыванию, отключение прерываний исключает передачу процессора
другому процессу. Таким образом, запретив прерывания, процесс
может сохранять совместно используемые данные, не опасаясь вмешательства другого процесса, но все же неправильно давать пользовательскому процессу возможность запрета прерываний. Если,
например, процесс отключил все прерывания, и в результате какого-либо сбоя не включил их обратно, ОС прекратит свое существование. Для ядра характерно запрещение прерываний для некоторых команд при работе с переменными и списками. Возникновение
прерывания в момент, когда список готовых процессов находится в неопределенном состоянии, могло бы привести ядро к краху.
20
Вывод: инструмент запрещения прерывания необходим для ОС, но
не для пользовательских процессов.
Рассмотрим возможность программного решения. Пусть имеется одна совместно используемая переменная блокировки, изначально равная нулю. Если процесс хочет попасть в критическую
область, он предварительно считывает значение переменной блокировки; если переменная равна нулю, то процесс изменяет ее на 1 и
входит в критическую область; если переменная равна единице, то
процесс ждет, пока ее значение не изменится на ноль. Таким образом, ноль означает, что ни одного процесса в критической области
нет, а единица наоборот, что есть процессы в критической области.
У этого метода те же проблемы, что и в примере с каталогом спулера, а именно: первый процесс считывает переменную блокировки,
обнаруживает, что она равна нулю, но прежде, чем он успевает изменить ее на единицу, управление может получить другой процесс,
изменяющий ее на единицу. Когда первый процесс снова получает
управление, он тоже заменяет переменную блокировки на единицу, и два процесса одновременно окажутся в критической области.
Проблема не решается повторной проверкой значения переменной.
Второй процесс может получить управление как раз после того, как
первый процесс закончил вторую проверку, но еще не заменил значение переменной блокировки.
1.14. Алгоритм Петерсона и команда TSL
Датский математик Деккер был первым, кто разработал программное решение проблемы взаимного исключения. В 1981 г. Петерсон разработал алгоритм, состоящий из двух процедур, написанных на языке С. Прежде, чем обратиться к совместно используемым переменным, процесс вызывает процедуру enter region со
своим номером 0 или 1 в качестве параметра, поэтому процессу при
необходимости придется подождать, прежде чем войти в критическую область. После выхода из критической области процесс вызывает процедуру leave region, чтобы обозначить свой выход и тем самым разрешить другому процессу вход в критическую область.
1.15. Примитивы межпроцессорного взаимодействия
Решения которые были предложены корректны, но они обладают одним и тем же недостатком: использование активного ожидания. Они реализуют следующий алгоритм: перед входом в критиче21
скую область процесс проверяет, можно ли это сделать. Если нельзя,
то процесс входит в цикл, ожидая возможности войти в критическую область. Этот алгоритм не только бесцельно тратит процессорное время, но, кроме того, может возникнуть ситуация, которую называют проблемой инверсии приоритета. Возможность вхождения
в критическую область должен предвидеть программист. Примитивы блокируют процессы в случае запрета на вход в критическую область. Одной из простейших является пара примитивов sleep и wake
up. Примитив sleep – это системный запрос, в результате которого
вызывающий процесс блокируется, пока его не разбудит другой
процесс. У системного запроса wake up один параметр – это процесс,
который следует запустить. Также возможно наличие одного параметра у обоих запросов: адрес ячейки памяти, используемой для согласования запросов ожидания и запуска.
1.16. Проблема производителя и потребителя
Эта проблема называется проблемой ограниченного буфера. Если один процесс (производитель) помещает данные в буфер, а другой процесс (потребитель) забирает их оттуда – может возникнуть
проблема. Трудности могут возникнуть в момент, когда производитель хочет поместить в буфер очередную порцию данных, но обнаруживает, что буфер полон. Для производителя решением является
ожидание, пока потребитель полностью или частично не освободит
буфер. Аналогично, если потребитель хочет забрать данные из буфера, а он пуст, потребитель уходит в состояние ожидания до тех
пор, пока производитель что-нибудь не положит туда и не разбудит потребителя. Это решение является простым, однако оно приводит к состоянию состязания, когда оба процесса могут оказаться
в состоянии ожидания. Решением может быть использование бита
ожидания активации. Если сигнал активации послан процессу, не
находящемуся в состоянии ожидания, этот бит устанавливается.
Позднее, когда процесс пытается уйти в состояние ожидания – бит
активации сбрасывается, но процесс остается активным. Этот бит
играет роль «копилки» сигналов активации.
1.17. Семафоры и решение проблемы производителя и потребителя
В 1965 г. Дейкстра предложил использовать целую переменную
для подсчета сигналов запуска, сохраненных на будущее. Им был
предложен новый тип переменных – семафоры, значение которых
22
может быть нулем (в случае отсутствия сохраненных сигналов активации) или некоторым положительным числом, соответствующим
количеству отложенных активирующих сигналов. Он предложил
две операции: down и up. Операция down сравнивает значение семафора с нулем. Если значение семафора >0, то операция down уменьшает его, т. е. расходует один из отложенных сигналов активации
и возвращает управление. Если значение семафора равно нулю – то
процедура не возвращает управление процессу, а он сам переводится в состояние ожидания. Все операции проверки значения семафора, его изменение или переводы процессов в состояние ожидания,
выполняются как единое, неделимое, элементарное действие. Тем
самым гарантируется, что после начала операции ни один процесс
не получит доступа к семафору до окончания, либо блокировки операции. Операция up – увеличивает значение семафора. Если с ним
связаны один или несколько ожидающих процессов, которые не могут завершить более раннюю операцию down, один из них выбирается системой, например, случайным образом, и ему разрешается
завершить свою операцию down. Таким образом, после операции
up, примененной к семафору, связанному с несколькими ожидающими процессами, значение семафора так и остается равным нулю.
Но зато число ожидающих процессов уменьшается на единицу. Операция увеличения значения семафора и активации процесса также
неделимы, т. е. ни один процесс не может быть блокирован во время
выполнения операции up. Такую возможность дает аппаратно реализованная на кристалле процессора команда TSL ( test and set lock).
Проблему потерянных сигналов запуска также можно решить
с помощью семафоров. Стандартным решением является реализация операций down и up в виде системных запросов с запретом ОС
всех прерываний на время проверки семафора, изменение его значения и возможного перевода процесса в состояние ожидания. Поскольку для выполнения всех этих действий требуется лишь несколько команд процессора, запрет прерываний на столь короткий
промежуток времени не приносит вреда. Если используется несколько процессов, каждый семафор необходимо защитить переменной блокировки с использованием команды ТSL, чтобы гарантировать обращение к семафору лишь одного процесса. Использование
команды ТSL принципиально отличается от активного ожидания,
при котором производитель и потребитель ждут опорожнения или
пополнения буфера. Операции с семафором могут занимать лишь
несколько долей микросекунды, тогда как активное ожидание может затянуться на время, большее на несколько порядков.
23
1.18. Мьютексы
Иногда используется упрощенная версия семафора, называемая
мьютексом. Мьютекс не способен считать, он может лишь управлять
взаимным исключением доступа к совместно используемым ресурсам или кодам. Реализация мьютекса проста и эффективна, что делает их использование особенно удобным в случае потоков, действующих только в пространстве пользователя. Мьютекс – это переменная,
которая может находиться в одном из двух состояний, блокированном и не блокированном. Поэтому для описания мьютекса нужен
всего один бит, хотя чаще используется целочисленная переменная,
у которой: 0 – не блокируемое состояние, а любое другое положительное целое число соответствует блокируемому состоянию. Значение
мьютекса устанавливается двумя процедурами. Если поток собирается войти в критическую область – он вызывает процедуру мьютекс
– lock. Если мьютекс не заблокирован (вход в критическую область
разрешен), запрос выполняется, и вызывающий поток может попасть
в критическую область. Напротив, если мьютекс заблокирован, вызывающий поток блокируется до тех пор, пока другой поток, находящийся в критической области, не выйдет из нее, вызвав процедуру
мьютекс – unlock. Если мьютекс блокирует несколько потоков, то из
них случайным образом выбирается один. Мьютексы легко реализуются в пространстве пользователя, если доступна команда TSL.
1.19. Функции ОС по управлению памятью
Под памятью будем понимать оперативную память компьютера (RAM или ОЗУ), в отличие от жесткого диска (storage). Оперативной памяти для сохранения информации требуется постоянное
электропитание.
– Функции ОС по управлению памятью в мультипрограммной
системе сводятся к выполнению следущих действий.
– Отслеживание свободной и занятой памяти.
– Выделение памяти процессам и освобождение памяти по мере
их завершения.
– Вытеснение кодов и данных процессов из оперативной памяти
на диск (полное или частичное), когда размера основной памяти недостаточно для размещения в ней всех процессов, и возвращение их
в оперативную память, когда в ней освобождается место.
– Настройка адресов программы на конкретную область физической памяти.
24
Во время работы ОС ей часто приходится создавать служебные
информационные структуры, такие как: описатели процессов и потоков, различные таблицы распределения ресурсов; буферы, используемые процессами для обмена данными; синхронизирующие
объекты и тому подобное. Все эти системные объекты требуют памяти. В некоторых ОС заранее (во время установки) резервируется некоторый фиксированный объем памяти для системных нужд.
Существует и другой подход, при котором память для системных
целей выделяется динамически. В таком случае различные подсистемы ОС при создании своих таблиц, объектов и структур, обращаются к подсистеме памяти с запросами. Помимо первоначального выделения памяти процессам при их создании ОС должна
заниматься также динамическим распределением памяти, т. е. выполнять запросы приложений на выделение им дополнительной памяти во время их выполнения.
1.20. Типы адресов
Для идентификации переменных и команд на разных этапах
жизненного цикла программы используются: символьные имена (метки), виртуальные адреса и физические адреса. Символьные
имена присваивает программист при написании программы на алгоритмическом языке или ассемблере. Виртуальные адреса (математические или логические) выбирает транслятор, переводящий
программу на машинный язык. Поскольку во время трансляции
в общем случае неизвестно в какое место оперативной памяти будет
загружена программа, то транслятор присваивает переменным или
командам виртуальные (условные) адреса, обычно считая по умолчанию, что начальным адресом программы будет нулевой адрес. Физические адреса – соответствуют номерам ячеек памяти, где в действительности расположены или будут расположены переменные и
команды. Совокупность виртуальных адресов процесса называется
его виртуальным адресным пространством. Диапазон возможных
адресов виртуального пространства у всех процессов является одним и тем же. Например, при использовании 32-разрядной шины
адреса диапазон виртуальных адресов определяется границами:
0000000016 ÷ FFFFFFFF16 , 232 примерно равно 4 миллиардам.
Тем не менее, каждый процесс имеет собственное виртуальное
адресное пространство, т. е. транслятор присваивает виртуальные
адреса переменным и кодам каждой программы независимо. Совпадение адресов переменных и команд различных процессов не при25
водит к конфликтам, так как в том случае, когда эти переменные
одновременно присутствуют в памяти, ОС отображает их на разные
физические адреса.
Следует различать величину адресного пространства (физического и виртуального) и объем памяти, который в нем помещается.
Количество возможных сочетаний дает количество ячеек адресуемой физической памяти (232). Между тем, в ячейке находится не
один бит информации, а слово, т. е. число, кратное байту.
1.21. Образ процесса и виртуальное адресное пространство
Содержимое назначенного процессу виртуального адресного пространства, т. е. коды команд, исходные и промежуточные данные, а
также результаты вычислений, представляют собой образ процесса. Во время работы процесса постоянно выполняются переходы от
прикладных кодов к кодам ОС, которые либо явно вызываются из
прикладных процессов как системные функции; либо вызываются
как реакция на внешние события или исключительные ситуации,
возникающие при некорректном поведении прикладных кодов. Для
того, чтобы упростить передачу управления от прикладного кода
к коду ОС, а также для легкого доступа модулей ОС к прикладным
данным (например, для вывода их на внешние устройства), в большинстве ОС ее сегменты разделяют виртуальное адресное пространство с прикладными сегментами активного процесса, т. е. сегменты ОС и сегменты активного процесса образуют единое виртуальное
адресное пространство. Обычно виртуальное адресное пространство
процесса делится на две непрерывные части: системную и пользовательскую. В некоторых ОС, например, Windows NT, ОS/2, эти части
делятся поровну и имеют одинаковый размер по 2 Gb. Хотя соотношение может быть и другим, например, 1Gb для ОС, а остальное
– пользователю. Часть виртуального адресного пространства каждого процесса, отводимая для сегментов ОС, является идентичной
для всех процессов. Поэтому при смене активного процесса заменяется только вторая часть виртуального адресного пространства, содержащая его индивидуальные сегменты, как правило, это коды и
данные прикладной программы (рис. 4).
Системная часть виртуальной памяти ОС любого типа включает
область, подвергаемую страничному вытеснению, Paged; и область,
на которую страничное вытеснение не распространяется, Notpaged.
В невытесняемой области размещаются модули ОС, требующие быстрой реакции и/или постоянного присутствия в памяти. Напри26
Индивидуальные части виртуального адресного пространства
Paged
not
Paged
Общая часть виртуального адресного пространства
Рис. 4
мер, диспетчер потоков или код, который управляет заменой страниц памяти (загрузка – выгрузка страниц). Остальные модули ОС
могут подвергаться страничному вытеснению как и пользовательские сегменты.
1.22. Методы распределения памяти
Все алгоритмы распределения (управления) памятью (рис. 5)
можно разделить на 2 класса.
Алгоритмы, в которых используется перемещение сегментов
процессов между оперативной памятью и диском.
Алгоритмы, в которых внешняя память не используется.
Простейший способ управления оперативной памятью состоит в том, что память разбивается на несколько областей фиксированной величины, называемых разделами. Такое разбиение мо-
Методы распределения памяти
Без использования
внешней памяти
С использованием
внешней памяти
Фиксированные
разделы
Страничное
распределение
Динамические
разделы
Сегментное
распределение
Перемещаемые
разделы
Сегментностраничное
Рис. 5
27
жет быть выполнено вручную оператором во время старта системы
или во время ее установки. После этого границы разделов не изменяются. Очередной процесс, поступивший на выполнение помещается либо в общую очередь, либо в очередь к некоторому разделу (рис. 6).
Подсистема управления памятью в этом случае выполняет задачи:
– Сравнивает объем памяти, требуемой для вновь поступившей
задачи, с размерами свободных разделов и выбирает подходящий
раздел.
– Осуществляет загрузку программы в один из разделов и настройку адресов.
Уже на этапе трансляции разработчик программы может создать раздел, в котором ее следует выполнять. Это может позволить
без использования перемещаемого загрузчика получить машинный
код, настроенный на конкретную область памяти. При простоте реализации данный метод имеет существенные недостатки, так как
в каждом разделе может выполняться только один процесс – число
задач ограничено числом разделов. Кроме того, независимо от размера программы, она будет занимать весь раздел. С другой стороны, разбиение памяти на разделы не позволяет выполнять процессы, программы которых не помещаются ни в один из разделов и для
которых было бы достаточно памяти нескольких разделов. Рассмотрим распределение памяти динамическими разделами. Каждому
поступившему вновь приложению на выполнение на этапе создания
процесса, выделяется вся необходимая ему память (если достаточный объем памяти отсутствует – процесс не создается). После завершения процесса память освобождается, и на это место может быть
1-й раздел
Очередная задача
2-й раздел
Очередь
3-й раздел
Рис. 6
28
загружен другой процесс. Таким образом, в произвольный момент
времени ОЗУ представляет собой случайную последовательность
занятых и свободных участков (разделов произвольного размера).
Функции ОС, предназначенные для реализации данного метода,
следующие:
– Ведение таблиц свободных и занятых областей, в которых указываются начальные адреса и размеры участков.
– При создании нового процесса анализ требуемой памяти, просмотр таблицы свободных областей и выбор раздела, размер которого достаточен для размещения кодов и данных нового процесса.
Такой выбор может осуществляться по разным правилам, например: «Первый попавшийся раздел достаточного размера»; «Раздел, имеющий наименьший достаточный размер».
– Загрузка программы в выделенный раздел и корректировка
таблиц свободных и занятых областей. Данный способ предполагает, что программный код не перемещается во время выполнения,
а значит, настройка адресов может быть проведена единовременно
во время загрузки.
– После завершения процесса корректировка таблиц свободных
и занятых областей.
По сравнению с разбиением на фиксированные разделы, динамический метод более гибок. Но у него есть существенный недостаток – фрагментация памяти, т. е. образование большого числа несмежных участков в свободной памяти небольшого размера. При
использовании перемещаемых разделов удается решить проблему фрагментации памяти. Один из методов борьбы с фрагментацией – это перемещение всех занятых участков в сторону старших
или младших адресов так, чтобы вся свободная память образовала
одну незанятую область. В случае такого решения на ОС накладываются дополнительные функции: ОС время от времени вынуждена
копировать содержание разделов из одного участка памяти в другой, при этом корректируя таблицы свободных и занятых областей.
Эта процедура называется сжатием. Поскольку программы (процессы) перемещаются в ходе своего выполнения, то в данном случае
не удается выполнить настройку адресов с помощью перемещающего загрузчика. Приходится использовать динамическое преобразование адресов. Хотя процедура сжатия приводит к более экономичному использованию памяти, она может потребовать значительных
ресурсов от системы, что часто перевешивает преимущество данного метода.
29
1.23. Swapping и виртуальная память
Swapping – это подкачка. В мультипрограммном режиме помимо активного процесса, который в настоящий момент выполняется
процессором, имеются приостановленные процессы, находящиеся
в состоянии ожидания ввода-вывода или освобождения ресурсов.
Образы таких неактивных процессов могут быть временно выгружены на диск до следующего цикла активности. К моменту, когда
приходит очередь выполнения такого выгруженного процесса, его
образ возвращается с диска в оперативную память (целиком). Если при этом выясняется, что свободного места в ОЗУ не хватает, то
на диск загружается другой процесс. Такая подмена ОЗУ дисковой
памятью позволяет повысить уровень мультипрограммирования.
Транслятор, используя виртуальные адреса, переводит программу
в машинный код, как будто в ее распоряжении имеется однородная
оперативная память большого объема. Виртуализация оперативной памяти осуществляется совокупностью программных кодов ОС
и аппаратных средств процессора и включает решение следующих
задач.
– Размещение данных в запоминающих устройствах разного типа. Например, часть кода программы в ОЗУ, а часть – на диске.
– Перемещение по мере необходимости данных между памятью
и диском.
– Выбор образов процессов и их частей для перемещения из ОЗУ
на диск и обратно.
– Преобразование виртуальных адресов в физические.
Виртуальная оперативная память может быть реализована на
основе двух разных подходов:
– swаpping – образы процессов при этом выгружаются на диск
и возвращаются в ОЗУ целиком (в чистом виде сейчас не используется);
– виртуальная память – между ОЗУ и диском перемещают части
(сегменты, страницы и т. п.) образов процессов.
Swаpping является частным случаем виртуальной памяти. Своппингу в чистом виде свойственна избыточность: когда ОС решает активизировать процесс, для его выполнения, как правило, не
требуется загружать весь процесс, а достаточно загрузить небольшую часть кодового сегмента, подлежащих выполнению команд и
частей сегментов данных, с которыми в данный момент работает
программа, а также отвести место под стек. Аналогично при освобождении памяти для загрузки нового процесса часто не требует30
ся выгружать другой процесс целиком. Достаточно вытеснить на
диск только часть его образца. Кроме того, своппинг в чистом виде
имеет еще один недостаток: при его использовании часто не удается загрузить для выполнения процесс, виртуальное адресное пространство которого превышает имеющуюся в наличии свободную
память.
В настоящее время все множество реализаций виртуальной памяти может быть представлено тремя классами:
– Страничная виртуальная память. Организует перемещение частей процессов между памятью и диском страницами, а именно: частями виртуального адресного пространства фиксированного и относительно небольшого размера.
– Сегментная виртуальная память. Предполагает перемещение
кодов сегментами, т. е. частями виртуального адресного пространства произвольного размера, полученного с учетом смыслового значения перемещаемых кодов.
– Сегментно-страничная виртуальная память. Использует двухуровневое деление: виртуальное адресное пространство делится на
сегменты, затем сегменты делятся на страницы. Единицей перемещения кодов является страница. Этот способ управления памятью
объединяет в себе элементы обоих предыдущих подходов.
1.24. Страничное распределение памяти
Виртуальное адресное пространство каждого процесса делится
на части одинакового фиксированного размера, называемые виртуальными страницами. В общем случае размер виртуального адресного пространства процесса не кратен размеру страницы, поэтому
последняя страница каждого процесса самодополняется фиктивной областью. Вся оперативная память компьютера также делится
на части такого же размера, называемые физическими страницами. Размер страницы выбирается равным степени двойки. В большинстве ОС размер страниц равен 4096, 512, 1024 байт. Это позволяет упростить механизм преобразования адресов. При создании
процесса ОС загружает в оперативную память несколько его виртуальных страниц (начальные страницы каждого сегмента и сегмента данных). Копия всего виртуального адресного пространства
процесса находится на диске. Смежные виртуальные страницы не
обязательно располагаются в смежных физических страницах. Для
каждого процесса ОС создает таблицу страниц – информационную
структуру, содержащую записи обо всех виртуальных страницах
31
процесса. Запись из таблицы называется дескриптером страницы и
включает следующую информацию.
– Номер физической страницы, в которую загружена данная
виртуальная страница.
– Признак присутствия (флаг), устанавливаемый в единицу, если виртуальная страница находится в ОЗУ.
– Признак модификации страницы (флаг), который устанавливается в единицу всякий раз, когда происходит запись по адресу,
относящемуся к данной странице.
– Признак обращения к странице (флаг), называемый битом доступа. Устанавливается в единицу при каждом обращении по адресу, относящемуся к данной странице.
Признаки присутствия модификации и обращения в большинстве современных процессоров устанавливаются аппаратно схемами процессора при выполнении операции с памятью.
Сами же таблицы страниц, также как и описываемые ими страницы, размещаются в ОЗУ. Адрес таблицы страниц включается
в контекст соответствующего процесса. При активации очередного процесса ОС загружает адрес его таблицы страниц в специальный регистр процессора, при этом при каждом обращении к памяти выполняется поиск номера виртуальной страницы, содержащей
требуемый адрес. Затем по этому номеру определяется нужный элемент таблицы страниц и из него извлекается описывающая страницу информация. Далее анализируется признак присутствия, и если
данная страница находится в ОЗУ, то выполняются преобразования
виртуального адреса в физический, т. е. виртуальный адрес заменяется указанным в таблице физическим адресом. Если же нужная
виртуальная страница в данный момент выгружена на диск – происходит так называемое страничное прерывание. При этом выполняющийся процесс переводится в состояние ожидания и активизируется другой процесс из очереди процессов, находящийся в состоянии готовности. Программа обработки страничного прерывания
находит на диске требуемую виртуальную страницу (для этого ОС
должна помнить положение вытесненной страницы в страничном
файле диска файловой системы) и пытается загрузить ее в ОЗУ. Если в памяти имеется свободная физическая страница, то загрузка
выполняется немедленно, если свободного места нет, то на основании принятой в данной ОС стратегии замещения страниц решается вопрос о том, какую страницу следует выгрузить из оперативной
памяти. После того, как выбрана страница, которая должна покинуть ОЗУ, обнуляется ее бит присутствия и анализируется признак
32
модификации. Если выталкиваемая страница за время последнего
пребывания в ОЗУ была модифицирована, то ее новая версия должна быть перезаписана на диск; если же нет – то принимается во внимание, что на диске уже имеется предыдущая копия этой виртуальной страницы и никакой записи на диск не производится.
1.25. Преобразование виртуальной страницы в физическую
Задача подсистемы виртуальной памяти состоит в отображении
виртуальных страниц в физические. Объем страницы выбирается
кратным степени двойки, из этого следует, что смещение S может
быть получено отделением k-младших разрядов в двоичной записи адреса, а оставшиеся старшие разряды адреса представляют собой двоичную запись номера страницы (при этом не важно, является страница виртуальной или физической). Например, если размер
страницы равен 210, то в этом примере – 1000111001, номер страницы будет равен числу 2, т. е. 10, а смещение – 00111001.
Номер страницы и ее начальный адрес могут быть получены
один из другого путем отбрасывания k нулей, соответствующих
смещению. Часто говорят, что таблица страниц содержит начальный физический адрес страницы памяти, хотя на самом деле в таблице указаны только старшие разряды адреса. Начальный адрес
страницы также называется базовым. В пределах страницы непрерывная последовательность виртуальных адресов отображается
в непрерывную последовательность физических адресов, это значит, что смещения виртуального и физического адреса равны между собой. Из этого следует простая схема преобразования виртуального адреса в физический (рис. 7).
Пусть произошло обращение к памяти по некоторому виртуальному адресу. Аппаратными средствами процессора выполняются
следующие действия:
– из специального регистра процессора извлекается адрес Т таблицы страниц (для каждого процесса этот адрес свой) активного процесса. На основании начального адреса Т и номера виртуальной страницы
Р (старшие разряды виртуального адреса), а также длины отдельной
записи в таблице страниц (системная константа L), определяется адрес
нужного дескриптора таблицы страниц, т. е. число (Т + РL);
– из этого дескриптора извлекается номер соответствующей физической страницы n;
– к номеру физической страницы присоединяется смещение S
(младшие разряды виртуального адреса).
33
K – младших разрядов
P
S смещение виртуальной
страницы
Номер виртуальной
страницы
T + PL
Таблица страниц
Регистр T ЦП
n
n № физической
страницы
S смещение физической
страницы
Рис. 7
Для уменьшения времени преобразования адресов во всех процессорах предусмотрен аппаратный механизм получения физического адреса по виртуальному. С этой же целью номер страницы выбирается равным степени 2, благодаря чему двоичная запись адреса
легко разделяется на номер страницы и смещение. В результате процедуры преобразования адресов более длительная операция сложения заменяется операцией присоединения (конкантенации).
1.26. Сегментное распределение памяти
При таком методе виртуальное адресное пространство процесса
делится на части – сегменты, размер которых определяется с учетом
смыслового значения содержащейся в них информации. Отдельный
сегмент может представлять собой подпрограмму, массив данных
и т. п. Деление виртуального адресного пространства на сегменты
осуществляется компилятором на основе указаний программиста
или в соответствии с принятыми в системе соглашениями. Максимальный размер сегмента определяется разрядностью виртуального адреса, например 32-разрядная организация шины адреса позволяет адресовать более четырех миллиардов ячеек памяти. При
этом максимально возможное виртуальное адресное пространство
процесса представляет собой набор из N виртуальных сегментов.
34
В каждом сегменте виртуальные адреса находятся в диапазоне
(00000000 – FFFFFFFF). Сегменты не упорядочены относительно
друг друга, так что общего линейного виртуального адреса для сегментов не существует.
Виртуальный адрес задается парой чисел: номером сегмента и линейным виртуальным адресом внутри сегмента. При загрузке процесса в ОЗУ помещается только часть его сегмента. Полная копия
виртуального адресного пространства находится на диске. Для каждого загруженного сегмента ОС подыскивает непрерывный участок
свободной памяти достаточного размера. Смежные сегменты виртуальной памяти одного процесса могут занимать в физической памяти несмежные участки. Если во время выполнения процесса происходит обращение по виртуальному адресу, относящемуся к сегменту, который в данный момент не присутствует в ОЗУ, происходит
прерывание. Операционная система приостанавливает активный
процесс, запускает на выполнение следующий, а параллельно организует загрузку нужного сегмента с диска. При отсутствии в памяти места для загрузки сегмента, ОС выбирает сегмент на выгрузку.
На этапе создания процесса во время загрузки его образа в ОЗУ система создает таблицу сегментов процесса, аналогичную таблице
страниц, в которой для каждого сегмента указывается.
1. Базовый физический адрес в ОЗУ.
2. Размер сегмента.
3. Правила доступа к сегменту.
4. Признаки модификации, присутствия и обращения к данному
сегменту.
Если виртуальное адресное пространство нескольких процессов
включает один и тот же сегмент, то в таблице сегментов этих процессов делаются ссылки на один и тот же участок ОЗУ, в который
данный сегмент загружается в единственном экземпляре. Таким
образом, сегментное распределение памяти имеет много общего со
страничным распределением.
Механизмы преобразования адресов этих двух способов управления памятью тоже похожи, но имеются и существенные отличия,
которые являются следствием того, что сегменты, в отличие от страниц, имеют произвольный размер. Виртуальный адрес при сегментной организации может быть представлен парой g, S (номер сегмента + смещение).
Физический адрес получается путем сложения адреса базового
сегмента, который определяется по номеру сегмента из таблицы сегментов, и смещения S (рис. 8).
35
g – номер сегмента
S смещение
Таблица сегментов
Физический адрес
Рис. 8
В данном случае невозможно обойтись конкантенацией. Именно
поэтому ядро заносит в таблицу страниц не полные адреса, а номера
физических страниц, которые совпадают со старшими разрядами
базовых адресов. Сегменты могут располагаться в физической памяти с любого адреса, следовательно, для определения местоположения в памяти необходимо задавать их полный начальный физический адрес. Использование операции сложения вместо конкантенации замедляет процедуру преобразования виртуального адреса
в физический по сравнению со страничной организацией.
Еще одним недостатком сегментного распределения памяти является фрагментация памяти, это означает, что после освобождения памяти от сегмента свободным остается произвольный участок
памяти, в который может быть записан либо сегмент такого же, либо меньшего размера. При продолжительной работе компьютера память может оказаться сильно фрагментированной. Для устранения
этого используется дефрагментация памяти, в ходе которой часть
сегментов выгружается в область вопинга и затем записывается обратно в память без промежутков.
При страничной организации памяти такая проблема тоже есть,
но она меньше проявляется.
1.27. Сегментно-страничное распределение памяти
Перемещение данных между памятью и диском осуществляется
не сегментами, а страницами, для этого каждый виртуальный сегмент и физическая память делится на страницы равного размера,
36
что позволяет более эффективно использовать память, сократив до
минимума фрагментацию. В отличие от набора виртуальных адресов при сегментной организации памяти все виртуальные сегменты
в сегментно-страничном методе образуют одно непрерывное линейное виртуальное адресное пространство. Координаты байтов в виртуальном адресном пространстве при сегментно-страничной организации можно задать двумя способами:
– линейным виртуальным адресом, который равен сдвигу одного
байта относительно границы общего линейного виртуального пространства.
– парой чисел, одно из которых является номером сегмента,
а другое смещением относительно начала сегмента.
При этом в отличие от сегментной модели при такой организации необходимо каким-то образом указать начальный виртуальный адрес сегмента с данным номером.
Система виртуальной памяти сегментно-страничной организации использует второй способ, так как он позволяет определить
принадлежность адреса заданному сегменту и проверить права доступа процесса к нему. Для каждого процесса ОС создает отдельную
таблицу сегментов, в которой содержатся описатели (дескрипторы)
всех сегментов процесса. Описание сегмента включает назначение
ему права доступа и другие характеристики, подобные тем, которые
содержатся в дескрипторах сегмента при сегментной организации
памяти (флаги, например). Однако имеется принципиальное отличие. В поле базового адреса указывается не начальный физический
адрес сегмента, отведенный ему в результате загрузки в оперативную память, а начальный линейный виртуальный адрес сегмента
в пространстве виртуальных адресов. Наличие базового виртуального адреса сегмента в дескрипторе позволяет однозначно преобразовать адрес, заданный в виде пары (№ сегмента, смещение в сегменте) в линейный виртуальный адрес байта, который затем преобразуется в физический адрес страничным механизмом. Деление
общего линейного виртуального адресного пространства процесса и
физической памяти на страницы осуществляется так же, как это
делается при страничной организации памяти. Размер страницы
выбирается равным степени двойки, что упрощает механизм преобразования виртуальных адресов в физические. Виртуальные страницы нумеруются в пределах виртуального адресного пространства
каждого процесса, а физические – в пределах ОЗУ. При создании
процессов в память загружается только часть страниц, остальные
загружаются только по необходимости. Время от времени система
37
выгружает уже ненужные страницы, освобождая место для новых.
Система ведет для каждого процесса таблицу страниц, в которой
указывается соответствие виртуальных страниц физическим. Базовые адреса таблицы сегментов и таблицы страниц процесса являются частью его контекста. При активации процесса эти адреса загружаются в специальные регистры ЦП и используются механизмом преобразования адресов. Преобразование виртуального адреса
в физический происходит в два этапа (рис. 9).
На первом этапе работает механизм сегментации. Исходный виртуальный адрес, заданный в виде пары (номер сегмента и смещение)
преобразуется в линейный виртуальный адрес. Для этого на основании базового адреса таблицы сегментов и номера сегмента вычисляется адрес дескриптора сегмента, анализируются его поля и выполняется проверка возможности выполнения заданной операции.
Если доступ к сегменту разрешен, то вычисляется линейный виртуальный адрес путем сложения базового адреса сегмента, извлеченного из дескриптора, и смещения, заданного в исходном виртуальном адресе.
На втором этапе работает страничный механизм. Полученный
линейный виртуальный адрес преобразуется в искомый физичеРегистр ЦП
Начальный адрес
Таблицы
Номер сегмента
Смещение сегмента
Таблица сегментов
Дескриптор сегмента
I ЭТАП
Базовый виртуальный
адрес сегмента
Линейный
виртуальный
адрес
Номер
страницы
Таблица страниц
II ЭТАП
Дескриптор страниц
Смещение
страницы
Базовый
физический
адрес
Исходный
физический
адрес
Рис. 9
38
ский адрес. В результате преобразования линейный виртуальный
адрес представляется в том виде, в котором он используется при
страничной организации, в виде пары (номер страницы, смещение). Благодаря тому, что размер страницы выбран равным степени
двойки, эта задача решается простым отделением некоторого количества младших двоичных разрядов, при этом в старших содержится номер виртуальной страницы, а в младших – смещение искомого элемента относительно начала страницы. Если размер страницы
равен 2k, то смещением является содержимое k младших разрядов,
а остальные старшие разряды содержат номер виртуальной страницы, которой принадлежит искомый адрес. Далее преобразование
адреса происходит так же, как при страничной организации: старшие разряды линейного виртуального адреса, содержащие номер
виртуальной страницы, заменяются на номер физической страницы, взятый из таблицы страниц, а младшие разряды виртуального
адреса, содержащие смещение, остаются без изменения.
1.28. КЭШ память
КЭШ – это способ совместного функционирования двух типов памяти, отличающихся временем доступа и способом хранения данных, который за счет динамического копирования в быстрое запоминающее устройство наиболее часто используемой информации из
более медленного запоминающего устройства, позволяет ускорить
доступ к данным, хранящимся на диске.
КЭШ память прозрачна для программ и пользователей. Система
не требует никакой внешней информации. Ни пользователь, ни программа не принимают участия в перемещении данных из ОЗУ в КЭШ
и обратно. Все это делается автоматически системными средствами.
КЭШ памятью также называют способы организации работы запоминающих устройств 2 типов (быстрого и медленного). КЭШ – быстрая
память, ОЗУ – медленная. Если кэширование применяется для уменьшения среднего времени доступа к ОЗУ, то в качестве КЭШ используют быстродействующую статическую память (среднее время обращения к ОЗУ – 6 нс, а к статическому КЭШ – 1 нс). Если кэширование используется системой ввода-вывода для ускорения доступа к данным,
хранящимся на диске, то в этом случае роль КЭШ памяти выполняют буферы ОЗУ, в которых оседают наиболее активно используемые
данные. Виртуальную память также можно считать одним из вариантов реализации принципов кэширования, в котором ОЗУ выступает в роли КЭШ по отношению к внешней дисковой памяти.
39
Содержимое КЭШ памяти – это совокупность записей обо всех
элементах, загруженных в нее из основной памяти. Каждая запись
об элементе данных включает в себя:
– значение элемента данных (сама информация);
– адрес, который этот элемент данных имеет в ОЗУ (в основной
памяти);
– дополнительная информация, которая используется для реализации алгоритма смещения данных в КЭШе и обычно включает
признак модификации и признак действительности данных.
При каждом обращении к основной памяти по физическому адресу просматривается содержимое КЭШ памяти с целью определения,
не находятся ли там нужные данные. КЭШ память не является адресуемой поэтому поиск нужных данных осуществляется по содержимому, т. е. по взятому из запроса значению поля адреса в ОЗУ.
Если данные обнаруживаются в КЭШе (быстрой памяти), т. е.
произошло КЭШ попадание – данные считываются из КЭШ и результат передается источнику запроса.
Если нужные данные отсутствуют в КЭШе, т. е. произошел КЭШпромах – они считываются из основной памяти и передаются источнику запроса и одновременно с этим копируются в КЭШ. Схема работы КЭШ приведена на рис. 10.
Среднее время доступа в системе с КЭШ линейно зависит от вероятности попадания в КЭШ и изменяется от среднего времени доступа
к ОЗУ до среднего времени доступа к КЭШ. Таким образом, использование КЭШ имеет смысл, когда вероятность попадания в него высока. Эта вероятность зависит от нескольких факторов, таких как
объем КЭШ; алгоритм замещения данных в КЭШ; особенности выполняемой программы; времени ее работы; уровня мультипрограм-
Источник запроса
к основной памяти
Медленный запрос
Медленный ответ
Быстрый ответ
КЭШ
Рис. 10
40
Основная
память
(ОЗУ)
мирования и других особенностей вычислительного процесса. В большинстве реализаций КЭШ процент попадания оказывается высоким
(свыше 90%). Такое высокое значение вероятности нахождения данных в КЭШ объясняется наличием двух объективных свойств. Они
называются свойствами пространственной и временной локальности.
Временная локальность означает, что если произошло обращение по некоторому адресу, то следующее обращение по тому же
адресу с большой вероятностью произойдет в ближайшее время.
Пространственная локальность подразумевает, что если произошло обращение по некоторому адресу, то с высокой степенью вероятности в ближайшее время произойдет обращение к соседним
адресам. Основываясь на свойстве временной локальности, данные,
только что считанные из основной памяти, размещают в ЗУ быстрого доступа (КЭШ), предполагая при этом, что скоро они опять понадобятся. В начале работы системы, когда КЭШ еще пуст, почти каждый запрос к основной памяти выполняется по полной программе:
просмотр КЭШа, констатация промаха, чтение данных из основной памяти, передача результата источнику запроса и копирование
данных в КЭШ. Затем, по мере заполнения КЭШ, в соответствии со
свойством временной локальности возрастает вероятность обращения к данным, которые уже были использованы на предыдущих
этапах работы, т. е. к данным, которые содержатся в КЭШе и могут
быть оттуда быстро считаны.
Свойства пространственной локальности также используются
для увеличения вероятности КЭШ попаданий: как правило, с КЭШ
памяти считывается не один информационный элемент, к которому
произошло обращение, а целый блок данных, расположенный в основной памяти в непосредственной близости с данным элементом.
Поскольку при выполнении программы очень высока вероятность,
что команды выбираются из памяти последовательно одна за другой из соседних ячеек, то имеет смысл загружать в КЭШ память целый фрагмент программы. Например, если программа ведет обработку массива, то ее работу можно ускорить, загрузив в КЭШ часть
или даже весь массив.
1.29. Устройства ввода-вывода
Устройства ввода-вывода можно разделить на две категории:
блочные устройства и символьные.
Блочным устройством называется устройство, хранящее данные
фиксированного размера, причем у каждого блока имеется адрес.
41
Обычно размеры блоков независимо от типов устройства колеблются в пределах (512–32765) байт. Важное свойство состоит в том, что
каждый блок может быть прочитан независимо от остальных блоков. Наиболее распространенными блочными устройствами являются диски.
Символьное устройство – принимает или предоставляет поток
символов из какой-либо блочной структуры. Оно не является адресуемым и не выполняет операции поиска.
Примерами символьных устройств являются: принтеры, интерфейсные адаптеры, мыши и т. п. Устройства ввода-вывода покрывают огромный диапазон скоростей, что создает определенные
трудности для программного обеспечения, поскольку приходится обеспечивать работоспособность и производительность на скоростях передачи данных, различающихся на несколько порядков.
В табл. 1 приведены значения скоростей передачи данных для разных устройств ввода-вывода.
Контроллер – это электронный компонент любого устройства
ввода-вывода. Интерфейс между устройством и контроллером чаТаблица 1
Значения скоростей передачи данных для разных устройств ввода-вывода
Устройство
Скорости данных
Клавиатура
10 байт/с
Мышь
100 байт/с
Модем аналоговый
7 кб/с
ISDN
16 кб/с
Лазерный принтер
100 кб/с
Сканер
400 кб/с
10-ти Мб Ethernet
1,5 мб/с
USB 2.0
15 мб/с
CD-ROM 40 скоростей
6 мб/с
100 Мб Ethernet
12 мб/с
IDE (ATA-2)
16,5 мб/с
WiFi 802.11
50 мб/с
ScSi-disk ultra-2
80 мб/с
Гигабитная сеть Ethernet
125 мб/с
Шина PCI
528 мб/с
42
сто является интерфейсом очень низкого уровня. Работа контроллера заключается в преобразовании последовательного потока битов в блок байтов и в выполнении коррекции ошибок, если это необходимо. Битовый поток обычно собирается бит за битом в буфере
контроллера, затем проверяется контрольная сумма блока и, если
она совпадает с объявленной (например в случае HDD в заголовке
сектора), блок объявляется считанным без ошибок, после чего он
копируется, например, в буфер, расположенный в ОЗУ. Контроллер монитора работает как бит – последовательное устройство на
таком же низком уровне. Он считывает из памяти байты, которые
следует отобразить и формирует сигналы для вывода изображения
на экран.
1.30. Способы организации ввода-вывода
Современные контроллеры внешних устройств имеют все признаки вычислительной системы, т. е. в их состав входит процессор,
ОЗУ и внутренние шины. Схема контроллера устройства ввода-вывода представлена на рис. 11.
Рассмотрим различные способы отображения регистров внешних устройств на память вычислительной системы. У процессора есть две команды для ввода и вывода. Это команды <IN REG.
PORT> и <OUT PORT. REG>. С помощью команды <IN> процессор
может записать из регистра устройства ввода-вывода порт в свой регистр. Команда <OUT> выполняет противоположное действие – записывает из своего регистра в регистр внешнего устройства.
Электромеханическая часть устройства
Буферная
память
Управляющие
регистры
Контроллер
устройства
Линия управления
Шина
Рис. 11
43
Команды <IN> и <OUT> есть в ассемблере, но не могут использоваться в языках высокого уровня.
С другой стороны на языках высокого уровня можно применять команды <write> и <read>, с помощью которых можно писать
и считывать из ОЗУ. Например, команда <In R0 3> и <MOV R0 3>
выполняет принципиально разные действия. Первая команда считает содержимое порта 3 в регистр R0 процессора, а вторая команда
считает в этот же регистр содержимое слова памяти по адресу 3. Таким образом, 3 в этих командах означают различные адреса из непересекающихся адресных пространств. Это происходит за счет того, что в случае, например, записи в регистр R0 процессора из порта 3, возбуждается линия управления, т. е. аппаратно эти адресные
пространства разносятся. Имеются три способа для организации
ввода-вывода.
1 способ. Раздельные адресные пространства
Разделение происходит за счет линии управления.
FFFFFFFF
Память
ОЗУ
00000000
00000000
Порт
ввода-вывода
2 способ. Одно адресное пространство
При отображении регистров ввода-вывода на память каждая
команда процессора, обращающаяся к памяти, может с таким же
успехом обращаться к управляющим регистрам устройств.
Память
ОЗУ
Адресное
пространство
ввода-вывода
При этом каждому управляющему регистру назначается уникальный адрес в памяти. Иногда такую схему называют отображаемым на адресное пространство памяти вводом-выводом. Обычно для регистров устройств отводятся адреса на вершине адресного
пространства.
Существуют разные гибридные схемы отображения ввода-вывода на память.
44
3 способ. Гибридный
Один из возможных вариантов третьего, гибридного подхода.
ОЗУ
Буферы
Порты
ввода-вывода
ОЗУ
Эта схема широко используется в платформах на базе Intel, в которых помимо портов ввода-вывода с адресами от 0 до 64 Кб используется адресное пространство ОЗУ от 640 Кб до 1 Мб для буферов
устройств ввода-вывода. Во всех случаях, когда ЦП хочет прочитать
слово данных либо из памяти, либо из порта ввода-вывода, он сначала выставляет нужный адрес на адресную шину, после чего выставляет <read> (считать) на управляющую шину. Сигнальная линия
при этом позволяет отличить обращение к памяти от обращения
к порту. В зависимости от состояния этой линии на запрос процессора реагирует либо устройство ввода-вывода (контроллер), либо память. Если пространство адресов общее (вариант 2), то каждый модуль памяти и каждое устройство ввода-вывода сравнивает выставленный на шину адрес с обслуживаемым им диапазоном адресов.
Если адрес попадает в этот диапазон, то соответствующее устройство реагирует на запрос процессора. Поскольку выделенные внешним устройством адреса удаляются из памяти, внешние устройства
не реагируют на них и конфликта не происходит. Схема 1 и 2 имеет
свои достоинства и недостатки.
К достоинствам относится:
При отображение на адресное пространство памяти ввода-вывода не требуются специальные команды процессора <in> и <out>.
В результате программу можно написать целиком на языке С, без
вставок на ассемблере и обращений к подпрограммам.
При отображении регистров ввода-вывода на память не требуется специального механизма защиты от пользовательских процессов, пытающихся обращаться к внешним устройствам. Все, что
нужно сделать – это исключить ту часть адресного пространства,
на которую отображаются управляющие регистры ввода-вывода из
адресного пространства пользователя. В результате такая схема позволяет разместить драйверы различных устройств в различных
адресных пространствах, тем самым не только уменьшив размер
ядра, но и исключив вмешательство драйверов в дела друг друга.
45
CPU
процессора
Ввод-вывод
Память
ОЗУ
Шина
Рис. 12
К недостаткам этих схем относится:
В большинстве современных ПК применяется кэширование памяти. Кэширование управляющих регистров привело бы к краху
ядра. Чтобы не допустить такой ситуации, необходима специальная
аппаратура, способная выборочно запрещать кэширование. Например, в зависимости от номера страницы памяти, к которой обращается процессор. Таким образом, отображение регистров ввода-вывода на память увеличивает сложность аппаратуры и самой ОС, которой приходится управлять избирательным кэшированием.
При едином адресном пространстве все модули памяти и устройства ввода-вывода должны изучать все обращения процессора к памяти, чтобы определить, на какие следует реагировать. Если у компьютера одна общая шина, реализовать подобный просмотр обращений несложно (рис. 12).
1.31. Использование нескольких шин для ввода-вывода
Соединение элементов в компьютере по принципу «каждый
с каждым» практически неосуществимо из-за его сложности, поэтому уже на первых компьютерах использовались шины. Шина – не
только набор проводников, по которым могут передаваться уровни
напряжений, соответствующие нулям и единицам, но и устройства,
которые ей управляют в соответствии с протоколом шины. Главная
характеристика шины – это ее разрядность.
Как правило, используется несколько шин.
– Одна ее часть предназначена для передачи данных – это шина
данных.
– Другая часть – для передачи адресов (адреса имеют не только
ячейки ОЗУ, но и практически все активные компоненты компьютера).
– Шина управления, выполняющая сервисные функции, помогающие управлять элементами компьютера.
46
Следующая характеристика шины – скорость передачи данных
(килобайт/мегабайт в секунду), чем выше разрядность – тем больше скорость. Помимо самих проводников в шину входят обслуживающие ее микросхемы, называемые мостами. Основная функция
мостов – управление шиной по ее протоколу. Любая шина, которая
управляется мостом, пробегает по времени определенное количество состояний, которые повторяются в цикле. В каждый данный
момент шина может находиться только в определенном состоянии:
принимать данные, ждать, передавать данные и тому подобное.
В современных ПК имеются несколько шин.
– Системная шина: чаще всего самая быстрая; имеет наибольшее
число разрядов; соединяет две главные части ПК – процессор и память.
Таким образом, в ядро системы входит три компонента, без которых
она не может существовать: процессор, шина (системная) и память.
– Одна или несколько локальных шин, выполняющих вспомогательные функции. К таким шинам могут присоединяться внешние
устройства. В задачу мостов входят также задачи передачи данных
с одной шины на другую. Мосты необходимы также и потому, что
шины могут работать на разных частотах и по разным протоколам.
– Сервисная шина, которая соединяет некоторые элементы или
компоненты компьютера, такие как системные часы и тому подобное.
Идея использования шины состоит в том, что все, кому надо общаться между собой в системе, подключаются к шине (они – абоненты). В каждый данный момент шина может соединять только
2 устройства, остальные устройства в такие моменты должны от нее
отключаться.
В конструкции современных ПК используется системная, т. е.
быстрая шина, напрямую соединяющая процессор и память, показанная на рис. 13.
Быстрая шина
CPU
процессора
Память ОЗУ
Вводвывод
Универсальная шина
Рис. 13
47
Быстрая ( системная ) шина предназначена для увеличения скорости обмена данными между процессором и памятью. Сложность
применения системной шины на компьютерах с отображением регистров ввода-вывода на память состоит в том, что у устройств ввода-вывода нет способа опреднлить адреса памяти, выставляемые
процессором на эту шину, следовательно, они не могут реагировать
на такие адреса, поэтому чтобы отображение регистров ввода-вывода могло работать по этой схеме, необходимы специальные меры.
Для решения этой проблемы можно сначала все обращения к памяти посылать по системной (быстрой) шине (чтобы не снижать производительности). Если память не отвечает – процессор пытается
сделать это еще раз, но по медленной (универсальной) шине, к которой подключены устройства ввода-вывода. Такое решение работает,
но требует увеличения сложности аппаратуры.
1.32. Прямой доступ к памяти
Центральный процессор может запрашивать данные от контроллера ввода-вывода по одному байту, но подобная система обмена
данными крайне нежелательна, так как расходует огромное количество процессорного времени, поэтому на практике уже давно используется другая схема – прямой доступ к памяти (DМА). Операционная система может использовать этот метод лишь при наличии
соответствующего оборудования – контроллера DМА.
DМА-контроллер может получать доступ к системной шине, независимо от процессора. Он содержит несколько регистров, доступных процессору для чтения и записи. К ним относятся: регистр
адреса памяти, счетчик байтов и один или несколько управляющих регистров. Эти регистры определяют какой порт ввода-вывода должен быть использован, направление переноса (чтение из
устройств ввода-вывода или запись), единицу переноса (осуществлять перенос побайтно или пословно), а также число байтов, которые следует перенести за одну операцию. Таким образом, процессор может запрограммировать контроллер на перенос данных
из устройства ввода-вывода в память и обратно так, как это удобно
для ОС (рис. 14).
Рассмотрим, как происходит перенос данных с диска в оперативную память.
Сначала контроллер считывает с диска блок (один или несколько
секторов) последовательно, байт за байтом, пока весь блок не окажется во внутреннем буфере контроллера. Этот контроллер прове48
Диск
Контроллер
ДМА
Контроллер диска
Память
1
4
Адрес
БУФЕР
Счетчик
CPU
ОЗУ
Управление
5
2
3
Шина
Рис. 14
ряет контрольную сумму, чтобы убедиться, что при чтении не произошло ошибки. После этого контроллер диска инициирует прерывание. Когда ОС начинает работу, она может прочитать блок диска
побайтно или пословно, в цикле сохраняя считанное слово или байт
в оперативной памяти.
При использовании DМА происходит следующее: сначала процессор программирует DМА-контроллер (шаг 1), устанавливая его
регистры и указывая таким образом какие данные и куда следует
переместить. Затем процессор дает команду дисковому контроллеру
прочитать данные во внутренний буфер и проверить контрольную
сумму. Когда данные проверены и получены контроллером диска,
DМА-контроллер начинает перенос данных, посылая по шине на
контроллер диска запрос чтения (шаг 2). Этот запрос выглядит как
обычный запрос чтения, так что контроллер диска даже не знает,
пришел он от процессора или DМА-контроллера. Адрес памяти уже
находится на адресной шине, так что контроллер диска знает, куда
следует переслать слово из своего внутреннего буфера.
Запись в память (шаг 3) является еще одним стандартным циклом шины.
Когда запись закончена, контроллер диска также по шине посылает сигнал подтверждения контроллеру DМА (шаг 4). Затем контроллер DМА увеличивает используемый адрес памяти и уменьшает значение счетчика байт. После этого шаги 2–4 повторяются, пока
значение счетчика не станет равно нулю.
По завершению цикла копирования DМА-контроллер инициирует прерывание процессора (шаг 5).
49
Отметим, что все это время, до 5-го шага, процессор и контроллер DМА занимались своим делом (процессор выполнял программу,
а DМА-контроллер читал данные).
Самые простые DМА-контроллеры за один раз выполняют одну операцию переноса данных, как это было описано ранее. Более
сложные контроллеры могут выполнять за один раз несколько подобных операций. У них несколько каналов, каждый из которых
управляется своим набором внутренних регистров. Такой контроллер может осуществлять перенос данных «одновременно» и обслуживать несколько устройств ввода-вывода. Многие шины могут работать в двух режимах: пословном и поблочном.
Пословный режим. В таком режиме процедура выглядит так,
как было описано ранее, но контроллер выставляет запрос на перенос первого слова и получает его, так что если процессору нужна
шина, ему придется подождать. Этот механизм называется захватом цикла, потому что контроллер периодически забирает случайный цикл у процессора, слегка его притормаживая.
Поблочный режим. В этом режиме контроллер DМА велит
устройству занять шину, сделать серию пересылок и отпустить ее.
Такой способ называется пакетным режимом, он более эффективен,
чем захват цикла, поскольку занятие шины требует времени, а в пакетном режиме эта процедура выполняется всего один раз для передачи блока данных.
1.33. Процедура прерываний. Контроллер прерываний
Аппаратное прерывание – это сигнализация от устройства (его
контроллера) центральному процессору о некоторых событиях, требующих программных действий. Прерывания требуют приостановки выполнения текущего потока команд (с сохранением состояния)
и запуска процедуры обработки прерывания. Эта процедура первым
делом должна идентифицировать источник прерывания (их может
быть несколько), а затем выполнить действия, связанные с реакцией на событие. Если события должны вызывать некоторые действие
прикладной программы, то обработчику прерывания следует только подать через ОС сигнал, который запустит поток команд, выполняющий эти действия.
Контроллер прерываний является периферийным устройством,
которое связано с процессором через ту или иную локальную шину.
По этой шине процессор может обращаться к регистрам контроллера, программируя его режимы и управляя им, а также получать
50
от контроллера 16-битный вектор прерывания, для чего в интерфейсе системной шины процессора и локальной шины имеется специальная команда подтверждения прерывания. Контроллер имеет
входы запросов от источников и один выход общего запроса. Каждому из входов соответствует свой вектор. Программированием регистров контроллера задается номер вектора для входа 0, остальным
входам соответствуют последующие номера векторов. Каждый вход
может быть программно замаскирован – тогда он не вызывает сигнал общего запроса. Контроллер занимает адреса в пространстве
ввода-вывода, а программное обращение позволяет управлять режимами работы контроллера, а также приоритетами.
1.34. Принципы программного обеспечения ввода-вывода
При разработке той части ОС, которая отвечает за ввод-вывод
следует придерживаться ряда принципов, главными из которых являются следующие.
Ключевая концепция разработки ПО ввода-вывода известна как
независимость от устройств. Эта идея означает возможность написания программ, способных получать доступ к любому устройству
ввода-вывода без предварительного указания конкретного устройства. Например, программа, читающая данные из входного файла,
должна с одинаковым успехом читать данные, записанные на дискете, жестком диске или компакт-диске. При этом не должны требоваться какие-либо изменения в программе. В качестве выходного
устройства также с равным успехом может быть указан экран, файл
на любом диске или принтер. Таким образом, все проблемы, связанные с отличиями этих устройств, должна решать ОС.
Имя файла или устройства должно быть просто текстовой строкой или целым числом и никаким образом не зависеть от физического устройства. Например, в Unix-подобных ОС все диски могут
быть произвольным образом интегрированы в иерархию файловой
системы так, что пользователю необязательно знать какое имя каждому устройству соответствует. Таким образом, все файлы и устройства адресуются одним и тем же способом по имени.
Ошибки должны обрабатываться как можно ближе к аппаратуре. Если контроллер обнаруживает ошибку чтения, например,
он должен по возможности попытаться исправить ее сам. Если он
не может этого сделать, то эту ошибку должен обработать драйвер устройства, возможно, попытавшись считать блок данных еще
раз. Иногда ошибки бывают временными. Как, например, ошибки
51
чтения, вызванные пылинками на читающих головках. Такие
ошибки часто исчезают при повторном чтении. Только если нижний уровень не может сам справиться с проблемой, ему следует информировать об этом верхний уровень.
Например, когда сетевой пакет приходит по сети, ядро не знает, куда его поместить до тех пор, пока не будет прочитан и проанализирован заголовок пакета. Кроме того, для многих устройств реального времени крайне важными оказываются параметры сроков
поступления данных, поэтому поступающие данные должны быть
помещены в буфер заранее, чтобы скорость, с которой эти данные
получаются из буфера или передаются в него вспомогательными
программами, не зависела бы от скорости заполнения буфера.
1.35. Програмный ввод-вывод
Главный аспект программного ввода-вывода на примере печати
(сетевой принтер) состоит в том, что после печати каждого символа процессор в цикле опрашивает готовность устройства. Такое поведение процессора называется опросом или ожиданием готовности
(активным ожиданием). Программный ввод-вывод легко реализуется, но его существенный недостаток состоит в том, что процессор занят все время, пока он осуществляется.
1.36. Управляемый прерываниями ввод-вывод.
Использование DМА
Предоставить процессору возможность делать что-нибудь в то
время, когда принтер находится в состоянии готовности, можно
с помощью прерывания. Процессор вызывает планировщик, который запускает какой-либо другой процесс, а процесс, попросивший
распечатать строку, оказывается заблокирован на все время печати строки. Когда принтер напечатал символ и готов принять следующий, он инициализирует прерывание. Это прерывание вызывает остановку текущего процесса и сохранение его состояния. Затем
запускается процедура обработки прерывания от принтера. Если
напечатаны все символы, то обработчик принимает меры для разблокировки процессов пользователя. В противном случае он печатает следующий символ, подтверждая прерывание, и возвращается
к процессу, выполнение которого было приостановлено. Недостаток
метода в том, что при печати каждого символа, сама обработка занимает некоторое время, это неэффективный метод.
52
Решение этой проблемы заключается в использовании DМА.
Идея состоит в том, чтобы позволить контроллеру DМА поставлять
принтеру символы по одному, не беспокоя при этом процессор. По
существу этот метод почти не отличается от программного вводавывода, с той лишь разницей, что всю работу вместо процессора выполняет контроллер DМА. Наибольший выигрыш от использования DМА состоит в уменьшении количества прерываний с одного
на один печатный символ до одного на один буфер.
Если символов много, а прерывание обрабатывается медленно,
то этот выигрыш весьма существенен.
1.37. Программные уровни ввода-вывода
Программное обеспечение ввода-вывода обычно организуется
в виде четырех уровней. У каждого уровня есть четко очерченная
функция, которую он должен выполнять, и строго определенный интерфейс с соседними уровнями. Функции и интерфейсы этих уровней меняются от одной ОС к другой. Эти уровни рассмотрены далее.
1.38. Обработка прерываний и драйверы
Хотя программный ввод-вывод бывает полезен, для большинства операций ввода-вывода прерывания являются неприятным, но
необходимым средством.
Лучший способ сделать прерывания незаметными заключается
в блокировке драйвера, начавшего операцию ввода-вывода, вплоть
до окончания этой операции и получения прерывания. Драйвер
может заблокировать себя сам, выполнив на семафоре процедуру
<down> на переменной состояния. Когда прерывание начинается, начинает работать обработчик прерываний, а после окончания прерывания, он может заблокировать драйвер, допустивший прерывание.
В любом случае драйвер разблокируется обработчиком прерывания. Такая схема лучше всего работает в драйверах, являющихся
процедурами ядра со своим собственным состоянием, стеком и счетчиком команд.
Рассмотрим программные уровни ввода-вывода.
1. Программное обеспечение ввода-вывода уровня пользователя.
2. Устройство – независимое программное обеспечение ОС.
3. Драйверы устройств.
4. Обработчики прерываний.
5. Аппаратура.
53
В большинстве операционных систем определен стандартный
интерфейс, который должен поддерживать все блочные драйверы и
второй стандартный интерфейс, который должен поддерживать все
символьные драйверы.
Эти интерфейсы включают наборы процедур, которые могут вызываться остальной частью ОС для обращения к драйверу.
К этим процедурам относятся чтение блока, запись символьной
строки и тому подобное.
Некоторые ОС являются монолитными, т. е. представляют собой
единую двоичную программу, содержащую в себе в откомпилированном виде все необходимые ей драйверы. Хотя уже в MS-DOS перешли к динамической перегрузке драйверов.
Рассмотрим структуру и функции драйверов.
Важными компонентами программного обеспечения являются драйверы – программные модули, содержащие процедуры работы с устройствами. Необходимость выделения драйверов в отдельные модули заключается в следующем. Устройство определенного
назначения может иметь самые разные реализации. Разработчик
драйвера хорошо знает программную модель и особенности работы со своим устройством. У драйверов несколько функций. Наиболее очевидная функция любого драйвера состоит в обработке абстрактных запросов чтения и записи от независимого от устройств
программного обеспечения, которое расположено над ним. Кроме
этого драйверы должны выполнять некоторые другие функции,
например драйвер при необходимости должен инициализировать
устройство. Ему также может понадобиться управлять регистрацией событий и управлять энергосбережением. Типичный драйвер начинает работу с проверки входных параметров. Если они не удовлетворяют определенным требованиям, драйвер возвращает ошибку. В противном случае драйвер преобразует абстрактные команды
в конкретные. Например, дисковый драйвер может преобразовать
линейный номер блока в физические координаты (головка–дорожка–сектор). Затем драйвер может проверить не используется ли данное устройство в данное время. Если оно занято, запрос может быть
поставлен в очередь. Если свободно, проверяется аппаратный статус
устройства, чтобы определить может ли запрос быть обслужен немедленно.
Также может оказаться необходимым включить устройство или
запустить двигатель, прежде чем начать процедуру переноса данных. Как только устройство включено и готово, можно начать процесс управления им. Управление подразумевает выдачу серии ко54
манд. Именно в драйвере определяется последовательность команд,
после чего драйвер начинает записывать команды в регистры контроллера устройства.
После записи каждой команды в контроллер необходимо проверить, принял ли ее контроллер и готов ли принять следующую.
Некоторые контроллеры способны принимать связанные списки
команд, находящиеся в памяти. Они способны сами считывать эти
списки при помощи ОС.
После того, как драйвер передал все команды контроллеру, считывание может развиваться двумя способами. Часто драйвер должен ждать, пока контроллер не выполнит для него работу, поэтому он блокируется до тех пор, пока прерывание от устройства его не
разблокирует. В другом случае операция завершается без задержек,
и драйверу не нужно блокироваться.
Драйвер возвращает информацию о своем состоянии и для вызывающей программы. Если в очереди находятся другие запросы, один из них может быть выбран и запущен. В противном случае драйвер блокируется в ожидании следующего запроса. Драйверам не разрешается обращаться к системным вызовам, но им
часто бывает необходимо взаимодействовать с ядром. Обычно разрешается обращение к некоторым системным процедурам. Например, драйверы обращаются к системным процедурам для выделения им аппаратно-фиксированных страниц памяти в качестве буфера. А также затем, чтобы вернуть эти страницы обратно ядру.
Кроме того, драйверы пользуются вызовами, управляющими таймерами, контроллерами прерываний, контроллерами DMA и тому
подобное.
1.39. Независимое от устройств
программное обеспечение ввода-вывода
Необходим единообразный интерфейс для устройств.
Размер блока не должен зависить от устройства.
Основная задача независимого от устройств программного обеспечения состоит в выполнении функций ввода-вывода, общих для
всех устройств, и предоставление единообразного интерфейса для
программ уровня пользователя.
Главной задачей ОС является обеспечение того, чтобы все устройства ввода-вывода и их драйверы выглядели бы более или менее
одинаково. Эта задача связана с интерфейсом между драйверами
устройств и остальной частью ОС.
55
Сложность в том, что функции драйверов, доступные системе,
отличаются. На практике это означает, что функции ядра, необходимые для драйверов, тоже отличаются.
Принципиально другой подход состоит в том, что у всех драйверов может быть сделан одинаковый или похожий интерфейс. При
этом значительно легче установить новый драйвер при условии, что
он соответствует стандартному интерфейсу. Это также означает, что
программист, пишущий драйвер, знает, что от него требуется, т. е.
какие функции он должен реализовывать, и к каким функциям
ядра можно обращаться.
Другой аспект единообразия интерфейса состоит в именовании
устройств ввода-вывода. Независимое от устройств ввода-вывода
программное обеспечение занимается отображением символьных
имен устройств на соответствующие драйверы.
1.40. Буферизация ввода-вывода
Одна из возможных стратегий обработки поступающих символов от входных устройств состоит в обращении процессов пользователя к системному вызову <read> и блокировки в ожидании отдельного символа. Каждый прибывающий символ выглядит как
прерывание. Процедура обработки прерываний передает символ
пользовательскому процессу и разблокирует его. Поместив куда-нибудь полученный символ, процесс читает следующий и опять блокируется. Недостаток такого подхода заключается в том, что процесс пользователя должен быть активизирован при прибытии каждого символа, что неэффективно. Более эффективно решение, когда
пользовательский процесс предоставляет буфер размером в n символов в пространстве пользователя, после чего выполняет чтение n
символов.
Процедура обработки прерываний помещает приходящие символы в буфер, пока он не заполнится. Затем активизируется процесс
пользователя. Но у такого подхода есть и недостаток: если слишком
много процессов начнут фиксировать свои страницы в памяти, то
<pull> (общее количество) доступных страниц уменьшится, в результате чего уменьшится производительность. Третий подход состоит в создании буфера, в который обработчик прерываний будет
помещать поступающие символы в ядре. Когда этот буфер заполнится, извлекается страница с буфером пользователя, и содержание буфера помещается туда за одну операцию. Такая схема намного эффективнее.
56
1.41. Вопросы к первому разделу
1. Перечислите функции ОС по управлению памятью в мультипрограммной системе.
2. Что используется для идентификации переменных и команд
на разных этапах жизненного цикла программы?
3. Является ли диапазон возможных адресов виртуального пространства у всех процессов одинаковым?
4. Почему совпадение виртуальных адресных пространств разных процессов не приводит к конфликтам?
5. Чем определяется максимальное значение виртуального
адресного пространства?
6. Что такое образ процесса?
7. В каких случаях выполняются переходы от прикладного кода
к кодам ОС при выполнении процесса?
8. Какая часть виртуального адресного пространства заменяется
при смене активного процесса?
9. Содержит ли виртуальное адресное пространство части?
10. Какие модули ОС размещаются в невытесняемой области системной части виртуальной памяти?
11. Перечислите функции, выполняемые ОС, при распределении
памяти динамическими разделами.
12. Какие задачи решаются при виртуализации оперативной
памяти?
13. Какие задачи выполняет подсистема управления памятью
при распределении фиксированными разделами?
14. На чем основан механизм виртуальной памяти?
15. Образуют ли сегменты ОС активного процесса единое виртуальное адресное пространство?
16. Кто назначает виртуальные и физические адреса?
17. Является ли часть виртуального адресного пространства процесса, отводимая под сегменты ОС, единой для всех процессов?
18. Какая информация из таблицы страниц устанавливается аппаратными схемами процессора?
19. Куда загружается адрес таблицы страниц процесса при его
активизации?
20. Перечислите этапы страничного прерывания?
21. Что происходит после того как выбрана страница, которая
должна покинуть ОЗУ?
22. В каком случае происходит страничное прерывание?
23. Какая информация содержится в дескрипторе страниц?
57
24. Что такое базовый адрес?
25. Какие сведения содержатся в таблице сегментов?
26. Какие недостатки у сегментного распределения памяти?
27. У каждого процесса своя таблица страниц?
28. Что содержится в таблице сегментов при сегментно-страничном распределении памяти?
29. Что позволяет наличие базового виртуального адреса сегмента?
30. Являются ли библиотеки процедур частью системы ввода-вывода?
31. Какие средства обеспечения ввода-вывода есть в пространстве пользователя?
32. Могут ли сегменты ОС разделять виртуальное адресное пространство с прикладными сегментами активного процесса?
33. Чем определяется максимальный размер сегмента?
34. На что указывает вектор прерываний?
35. Совпадают ли виртуальные и физические адреса процесса по
объему и содержанию?
36. Подвержены ли коды ядра ОС страничному вытеснению?
37. От чего зависит вероятность обнаружения данных в КЭШе?
38. При каких условиях происходит страничное прерывание?
39. Как соотносятся размеры виртуального и физического адресного пространства?
40. Назовите функции ОС по управлению памятью?
41. Чем определяется максимальный размер виртуального
адресного пространства?
42. Чем определяется максимальный размер физического адресного пространства?
43. В чем разница между виртуальной памятью и свопингом?
44. Перечислите известные вам типы адресов.
58
2. UNIX-подобные операционные системы
Взаимодействие ОС UNIX с аппаратурой и пользовательскими программами удобно рассматривать как пирамиду (рис. 15). На
нижнем уровне располагается аппаратное обеспечение, состоящее
из центрального процессора, памяти, дисков и других устройств.
Операционная система UNIX управляет аппаратным обеспечением и предоставляет программам интерфейс системных вызовов.
Эти системные вызовы позволяют программам создавать процессы,
файлы и прочие ресурсы, а также управлять ими.
Программы обращаются к системным вызовам, помещая аргументы в регистры центрального процессора (или иногда в стек) и
выполняя команду эмулированного прерывания для переключения из пользовательского режима в режим ядра и передачи управления ОС UNIX. Поскольку на языке высокого уровня невозможно написать программу эмулированного прерывания, этим занимаются библиотечные функции, по одной на системный вызов.
Эти процедуры написаны на ассемблере, но они могут вызываться
из программ, написанных на С. Каждая такая процедура помещает аргументы в нужное место и выполняет команду эмулированного прерывания TRAP. Таким образом, чтобы обратиться к системному вызову read, программа, написанная например, на языке
С++, должна вызвать библиотечную процедуру read. Важную роль
в UNIX-подобных ОС играет стандарт P1003 (Portable Operating
System-4), который часто называют POSIX. Этот стандарт по существу определяет интерфейс переносимой ОС. Другими словами,
POSIX объединяет библиотечные процедуры, соответствующие
Прикладные
программы
пользователей
Обслуживающие программы
Стандартная оболочка
ОС UNIX
Аппаратное обеспечение
Рис. 15
59
системным вызовам, их параметры, а также указывает, что они
должны делать и какой результат возвращать. В этом стандарте не
упоминаются сами системные вызовы, но даются правила их описания и использования. Помимо ОС и библиотеки системных вызовов,
все версии UNIX содержат большое количество стандартных программ, некоторые из них описываются стандартом POSIX 1003.2,
тогда как другие могут различаться в разных версиях систем UNIX.
К таким программам относятся командный процессор (оболочка),
компиляторы, редакторы, программы обработки текста и утилиты для работы с файлами. Именно эти программы и запускаются
пользователем с терминала. Таким образом, в UNIX-подобной ОС
имеется три интерфейса: интерфейс системных вызовов, интерфейс
библиотечных функций и интерфейс, образованный набором стандартных обслуживающих программ.
2.1. Структура ядра операционной системы UNIX
Упрощенная структура, дающая представление о составе ядра
UNIX-подобной ОС приведена на рис. 16 .
Нижний уровень ядра состоит из драйверов устройств и процедуры диспетчеризации процессов. Все драйверы системы UNIX делятся на два класса: драйверы символьных устройств и драйверы
блочных устройств. Основное различие между этими двумя классами устройств заключается в том, что на блочных устройствах разрешается операция поиска, а на символьных – нет. Технически сетевые устройства представляют собой символьные устройства, но они
обрабатываются иначе, поэтому на схеме они выделены отдельно.
Диспетчеризация процессов производится при возникновении прерывания. При этом низкоуровневая программа останавливает выполнение работающего процесса, сохраняет его состояние в таблице
процессов ядра и запускает соответствующий драйвер. Кроме того,
диспетчеризация процессов производится также, когда ядро завершает свою работу и пора снова запустить процесс пользователя. Программа диспетчеризации процессов написана на ассемблере и представляет собой отдельную от процедуры планирования программу.
Уровень выше сетевых драйверов выполняет в том чиcле и функции поддержки стека TCP/IP. Большинство систем UNIX содержат
в своем ядре модули этого стека, что дает возможность организовать
программную поддержку статической маршрутизации. Над уровнем стека ТСP/IP расположены модули, реализующие прикладные
сетевые протоколы и системы, такие, как FTP, Telnet, SNMP, DNS,
60
Символьные
устройства
Виртуальная
память
Буферный
КЭШ
Страничный
КЭШ
Драйверы дисковых
устройств
Создание и завершение
процессов
Страничные
прерывания
Отображение
процессов
Сокеты
Модули
сетевых
протоколов
Дисциплины
каналов
связи
Файловая
система
Стек
TCP/IP
Обработанный
телетайп
Именование
файлов
Драйверы
сетевых
устройств
Телетайп
Упраление
терминалом
Обработчик сигналов
Аппаратные и
эмулированные
прерывания
Системные вызовы
Планировщик
процессов
Диспетчеризация
процессов
Аппаратура
Рис. 16
NFS, rlogin и т. п. Над сетевыми прикладными протоколами располагается интерфейс сокетов, позволяющий программам создавать
«почтовые ящики» для сетей и протоколов по определенным правилам. Для использования сокетов пользовательские программы
должны установить и описать параметры, которые их «интересуют» на низлежащих уровнях. Над дисковыми драйверами располагаются буферный кэш и страничный кэш файловой системы. Над
буферным кэшэм располагаются файловые системы. Большинством систем unix поддерживаются несколько файловых систем,
включая быструю файловую систему Беркли, журнальную файловую систему, а также различные виды файловых систем System V.
Все эти файловые системы совместно используют общий буферный кэш. Выше файловых систем помещается именование файлов, управление каталогами, управление жесткими и символьными
61
связями, а также другие средства файловой системы, одинаковые
для всех файловых систем. Над страничным кэшэм располагается система виртуальной памяти. В ней содержится вся логика работы со страницами, например алгоритм замещения страниц. Поверх
него находится программа отображения файлов на виртуальную
память и высокоуровневая программа управления страничными
прерываниями. Эта программа решает, что нужно делать при возникновении страничного прерывания. Сначала она проверяет допустимость обращения к памяти и, если все в порядке, определяет местонахождение требуемой страницы и то, как она может быть
получена. Последний столбец имеет отношение к управлению процессами. Над диспетчером располагается планировщик процессов,
выбирающий процесс, который должен быть запущен следующим.
Если потоками управляет ядро, то управление потоками также помещается здесь, хотя в некоторых системах UNIX управление потоками вынесено в пространство пользователя. Над планировщиком расположена программа для обработки сигналов и отправки их
в требуемом направлении, а также программа, занимающаяся созданием и завершением процессов. Верхний уровень представляет
собой интерфейс системы. Слева располагается интерфейс системных вызовов. Все системные вызовы поступают сюда и направляются одному из модулей низших уровней в зависимости от природы
системного вызова. Правая часть верхнего уровня представляет собой вход для аппаратных и эмулированных прерываний, включая
сигналы, страничные прерывания, разнообразные исключительные ситуации процессора и прерывания ввода-вывода.
2.2. Загрузка UNIX
Детали процесса загрузки UNIX-подобной ОС варьируются от системы к системе. Но в общих чертах этот процесс выглядит так. Когда компьютер включается, в память считывается и исполняется первый сектор (главная загрузочная запись) загружаемого диска. Этот
сектор содержит небольшую (512-байтовую) программу, загружающую автономную программу под названием boot с загрузочного
устройства, как правило, с жесткого диска. Программа boot сначала
копирует саму себя в фиксированный адрес памяти в старших адресах, чтобы освободить нижнюю память для ОС. Загрузившись, программа boot считывает корневой каталог с загрузочного устройства.
Чтобы сделать это, она должна понимать формат файловой системы
и каталога. Затем она считывает ядро ОС и передает ему управление.
62
На этом программа boot завершает свою работу, после чего уже работает ядро системы. Начальная программа ядра написана на ассемблере и является в значительной мере машинно-зависимой. Как правило, эта программа устанавливает указатель стека, определяет тип
центрального процессора, вычисляет количество имеющегося в наличии ОЗУ, устанавливает запрет прерываний, разрешение работы
диспетчера памяти, наконец, вызывает процедуру main, написанную на языке С, чтобы запустить основную часть ОС.
Программа на языке С также должна проделать значительную
работу по инициализации, но эта инициализация скорее логическая, нежели физическая. Она начинается с того, что выделяется
память под буфер сообщений, что должно помочь решению проблем
с загрузкой системы. По мере выполнения инициализации в этот
буфер записываются сообщения, информирующие о том, что происходит в системе. В случае неудачной загрузки их можно прочитать
с помощью специальной программы. Затем выделяется память для
структур данных ядра. Большинство этих структур имеют фиксированный размер, но размер некоторых из них, например, размер
буферного кэша и некоторых структур таблиц управления страницами памяти, зависит от доступного объема оперативной памяти.
Затем ОС начинает определение конфигурации компьютера. Операционная система считывает файлы конфигурации, в которых сообщается, какие типы устройств ввода-вывода могут присутствовать,
и проверяет, какие из устройств действительно присутствуют. Если
проверяемое устройство отвечает, оно добавляется к таблице подключенных устройств. Если устройство не отвечает, оно считается
отсутствующим и в дальнейшем игнорируется. Как только список
устройств определен, ОС должна найти драйверы устройств. На этом
этапе различные версии UNIX могут вести себя по-разному. В частности, система 4.4BSD не может динамически загружать драйверы
устройств, поэтому любое устройство ввода-вывода, чей драйвер не
был статически скомпонован с ядром, не может использоваться. Некоторые другие версии UNIX, как, например, Linux, напротив, могут динамически загружать драйверы (как это могут делать все версии Windows).
Главный аргумент в пользу динамической загрузки заключается в том, что клиентам с различными конфигурациями может быть
поставлен один и тот же двоичный файл, который автоматически
загрузит необходимые ему драйверы. Если драйверы не могут загружаться динамически, такой сценарий предотвращает установку в ядро не отлаженной или вредоносной программы. Более того,
63
в больших системах конфигурация аппаратуры точно известна уже
во время компиляции и компоновки ОС. Изменения производятся
довольно редко, поэтому перекомпоновка системы при добавлении
нового устройства не представляет проблемы.
Когда загружающаяся ОС определила конфигурацию аппаратного обеспечения, она должна загрузить процесс 0, установить его
стек и запустить этот процесс. Процесс 0 продолжает инициализацию, выполняя такие задачи, как программирование таймера реального времени, монтирование корневой файловой системы и создание процесса 1 (init) и страничного демона (процесс 2). Процесс
init проверяет свои флаги, в зависимости от которых он запускает
ОС либо в однопользовательском, либо в многопользовательском режиме. В первом случае он создает процесс, выполняющий оболочку,
и ждет, когда тот завершит свою работу. Во втором случае процесс
init создает процесс, исполняющий сценарий оболочки инициализации системы, который может выполнять проверку непротиворечивости файловой системы, монтировать дополнительные файловые системы, запускать демонов и т. д. Затем он считывает специальный файл, в котором перечисляются терминалы и некоторые их
свойства. Для каждого разрешенного терминала он создает копию
самого себя, которая затем исполняет программу getty. Программа getty устанавливает для каждой линии (некоторые из них могут
быть, например, модемами) скорость линии, после чего выводит на
терминале приглашение к входу в систему:
login:
После этого программа getty пытается прочитать имя пользователя, введенное с клавиатуры. Когда пользователь садится за терминал и вводит свое имя, программа getty завершает свою работу выполнением программы регистрации. После этого программа
login запрашивает у пользователя его пароль, зашифровывает его и
сравнивает с зашифрованным паролем, хранящимся в файле паролей /etc/passwd. Если пароль введен верно, программа login вместо
себя запускает оболочку пользователя, которая ждет первой команды. Если пароль введен неверно, программа login просто еще раз
спрашивает имя пользователя.
2.3. Оболочка UNIX
У многих версий системы UNIX имеется графический интерфейс
пользователя, схожий с популярными интерфейсами, примененными на компьютере Macintosh и впоследствии в системе Windows.
64
Однако многие программисты до сих пор предпочитают интерфейс
командной строки, называемый оболочкой (shell). Подобный интерфейс значительно быстрее в использовании, существенно мощнее, проще расширяется. Хотя система ОС Linux полностью поддерживает графическое окружение (X Windows), часто программисты
создают несколько консольных окон и действуют так, как если бы
у них был десяток алфавитно-цифровых терминалов, на каждом из
которых запущена оболочка. Когда оболочка запускается, она инициализируется, а затем печатает на экране символ приглашения
к вводу (обычно это знак доллара или процента) и ждет, когда пользователь введет командную строку. После этого, оболочка извлекает из нее команду и анализирует ее. Если ошибки нет, то команда
выполняется (например, выполняется программа), а затем опять
следует приглашение к работе. Здесь важно подчеркнуть, что оболочка представляет собой обычную пользовательскую программу.
Все, что ей нужно, – это способность ввода с терминала и вывода
на терминал, а также возможность запускать другие программы.
У команд оболочки могут быть аргументы, которые передаются запускаемой программе в виде текстовых строк. Например, командная
строка ср sor dst запускает программу ср с двумя аргументами sor и
dst. Эта программа интерпретирует первый аргумент как имя существующего файла. Она копирует этот файл и называет его копию dst.
Программа вроде оболочки не должна открывать терминал, чтобы прочитать с него или вывести на него строку. Вместо этого запускаемые программы автоматически получают доступ к файлу,
называемому стандартным устройством ввода (standard input), и
к файлу, называемому стандартным устройством вывода (standard
output), а также к файлу, называемому standard error (стандартное
устройство для вывода сообщений об ошибках). По умолчанию всем
трем устройствам соответствует терминал, т. е. клавиатура для ввода и экран для вывода. Многие программы в системах UNIX читают
данные со стандартного устройства ввода и пишут на стандартное
устройство вывода.
Стандартные ввод и вывод также можно перенаправить, что является очень полезным свойством. Для этого используются символы «<» и «>» соответственно.
Разрешается их одновременное использование в одной командной строке. Например, команда srt <in >out заставляет программу
srt взять в качестве входного файл in и направить вывод в файл out.
Поскольку стандартный вывод сообщений об ошибках не был перенаправлен, все сообщения об ошибках будут печататься на экране.
65
2.4. Процессы в системе UNIX
Каждый процесс в UNIX запускает одну программу и изначально
получает один поток управления. Другими словами, у процесса есть
счетчик команд, указывающий на следующую исполняемую команду процессора. Большинство версий UNIX позволяют процессу после того, как он запущен, создавать дополнительные потоки. UNIX
представляет собой многозадачную систему, так что несколько независимых процессов могут работать квази – одновременно. У каждого пользователя может быть одновременно несколько активных
процессов, так что в большой системе могут квази – одновременно,
работать сотни и более процессов. Действительно, на большинстве
однопользовательских рабочих станций, даже когда пользователь
куда-либо отлучается, работают десятки фоновых процессов, называемых демонами. Они запускаются автоматически при загрузке системы. Типичным демоном является cron daemon. Он просыпается раз в минуту, проверяя, не нужно ли что-либо сделать. Если
у него есть работа, он ее выполняет и отправляется спать дальше.
Этот демон позволяет планировать в системе UNIX активность на
минуты, часы, дни и даже месяцы вперед. Другие демоны управляют входящей и исходящей электронной почтой, очередями на
принтер, проверяют, достаточно ли еще осталось свободных страниц памяти и т. д. Демоны реализуются в системе UNIX довольно просто, так как каждый из них представляет собой отдельный
процесс, независимый от всех остальных процессов. Процессы создаются в ОС UNIX чрезвычайно несложно. Системный вызов fork
создает точную копию исходного процесса, называемого родительским процессом. Новый процесс называется дочерним процессом.
У родительского и у дочернего процессов есть свои собственные образы памяти. Если родительский процесс впоследствии изменяет какие-либо свои переменные, изменения остаются невидимыми
для дочернего процесса, и наоборот. Открытые файлы совместно используются родительским и дочерним процессами. Это значит, что
если какой-либо файл был открыт до выполнения системного вызова fork, он останется открытым в обоих процессах и в дальнейшем.
Изменения, произведенные с этим файлом, будут видимы каждому процессу. Такое поведение является единственно разумным,
так как эти изменения будут также видны любому другому процессу, который тоже откроет этот файл. Тот факт, что образы памяти,
переменные, регистры и все остальное у родительского процесса и
у дочернего идентично, приводит к затруднению. Как процессам уз66
нать, который из них должен исполнять родительскую программу,
а который – дочернюю? Эта проблема решается просто: системный
вызов fork возвращает дочернему процессу число 0, а родительскому – отличный от нуля PID (Process IDentifier – идентификатор
процесса) дочернего процесса. Таким образом, оба процесса могут
проверить возвращаемое значение и действовать, например, так,
как это реализовано в приведенном листинге программы создания
процесса.
рid = fork( ); /* если fork завершился успешно, pid > 0 в родительском процессе*/
if pid < 0) {
handle _ егrог(); /* fork потерпел неудачу (например, память
или какая-либо таблица переполнена) */
} е1se if (рid > 0) {
/* здесь располагается родительская программа. */
} е1se {
/* здесь располагается дочерняя программа. */
}
Процессы распознаются по своим PID-идентификаторам. При
создании процесса его РID выдается родителю нового процесса. Если дочерний процесс желает узнать свой РID, он может воспользоваться системным вызовом getpid. Идентификаторы процессов используются различным образом. Например, когда дочерний процесс завершается, его РID также выдается его родителю. Это может
быть важно, так как у родительского процесса может быть много
дочерних процессов. Поскольку у дочерних процессов также могут
быть дочерние процессы, исходный процесс может создать целое дерево родственников. В системе UNIХ процессы могут общаться друг
с другом с помощью разновидности обмена сообщениями. Можно
создать канал между двумя процессами, в который один процесс
может писать поток байтов, а другой процесс может его читать. Эти
каналы иногда называют трубами. Синхронизация процессов достигается путем блокирования процесса при попытке прочитать
данные из пустого канала. Когда данные появляются в канале,
процесс разблокируется. Когда оболочка читает, например, строку
srt < f | head. Она создает два процесса, srt и head, а также устанавливает между ними канал таким образом, что стандартный поток вывода программы srt соединялся со стандартным потоком ввода программы head. При этом все данные, формируемые программой srt, попадают напрямую программе head, для чего не требуется
временного файла. Если канал переполняется, система приостанав67
ливает работу программы srt, пока программа head не удалит из
него хоть сколько-нибудь данных. Процессы также могут общаться другим способом: при помощи программных прерываний. Один
процесс может послать другому так называемый сигнал. Процессы
могут сообщить системе, какие действия следует предпринимать,
когда придет сигнал. У процесса есть выбор: проигнорировать сигнал, перехватить его или позволить сигналу уничтожить процесс
(действие по умолчанию для большинства сигналов). Если процесс
выбрал перехват посылаемых ему сигналов, он должен указать процедуры обработки сигналов. Когда сигнал прибывает, управление
передается обработчику. Когда процедура обработки сигнала завершает свою работу, управление снова возвращается в то место процесса, в котором она находилась, когда пришел сигнал. Обработка
сигналов аналогична обработке аппаратных прерываний ввода-вывода. Процесс может посылать сигналы только членам его группы
процессов. Процесс может послать сигнал сразу всей группе за один
системный вызов.
2.5. Управление процессами в UNIХ
У каждого процесса есть пользовательская часть, в которой работает программа пользователя. Однако когда один из потоков обращается к системному вызову, происходит эмулированное прерывание с переключением в режим ядра. После этого поток начинает
работу в контексте ядра, с отличной картой памяти и полным доступом к ресурсам компьютера. С этого момента у него есть свой стек и
счетчик команд. Это важно, так как системный вызов может блокироваться на полпути, например, ожидая завершения дисковой операции. При этом счетчик команд и регистры сохраняются таким образом, чтобы поток можно было восстановить в режиме ядра.
Ядро поддерживает две структуры данных, обеспечивающих
их выполнение: это – таблица процессов и структура пользователя.
Таблица процессов располагается в ОЗУ резидентно и обеспечивает переключение с процесса на процесс. В этой таблице содержится
информация обо всех процессах, в том числе и тех, которых в данный момент нет в ОЗУ. Структура пользователя загружается в ОЗУ,
лишь в том случае, если процесс выполняется.
Информация в таблице процессов может быть разделена на следующие категории.
Параметры планирования. Они нужны для выбора процесса, которому будет передано управление, и содержат информацию
68
о использованном процессорном времени и времени, проведенном
в ожидании.
Образ памяти. Это – указатели на сегменты программы, данных
и стека, и соответствующие таблицы. Если процесса в данный момент нет в памяти, то здесь содержится информация о том, как его
найти на диске.
Сигналы. Это – указатели на то, какие сигналы заблокированы,
перехватываются и игнорируются, а также находятся в процессе
доставки.
Дополнительная информация. Это – PID процесса и его родителя, идентификаторы пользователя и группы, текущее состояние и
ожидаемые процессом события.
Хотя процессу, выгруженному на диск, можно послать сигнал,
такой процесс не может читать файл, поэтому информация о сигналах содержится в таблице процессов резидентно, даже если процесс не выполняется. В то же время описатели файлов находятся
в структуре пользователя и загружаются в ОЗУ вместе с процессом.
Данные, хранящиеся в структуре пользователя, содержат следующую информацию.
1. Регистры процессора. Cодержимое этих регистров сохраняется
здесь, если происходит прерывание с переходом в режим ядра.
2. Состояние системного вызова. Это – информация о текущем
системном вызове, включая параметры и результат.
3. Таблица дискрипторов файлов. Эта таблица используется в качестве индекса для определения структуры данных, соответствующей файлу, с которым работает системный вызов.
4. Учетная информация. Указатель на таблицу, учитывающую
процессорное время, использованное процессом в пользовательском
и системном режимах. В некоторых системах здесь также ограничивается процессорное время, которое может использовать процесс,
максимальный размер стека, количество страниц памяти и т. д.
5. Стек ядра. Фиксированный стек для использования процессом в режиме ядра.
Когда выполняется системный вызов fork, вызывающий процесс
обращается в ядро и ищет свободную ячейку в таблице процессов,
в которую можно записать данные о дочернем процессе. Если свободная ячейка находится, системный вызов копирует туда информацию из ячейки родительского процесса. Затем он выделяет память для сегментов данных и для стека дочернего процесса, куда
копируются соответствующие сегменты родительского процесса.
Структура пользователя (которая часто хранится вместе с сегмен69
том стека) копируется вместе со стеком. Программный сегмент может либо копироваться, либо использоваться совместно, если он
доступен только для чтения. Начиная с этого момента, дочерний
процесс может быть запущен. Когда пользователь вводит с терминала команду, например ls, оболочка создает новый процесс, клонируя свой собственный процесс с помощью системного вызова fork.
Новый процесс оболочки затем вызывает системный вызов ехес,
чтобы считать в свою область памяти содержимое исполняемого
файла ls. Механизм создания нового процесса довольно прост. Для
дочернего процесса создается новая ячейка в таблице процессов, которая заполняется из соответствующей ячейки родительского процесса. Дочерний процесс получает PID, затем настраивается его
карта памяти. Кроме того, дочернему процессу предоставляется совместный доступ к файлам родительского процесса. Затем настраиваются регистры дочернего процесса, после чего он готов к запуску.
Семантика системного вызова fork требует, чтобы никакая область
памяти не использовалась совместно родительскими и дочерними
процессами. Дочернему процессу выделяются новые таблицы страниц, но эти таблицы указывают на страницы родительского процесса, помеченные как доступные только для чтения. Когда дочерний
процесс пытается писать в такую страницу, происходит прерывание. При этом ядро выделяет дочернему процессу новую копию этой
страницы, к которой этот процесс получает также и доступ записи. Таким образом, копируются только те страницы, в которые дочерний процесс пишет новые данные. Такой механизм называется
копированием при записи. При этом экономится память, так как
страницы с программой не копируются. После того как дочерний
процесс начинает работу, его программа (копия оболочки) выполняет системный вызов ехес, задавая имя команды в качестве параметра. При этом ядро находит и проверяет исполняемый файл, копирует в ядро аргументы и строки окружения, а также освобождает
старое адресное пространство и его таблицы страниц.
2.6. Системные вызовы в UNIХ
Рассмотрим теперь системные вызовы UNIX, предназначенные
для управления процессами. Обсуждение системных вызовов проще всего начать с системного вызова fork. Этот системный вызов
представляет собой единственный способ создания новых процессов
в системах UNIX. Он создает точную копию оригинального процесса, включая все описатели файлов, регистры и все остальное. После
70
выполнения системного вызова fork исходный процесс и его копия
(родительский процесс и дочерний) идут каждый своим путем. Сразу после выполнения системного вызова fork: значение всех соответствующих переменных в обоих процессах одинаково, но затем
изменение переменных в одном процессе не влияет на переменные
другого процесса. Системный вызов fоrk возвращает значение, равное нулю, для дочернего процесса и идентификатор (PID) дочернего
процесса – для родительского. Таким образом, два процесса могут
определить, кто из них родитель, а кто – дочерний процесс
Системные вызовы, относящиеся к процессам
Системный вызов. Описание.
рid=fork() Создать дочерний процесс, идентичный родительскому
рid=waitpid(рid, &statloc, орts) Ждать завершения дочернего
процесса
s=ехесve(name, argv, envp) Заменить образ памяти процесса
ехit(status) Завершить выполнение процесса и вернуть статус
s=sigastion(sig, &set, &old) Определить действие, выполняемое
при приходе сигнала
s=sigreturn(&context) Вернуть управление после обработки сигнала
s=sigprocmask(how, &set, &old) Исследовать или изменить маску сигнала
s=sigpending(set) Получить или установить блокированные сигналы
s=kill(pid, sig) Послать сигнал процессу
s=sigsuspend(sigmask) Заменить маску сигнала и приостановить
процесс
residual=alarm(seconds) Установить будильник
s=pause() Приостановить выполнение процесса до следующего
сигнала
В большинстве случаев после системного вызова fork дочернему процессу потребуется выполнить программу, отличающуюся от
программы, выполняемой родительским процессом. Рассмотрим
работу оболочки. Она считывает команды с терминала, выполняет
введенную команду, затем ждет окончания работы дочернего процесса, после чего считывает следующую команду. Для ожидания
завершения дочернего процесса родительский процесс обращается
к системному вызову waitpid. У этого системного вызова три параметра. В первом параметре указывается PID процесса, завершение
которого ожидается. Если вместо идентификатора процесса указать
число –1, то в этом случае системный вызов ожидает завершения
71
любого дочернего процесса. Второй параметр представляет собой
адрес переменной, в которую записывается статус завершения дочернего процесса (нормальное или ненормальное завершение, а также возвращаемое на выходе значение). Третий параметр определяет, будет ли обращающийся к системному вызову waitpid процесс
блокирован до завершения дочернего процесса или сразу получит
управление после обращения к системному вызову. В случае оболочки дочерний процесс должен выполнить команду, введенную
пользователем. Он выполняет это при помощи системного вызова
ехес, который заменяет весь образ памяти содержимым файла, указанным в первом параметре системного вызова. Упрощенный вариант оболочки, иллюстрирующей использование системных вызовов
fork, waitpid и ехес, приведен ниже.
while (TRUE) { /* вечный цикл */
type _ promt( ); /* вывести приглашение ко вводу */
read command(command, params); /* прочитать с клавиатуры
строку */
рid = fork( ); /* ответвить дочерний процесс */
if (pid<0) {
рrint(«Создать процесс невозможно»); /* ошибка */
continue; /* следующий цикл */
}
If (pid != 0) {
Waitpid (-1, &status, 0); /* родительский процесс ждет завершения дочернего процесса */
} else {
execve(command, params, 0); /* дочерний процесс выполняет работу */
}
}
В общем случае у системного вызова три параметра: имя исполняемого файла, указатель на массив аргументов и указатель на массив строк окружения. Различные варианты системного вызова exec,
включая процедуры execl, execv, execle и execve, позволяют пропускать некоторые параметры или задавать их другими способами.
Все эти процедуры обращаются к вызову exec, процедуры с таким
именем нет.
Рассмотрим простой системный вызов ехit, который процессы
должны использовать, заканчивая исполнение. У него есть один параметр, статус выхода (от 0 до 255), возвращаемый родительскому
процессу в переменной status системного вызова waitpid. Младший
байт переменной status содержит статус завершения, равный 0 при
72
нормальном завершении или код ошибки при аварийном завершении. Например, если родительский процесс выполняет оператор
n = waitpid(-1, &status. 0); он будет приостановлен до тех пор,
пока не завершится какой-либо дочерний процесс. Если дочерний
процесс завершится со, скажем, значением статуса, равным 4, в качестве параметра библиотечной процедуры ехit, то родительский
процесс получит РID дочернего процесса и значение статуса, равное 0х0400 (0х означает в программах на языке С шестнадцатеричное число). Младший байт переменной status относится к сигналам,
старший байт представляет собой значение, задаваемое дочерним
процессом в виде параметра при обращении к системному вызову
ехit. Если процесс уже завершил свою работу, а родительский процесс не ожидает этого события, то дочерний процесс переводится
в так называемое состояние зомби, т. е. приостанавливается. Когда родительский процесс наконец обращается к библиотечной процедуре waitpid, дочерний процесс завершается. Несколько системных вызовов относятся к сигналам, используемым различными способами. Например, если пользователь по ошибке велит текстовому редактору отобразить содержание очень длинного файла, а затем осознает свою ошибку, то потребуется некоторый способ прервать работу
редактора. Обычно для этого пользователь нажимает специальную
клавишу (например, DEL или CTRL+С), в результате чего редактору
посылается сигнал. Редактор перехватывает сигнал и останавливает
вывод. Чтобы заявить о своем желании перехватить тот или иной сигнал, процесс может воспользоваться системным вызовом sigation.
Первый параметр этого системного вызова – сигнал, который требуется перехватить. Второй параметр представляет собой указатель на
структуру, в которой хранится указатель на процедуру обработки
сигнала вместе с различными битами и флагами. Третий параметр
указывает на структуру, в которой система возвращает информацию
о текущем обрабатываемом сигнале, на случай, если позднее его нужно будет восстановить. Обработчик сигнала может выполняться сколь
угодно долго. Однако на практике обработка сигналов не занимает
много времени. Когда процедура обработки сигнала завершает свою
работу, она возвращается к той точке, в которой ее прервали.
Системный вызов sigaction может также использоваться для
игнорирования сигнала или чтобы восстановить действие по умолчанию, заключающееся в уничтожении процесса. Нажатие на клавишу DEL, не является единственным способом послать сигнал.
Системный вызов kill позволяет процессу послать сигнал любому
родственному процессу. Выбор названия для данного системного
73
вызова (kill – убить, уничтожить) не особенно удачен, так как по
большей части он используется процессами не для уничтожения
других процессов, а наоборот, в надежде, что этот сигнал будет перехвачен и обработан соответствующим образом. Во многих приложениях реального времени бывает необходимо прервать процесс через определенный интервал времени, чтобы заставить его сделать
что-либо, например, переслать повторно возможно потерянный пакет по ненадежной линии связи. Для обработки данной ситуации
существует системный вызов а1аrm (будильник). Параметр этого системного вызова задает временной интервал, по истечении которого
процессу посылается сигнал SIGALARM.
2.7. Системные вызовы управления потоками
В первых версиях ОС UNIX потоков не было. Поэтому применяли различные пакеты поддержки потоков, однако, написать переносимую программу при этом стало почти невозможно. В конце концов, системные вызовы, используемые для управления потоками,
были стандартизированы в виде части стандарта POSIX (Р1003.1с).
В стандарте POSIX не указывается, должны ли потоки реализовываться в пространстве ядра или в пространстве пользователя. Преимущество потоков в пользовательском пространстве состоит в том,
что они легко реализуются без необходимости изменения ядра, в то
же время переключение потоков осуществляется эффективно. Недостаток потоков в пространстве пользователя заключается в том,
что если один из потоков заблокируется (например, на операции
ввода-вывода, семафоре или страничном прерывании), все потоки
процесса блокируются. Ядро полагает, что существует только один
поток, и не передает управление процессу потока, пока блокировка не снимется. Таким образом, системные вызовы, определенные
в стандарте Р1003.1с, были тщательно отобраны так, чтобы потоки
могли быть реализованы любым способом. До тех пор пока пользовательские программы четко придерживаются семантики стандарта Р1003.1с, оба способа реализации должны работать корректно.
Когда используется системная реализация потоков, они являются
настоящими системными вызовами. При использовании потоков на
уровне пользователя они полностью реализуются в динамической
библиотеке в пространстве пользователя. Например, системный вызов pthread_create создает новый поток. Обращение к этому системному вызову производится следующим образом: err = pthread _
create(&tid, attr, function, arg).
74
Этот вызов создает в текущем процессе новый поток, в котором
работает программа function, а arg передается этой программе в качестве параметра. Идентификатор нового потока хранится в памяти по адресу, на который указывает первый параметр. С помощью
параметра attr можно задавать для нового потока определенные
атрибуты, такие как приоритет планирования. После успешного
выполнения данного системного вызова в адресном пространстве
пользователя появляется на один поток больше. Поток, выполнивший свою работу и желающий прекратить свое существование, обращается к системному вызову рthread _ exit. Поток может подождать, пока не завершится процесс, обратившись к системному
вызову pthread _ join. Если ожидаемый поток уже завершил свою
работу, системный вызов pthread _ join выполняется мгновенно.
В противном случае обратившийся к нему поток блокируется. Синхронизация потоков может осуществляться при помощи мьютексов. Как правило, мьютекс охраняет какой-либо ресурс, например
буфер, совместно используемый двумя потоками, чтобы гарантировать, что только один поток в каждый момент времени имеет доступ к общему ресурсу, предполагается, что потоки блокируют (захватывают) мьютекс перед обращением к ресурсу и разблокируют
(отпускают) его, когда ресурс им более не нужен. До тех пор пока
потоки соблюдают данный протокол, состояния состязания можно избежать. Мьютексы подобны двоичным семафорам, т. е. семафорам, способным принимать только значения 0 и 1.. . Мьютексы
могут создаваться вызовом pthread _ mutex _ init и уничтожаться
при помощи вызова рthread _ mutex _ destгоу. Мьютекс может находиться в одном из двух состояний: блокированный и разблокированный. Поток может заблокировать мьютекс с помощью вызова
рthread _ mutex _ lосk. Если мьютекс уже заблокирован, то поток,
обратившийся к этому вызову, блокируется. Когда поток, захвативший мьютекс, выполнил свою работу в критической области,
он должен освободить мьютекс, обратившись к вызову рthread _
mutex _ unlock. Мьютексы предназначены для кратковременной
блокировки, например для защиты совместно используемой переменной. Они не предназначаются для долговременной синхронизации, например для ожидания, когда освободится накопитель на
ленте или диск. Для долговременной синхронизации применяются переменные состояния. Эти переменные создаются с помощью
вызова pthread _ count _ init и уничтожаются вызовом pthread _
count _ destroy. Переменные состояния используются следующим
образом: один поток ждет, когда переменная примет определенное
75
значение, а другой поток сигнализирует ему изменением этой переменной. Например, обнаружив, что нужный ему накопитель занят, поток может обратиться к вызову pthread _ count _ wait, задав
в качестве параметра адрес переменной, которую все потоки согласились связать с накопителем или диском. Когда поток, наконец,
освободит внешнее устройство, он обращается к вызову pthread _
count _ signal, чтобы изменить переменную состояния и тем самым
сообщить ожидающим потокам, что внешнее устройство свободно.
Если ни один поток в этот момент не ждет, когда освободится это
внешнее устройство, сигнал просто теряется. Другими словами, переменные состояния не считаются семафорами.
2.8. Сигналы
Сигналы, требуемые стандартом POSIХ, приведены ниже. В большинстве систем UNIX также имеются дополнительные сигналы, но
программы, использующие их, могут оказаться непереносимыми
на другие версии UNIX.
Сигналы, требуемые стандартом РOSIХ
Сигнал SIGАВRТ Причина.
Посылается, чтобы прервать процесс и создать дамп
памяти.
SIGАLRM Истекло время будильника.
SIGEРЕ Произошла ошибка при выполнении операции с пла
вающей точкой (например, деление на 0).
SIGHUР Модем повесил трубку на телефонной линии, ис
пользовавшейся процессом.
SIGILL Пользователь нажал клавишу DEL, чтобы прервать
процесс.
SIGQUIТ Пользователь нажал клавишу, требуя прекращения
работы процесса с созданием дампа памяти.
SIGKILL Посылается, чтобы уничтожить процесс (не может
игнорироваться или перехватываться).
SIGРIРЕ Процесс пишет в канал, из которого никто не читает
SIGSEGV Процесс обратился к неверному адресу памяти.
SIGTERM Вежливая просьба процессу завершить свою работу.
SIGUSR1 Может быть определено приложением.
SIGUSR2 Может быть определено приложением.
76
Иногда бывает необходимо прервать процесс на определенное
время, а затем заставить его сделать что-либо, например, повторно
отослать в сеть пакет, возможно, не дошедший до адресата. Для обработки подобных ситуаций имеется системный вызов alarm. Параметр этого системного вызова задает временной интервал, по истечении которого процессу посылается сигнал SIGALRM. У процесса в каждый момент времени может быть только один будильник
(alarm) c. Например, если процесс обращается к системному вызову
аlarm с параметром 10 с, а 5 с спустя снова обращается к нему с параметром 20 с, то он получит только один сигнал через 20 с после
второго системного вызова. Первый сигнал будет отменен вторым
обращением к системному вызову а1аrm. Если параметр системного
вызова а1агm равен нулю, то такое обращение отменяет любой сигнал будильника. Если сигнал будильника не перехватывается, то
действие по умолчанию заключается в уничтожении процесса.
2.9. Вопросы ко второму разделу
1. C какой концепцией тесно связано понятие сигналов? Приведите примеры использования сигналов.
2. Целесообразно ли забирать у процесса зомби память?
3. Почему процесс может послать сигнал только членам своей
группы?
4. Какие процессы обладают более высоким приоритетом демоны или интерактивные процессы?
5. Для чего в таблице процессов для каждого процесса хранится
PID родительского процесса?
6. Почему начальный загрузчик, находящийся в нулевом секторе диска, не используется напрямую для загрузки ОС?
7. Может ли страничное прерывание привести к завершению
процесса, его вызвавшего?
8. Как после выполнения системного вызова fork определить какой процесс является дочерним, а какой родительским?
9. Перечислите параметры системного вызова waitpid.
10. Перечислите преимущества и недостатки использования потоков.
11. Для чего предназначены мьютексы?
77
3. Задания и примеры
3.1. Описание некоторых команд
и системных вызовов linux
Команда top
Команда top показывает список работающих в данный момент
процессов и важную информацию о них, включая использование
ими памяти и процессора. Этот список интерактивно формируется
в реальном времени.
Основные поля вывода команды top:
Поле
PID
PPID
USER
GROUP
Описание
Идентификатор процесса.
Идентификатор родительского процесса.
Идентификатор пользователя, запустившего процесс.
Идентификатор группы, которой принадлежит
процесс.
S
Состояние процесса.
PR
Приоритет процесса.
N1
Относительный приоритет процесса
TIME+, TIME Количество процессорного времени, которое исполь
зует процесс с момента своего запуска.
VIRT
Полный объем виртуальной памяти, которую зани
мает процесс.
RES
Объем резидентной виртуальной памяти, которую за
нимает процесс.
SHR
Объем общей виртуальной памяти, которую ис
пользует процесс.
SWAP
Объем виртуальной памяти процесса, выгруженной
на диск.
%CPU
Процент использования общего процессорного вре
мени.
%MEM
Процент использования доступной физической па
мяти.
CMD
Команда, использованная для создания процесса.
78
Субкоманды, которые можно использовать в top:
КомандаОписание
Пробел
h
k
n
u
M
P
H
A
R
Немедленно обновить содержимое экрана.
Вывести справку о программе.
Уничтожить процесс. Программа запрашивает у вас
код процесса и сигнал, который будет ему послан.
Изменить число отображаемых процессов. Вам предлагается ввести число.
Сортировать по имени пользователя.
Сортировать по объёму используемой памяти.
Сортировать по загрузке процессора.
Переключить отображения потоков.
Включить режим отображения альтернативной панели top.
Изменить nice-фактор процесса. Программа запрашивает у вас код процесса и nice-фактор, который будет
ему присвоен.
Утилита Watch
Утилита, запускает и следит за программой через фиксированные интервалы времени. Если интервал не задан с помощью опции -n, то команда будет запускаться каждые 2 секунды. Завершить программу можно с помощью нажатия клавиш CTRL-C. Опция -d подсветит изменения в выводе.
Пример: watch -d ls -l – наблюдение за изменениями в директории.
Команда ps
Выдает информацию об активных процессах.
Командой ps обрабатываются следующие опции: КомандаОписание
-eВывести информацию обо всех процессах.
-d Вывести информацию обо всех процессах, кроме ли
деров групп.
-N
отрицание выбора.
-d
все процессы, кроме главных системных процессов
сеанса.
-l
Генерировать листинг в длинном формате.
-f
Генерировать полный листинг.
Пример: ps-e – вывести информацию о всех процессах.
79
3.2. Системные вызовы управления процессами
int execv (const char *FILENAME, char *const ARGV[])
Функция исполняет файл, заданный строкой FILENAME, как новое изображение процесса. Аргумент ARGV представляет собой мас-
сив строк, заканчивающихся нулем, которые используются как
аргумент argv функции main. Последним элементом этого массива должен быть нулевой указатель. Первый элемент массива – имя
файла программы. Среда нового изображения процесса берется из
переменной environ текущего изображения процесса.
exit(int status)
Функция exit() приводит к обычному завершению программы,
и величина status возвращается процессу-родителю.
pid _ t fork (void)
Функция создает новый процесс. Прототип функции определен в заголовочном файле <unistd.h>. Если операция выполнилась
успешно, то выполнение и родительского, и дочернего процессов
продолжается, начиная с команды, следующей за fork. В случае дочернего процесса fork возвращает значение 0, а в случае родительского процесса – идентификатор порожденного процесса.
pid _ t waitpid (pid _ t PID, int *STATUS-PTR, int OPTIONS)
Используется для запроса информации о состоянии дочернего
процесса, идентификатор которого равен PID. Обычно вызывающий
процесс приостанавливает выполнение до тех пор, пока дочерний
процесс не завершит выполнение. Аргумент PID может принимать
и другие значения. Значение –1 означает ожидание любого процесса, 0 – запрашивает информацию о процессах, находящихся в той же
группе процессов. Если информация о состоянии процесса доступна
немедленно, функция не ожидает окончания выполнения процесса.
Если запрошен статус нескольких процессов, то возвращается статус
случайного процесса. Аргумент OPTIONS является битовой маской,
значение которой может формироваться из следующих макросов:
WNOHANG Родительский процесс не должен ожидать завершения
дочернего.
WUNTRACED Запрашивается информация об остановленных и завершенныхпроцессах.
Информация о состоянии сохраняется в объекте, на который
указывает STATUS-PTR, если он не является нулевым указателем.
80
В случае нормального завершения функция возвращает идентификатор процесса, для которого запрашивается состояние, иначе
возвращается –1. Переменная errno может принимать следующие
значения:
EINTR Выполнение функции прервалось поступившим сигналом.
ECHILD Отсутствуют дочерние процессы или процесс, идентификатор которого передан в функции, не является дочерним.
EINVAL Неверное значение аргумента OPTIONS.
Функция sigaction определяет обработчик сигнала. Функция
определена в заголовочном файле <signal.h>.
int sigaction(int SIGNUM, const struct sigaction *ACTION,
struct sigaction *OLD _ ACTION)
Структура sigaction определяется следующим образом:
struct sigaction {
int sa _ flags;
sighandler _ t sa _ handler;
sigset _ t sa _ mask;
};
sa _ handler используется таким же образом, как и аргумент
ACTION в функции signal; sa _ mask задает список сигналов, кото-
рые должны быть заблокированы при выполнении обработчика;
sa _ flags задает значения флагов, которые влияют на поведение
сигнала.
Аргумент ACTION задает новый обработчик для сигнала, OLD _
ACTION используется для того, чтобы возвратить информацию об обработчике, ассоциированном с сигналом. Если OLD _ ACTION является нулевым указателям, информация о предыдущем обработчике
не возвращается. Если ACTION является нулевым указателем, обработчик сигнала не изменяется. Функция возвращает следующие
значения:
0 – в случае успеха;
–1 – в случае ошибки;
EINVAL – в случае неправильно заданного идентификатора сигнала.
Набор сигналов, который заблокирован в данный момент, называется «маской сигналов». Каждый процесс имеет свою собственную маску сигналов. При создании нового процесса, он наследует
маску сигналов родительского процесса. Для модификации маски
сигналов используется следующая функция:
int sigprocmask(int HOW, const sigset _ t *SET, sigset _ t *OLDSET)
81
Аргумент HOW определяет, каким образом изменяется маска сигналов и может принимать следующие значения:
SIG _ BLOCK – сигналы, задаваемые в наборе, блокируются – добавляются к уже существующей маске сигналов;
SIG _ UNBLOCK – сигналы, задаваемые в наборе, разблокируются – удаляются из уже существующей маски сигналов процесса;
SIG _ SETMASK устанавливает набор сигналов для процесса, старое
содержимое маски игнорируется.
Аргумент OLDSET используется для возврата старого содержимого маски сигналов процесса. Функция возвращает 0 в случае успеха
и –1 в противном случае. При возникновении ошибки переменная
errno может принимать следующее значение:
EINVAL SIGNUM содержит неправильный номер сигнала.
Для проверки того, обработчики каких сигналов активны в настоящий момент используется следующая функция:
int sigpending(sigset _ t *SET)
Функция возвращает информацию об активных в текущий момент сигналах. Если имеется заблокированный сигнал, поступивший процессу, то он также включается в маску сигналов. Возвращает 0 в случае успешного выполнения и –1 в случае ошибки.
Для посылки сигналов другим процессам может использоваться
функция kill. Она может использоваться для выполнения множества различных действий. Необходимость посылать сигналы другим процессам возникает, например, в следующих случаях:
– родительский процесс запускает порожденный для выполнения некоторой задачи, причем выполняющий неопределенный
цикл, и завершает его, когда в дальнейшем выполнении задачи нет
необходимости;
– процесс выполняется как один из процессов в группе и нуждается в прерывании или информировании других процессов при возникновении ошибки или какого-либо критического события;
– два процесса необходимо синхронизировать при одновременном выполнении.
int kill(pid _ t PID, int SIGNUM)
Функция kill посылает сигнал SIGNUM процессу или группе про-
цессов, задаваемому PID. PID может принимать следующие значения:
– > 0 – сигнал посылается процессу, идентификатор которого
равен PID;
– == 0 – сигнал посылается всем процессам, которые находятся
в той же группе процессов, что и процесс-отправитель;
82
– < -1 – сигнал посылается группе процессов, идентификатор
которой равен PID;
– == -1, если процесс является привилегированным, то сигнал
посылается всем процессам, кроме некоторых системных процессов. Иначе сигнал посылается всем процессам, владельцем которых
является текущий пользователь.
Если сигнал успешно послан, то kill возвращает 0, иначе сигнал
не посылается и функция возвращает –1. В случае неудачи в переменной errno возвращаются следующие коды:
EINVAL – аргумент SIGNUM содержит неправильный номер сигнала;
EPERM – отсутствуют привилегии на посылку сигнала процессу
или группе процессов;
ESCRH – аргумент PID не соответствует ни одному из существующих процессов или групп процессов.
Процесс может послать сигнал самому себе с помощью вызова
kill(getpid(), SIG), что равнозначно raise(SIG).
Наиболее безопасным способом ожидания сигнала является использование функции sigsuspend:
int sigsuspend(sigset _ t *SET)
Функция заменяет маску сигналов процесса и приостанавливает его работу до поступления одного из сигналов, который не заблокирован маской. Маска, задаваемая SET, действует только на время
ожидания, вызываемого функцией, после возвращения управления
процессу восстанавливается старая маска сигналов.
3.3. Компилятор GCC
Средствами, традиционно используемыми для создания программ для открытых ОС, являются инструменты разработчика
GNU. Одним из этих инструментов является компилятор GCC, эта
аббревиатура означает – GNU Compiler Collection. Файлы с исходными кодами программ, – это обычные текстовые файлы, и создавать их можно с помощью любого текстового редактора.
Создайте отдельный каталог hello. Это будет каталог нашего
первого проекта. В нём создайте текстовый файл hello.c со следующим текстом:
#include <stdio.h>
int main(void)
{
83
printf(«Hello world!\n»);
return(0);
}
Затем в консоли зайдите в каталог проекта. Наберите команду
gcc hello.c
Теперь посмотрите внимательно, что произошло. В каталоге появился новый файл a.out. Это и есть исполняемый файл. Запустим
его. Наберите в консоли:
./a.out
Программа должна запуститься, т. е. должен появиться текст:
Hello world!
Компилятор gcc по умолчанию присваивает всем созданным исполняемым файлам имя a.out. Если хотите назвать его по-другому,
нужно к команде на компиляцию добавить флаг -o и имя, которым
вы хотите его назвать. Давайте наберём такую команду:
gcc hello.c -o hello
Мы видим, что в каталоге появился исполняемый файл с названием hello. Запустим его.
./hello
3.4. Упражнения
Задание 1
Процессы и потоки. Ч. 1
Цель работы: практическое изучение процессов и потоков с помощью дескриптора процессов top в UNIX подобной ОС.
1. Загрузить ОС UBUNTU, выбрав соответствующий вариант загрузки в меню загрузчика.
2. Ввести имя пользователя studentN, где N – номер рабочего места в лаборатории, если считать по часовой стрелке.
3. На приглашение ввести пароль набрать 12345678.
4. После загрузки ОС в графическом режиме выбрать в меню Applications, Accessoires, а затем Terminal.
5. Ввести команду вызова справочной системы ОС man с указанием объекта справки, т. е. man top.
6. Ознакомиться с описанием дискриптора top.
7. Ввести команду top для ее запуска в режиме отображения основной панели.
8. Используя top, определить:
– общее количество процессов;
84
– активных;
– в состоянии ожидания;
– остановленных;
– zombie.
9. С помощью субкоманды А включить режим отображения альтернативной панели top.
10. Ввести субкоманду u и имя пользователя, процессы которого
следует отображать:
u studentN
11. Используя данные, отображаемые дескриптором, определить
для процессов пользователя top, bash и gnome-terminal их идентификаторы (PID) и идентификаторы процессов родителей (PPID).
12. Используя эти данные, определить:
– какие из этих процессов были созданы ОС, а какие – пользователем;
– какие из этих трех процессов являются родительскими, а какие – дочерними по отношению к друг к другу;
– cобытия (действия), которые приводили к созданию каждого
из этих процессов.
13. Включить полученные выводы в отчет.
14. В письменном виде ответить на следующие вопросы:
– Может ли выполняться программа на компьютере без операционной системы?
– Может ли в мультипрограммной среде в каждый данный момент времени выполняться более одного процесса?
Задание 2
Процессы и потоки. Ч. 2
1. Для процессов gnom-terminal, bash и top определить следующие параметры:
– приоритет;
– статус;
– размер резидентной памяти;
– процент использования процессора;
– размер разделяемой памяти процесса;
– размер свопируемой памяти;
– размер физической памяти, используемый исполняемым кодом;
– размер памяти, выделенной для данных и стека;
– количество неудачных обращений процесса к страницам вне
его адресного пространства;
85
– количество страниц, которые были модифицированы с тех пор,
когда они были последний раз записаны на диск.
2. Используя субкоманду, H – переключение отображения потоков, определить потоки, их PID, PPID и название, а также родственные связи и их возможную принадлежность к процессам пользователя.
3. Для выявленных потоков определить значения параметров,
приведенных в п. 1.
4. С помощью top определить процесс с PID, равным 1, и его свойства.
5. Включить в отчет ответы на следующие вопросы:
– что такое образ процесса;
– название, назначение и свойства процесса с идентификатором 1.
Задание 3
Управление процессами
1. Используя субкоманду Renice ( R ), изменить статус ( NI )
процесса init со значения 0 на значение -5.
2. Используя субкоманду Renice ( R ), изменить статус ( NI )
процесса khelper со значения -5 на значение -1.
3. Убедиться, что статус процессов init и khelper приобрел необходимые значения.
Уничтожение процессов
1. С помощью субкоманды k уничтожить процессы, созданные ОС:
– dbus-launch;
– mapping-daemon;
– ssh-agent.
2. Cделать выводы по результатам выполнения п. 1 работы
в письменном виде. В частности, определить возможен ли случай,
когда при уничтожении процесса уничтожаются и другие процессы. Объяснить это явление.
3. Определить в каких случаях уничтожение процесса может
приводить к зависанию ОС.
4. В письменном виде ответить на вопрос, в чем главное отличие
между процессами x-session-manage и gnome-screensav?
86
Задание 4
Организация памяти и ввода-вывода в ОС Linux
Цель работы: практическое изучение инструментов мониторинга памяти и ввода-вывода в ОС Linux.
1. Используя команду vmstat -n, определить:
– число процессов в состоянии готовности;
– число процессов в состоянии ожидания не по прерыванию;
– количество использованной виртуальной памяти;
– количество неиспользованной памяти;
– количество свопированной памяти с диска;
– количество свопируемой памяти на диск;
– число блоков, полученных с блочных устройств;
– число блоков, переданных на блочные устройства;
– число прерываний в секунду, включая прерывания по таймеру;
– число контекстных переключений в секунду;
– время исполнения пользовательского кода;
– время исполнения кода ядра;
– неиспользованное время;
– время ожидания ввода-вывода.
2. Используя команду vmstat -s, определить:
– общее количество оперативной памяти;
– количество используемой памяти;
– количество памяти, находящейся в активном состоянии;
– количество памяти, находящейся в неактивном состоянии;
– количество свободной оперативной памяти;
– количество оперативной памяти, выделенной для буферов;
– общее количество оперативной памяти для свопирования;
– использованная для свопирования оперативная память;
– число неприоритетных переключений пользовательских процессов;
– число приоритетных переключений пользовательских процессов;
– число системных переключений процессора;
– число неиспользованных переключений процессора;
– число переключений для ожидания ввода-вывода;
– число переключений по вектору прерываний;
– число переключений по программным прерываниям;
– число захваченных переключений процессора;
– число загруженных страниц;
– число выгруженных страниц;
87
– число вытесненных в своп-область страниц;
– число загруженных в своп-область страниц;
– количество прерываний;
– количество переключений контекстов процессов;
– время загрузки;
– число ветвлений процессов.
3. Используя команду free, определить:
– общее количество оперативной памяти;
– количество используемой памяти;
– количество свободной оперативной памяти;
– количество оперативной памяти, выделенной для буферов;
– общее количество оперативной памяти для свопирования.
4. Сравнив результаты, полученные в первом и третьем пунктах,
в письменном виде ответить на вопрос, отличаются ли значения общего количества, cвободной, используемой, буферной и свопируемой памяти при использовании команд vmstat -n и free, и если да,
то почему.
Задание 5
Moдули ядра ОС Linux
Цель работы: практическое изучение состава и назначения модулей ОС Linux.
1. Используя команду lsmod, определить:
– состав модулей ОС Uduntu;
– их размер;
– частоту использования;
– процессы, их использующие.
2. Определить модули, обеспечивающие графический режим работы ОС.
3. Используя команды sudo modinfo имя модуля, определить для
этих модулей:
– путь к папке, содержащий модуль с расширением .ko;
– идентификатор универсальной общественной лицензии;
– версию протокола BNEP;
– имя автора;
– код версии модуля.
88
Задание 6
Системные вызовы
Цель: получение практических навыков использования системных вызовов при написании программ.
Выполнение
1. Ознакомиться с листингом программы, приведенной в примерах 1 и 2, с используемыми там командами fork, execv, waitpid,
kill, fopen, fclose.
2. Скомпилировать программы и запустить программу StartProcess.
Ознакомиться с результатами выполнения вычислений числа pi.
3. С помощью команд watch и ps наблюдать динамику процессов,
порождаемых программой StartProcess. Пример возможного синтаксиса команды: watch -n 0,01 -d ps -C StartProcess,DummyProcess
-F -l f.
4. Получить файл, содержащий все изменения информации о состоянии процессов, запущенных программой StartProcess. Пример
возможного синтаксиса команды: while(true) do ps -C StartProcess,
DummyProcess -F -l >> log.txt; done.
5. С помощью команды ps определить и проанализировать изменения следующих параметров процессов, запускаемых программой
StartProcess:
– pid и ppid;
– статусы процессов;
– выделяемое планировщиком время ЦП для процессов;
– приоритеты процессов и поправки к ним (nice-фактор);
– размер образов процессов в памяти;
– адрес события, которого ожидает процесс; у активного процесса эта колонка пуста;
– истраченное процессами время ЦП;
– объем физической оперативной памяти, выделенной процессу.
6. Отметить полученные результаты по всем параметрам в отчете.
Задание 7
Сигналы
Цель работы: ознакомление с сигналами, используемыми в ОС
Linux, а также получение навыков их применения при написании
программ.
1. Ознакомиться с листингом программы, приведенной в примере 3, используемыми там функциями kill, getpid, getppid,
sigaction, sigfillset.
89
2. Скомпилировать и запустить программу signal.c. Ознакомиться с результатами ее выполнения и включить их в отчет.
3. Написать программу(ы), иллюстрирующую получение следующих типов сигналов процессом:
– SIGKILL – Этот сигнал приводит к немедленному завершению
процесса. Этот сигнал не может игнорировать процесс или установить для него новый обработчик.
– SIGTERM – Этот сигнал является запросом на завершение процесса.
– SIGCHLD – Система посылает этот сигнал процессу при завершении одного из его дочерних процессов.
– SIGHUP – Система посылает этот сигнал, когда происходит отключение от терминала.
– SIGINT – Система посылает этот сигнал, когда пользователь нажимает комбинацию клавиш Ctrl+C.
– SIGILL – Система посылает этот сигнал при попытке выполнить
недопустимую операцию.
– SIGFPE – Система посылает этот сигнал при попытке выполнить недопустимую операцию с плавающей точкой.
– SIGSEGV – Система посылает этот сигнал при выполнении программой недопустимого обращения к памяти.
– SIGPIPE – Система посылает этот сигнал при обращении программы к разрушенному потоку данных.
При написании программы можно воспользоваться функцией
raise(sig)
4. Пояснить действия, происходящие при приеме этих сигналов
процессом.
5. Включить полученные результаты в отчет.
Пример 1. Эта программа предназначена для практического изучения системных вызовов и некоторых возможностей файловой
системы в UNIX-подобных ОС.
При запуске она создает определенное количество дочерних процессов, каждый из которых вычисляет число Пи с заданной точностью.
При создании дочернего процесса также создается файл, в который дочерний процесс возможно запишет результат своей работы.
После этого программа ожидает 2 секунды, и затем происходит
уничтожение первой половины дочерних процессов.
Далее происходит ожидание завершения работы оставшейся половины дочерних процессов.
90
Когда все процессы уничтожены или завершили свою работу
успешно, программа завершает работу.
Рекомендуется ВНИМАТЕЛЬНО ознакомится со страницами
man для следующих функций:
– fork – создание дочернего процесса;
– execv – выполнение внешнего процесса внутри текущего;
– waitpid – ожидание завершения дочернего процесса;
– kill – запрос на завершение дочернего процесса;
– fopen – открывает файл;
– fclose – закрывает файл.
Операции с файлами и процессами используют низкоуровневые
функции, в то время как вывод на консоль осуществляется средствами стандартной библиотеки.
#include
#include
#include
#include
#include
#include
#include
#include
<sys/wait.h>
<assert.h>
<stdio.h>
<stdlib.h>
<unistd.h>
<string.h>
<vector>
<iostream>
// Данным макросом определяется количество создаваемых дочерних процессов.
#define NUM _ PROCESSES 10
int main (int argc, char* argv[])
{
// В данной переменной будет храниться идентификатор только
что созданного дочернего процесса.
pid _ t cpid;
// В данном массиве переменных будут сохранены все идентификаторы для созданных дочерних процессов.
std::vector<pid _ t> startedProcesses;
for (int i = 0; i < NUM _ PROCESSES; ++i)
{
 // После выполнения вызова fork() процесс будет раздвоен, начиная со следующего вызова выполнения в обоих ( старом и новом
) процессах.
 // Начнется следующей строкой, при этом разница будет только
в возвращенном fork() значении.
 cpid = fork();
91
 // Если результат выполнения fork равен -1, то это значит,
что мы находимся в родительском процессе и создать дочерний процесс не удалось.
 if (cpid == -1) { perror(«fork»); exit(EXIT _ FAILURE); }
 if (cpid == 0) {
  // Если результат выполнения fork равен 0, то это значит,
что мы находимся в только что созданном ДОЧЕРНЕМ процессе.
  // Далее – запустим выполнение внешнего процесса для вычисления числа Пи.
  // Формируем командную строку, с которой будет запущен
внешний процесс DummyProcess внутри нового дочернего процесса.
char szIterations[10];
char * newargv[] =
{
«./DummyProcess»,
szIterations,
NULL
};
  // Формируем количество итераций при вычислении Пи. У всех
процессов результат будет несколько отличаться.
sprintf(szIterations, «%d», 100000000 + i*1000000);
std::cout << «Starting process with « << szIterations << « iterations! « << std::endl;
strcpy(newargv[1], szIterations);
  // Выполняем внешний процесс внутри себя. Обратите внимание, в текущем директории должен быть
  // исполняемый файл DummyProcess, умеющий вычислять Пи!
execv(«./DummyProcess»,newargv);
 }
 else
 {
  // если результат выполнения fork больше 0, то это значит,
что мы находимся в родительском процессе и знаем
  // идентификатор только что созданного дочернего процесса.
  // сохраним идентификатор только что созданного дочернего
процесса для последующей работы с ним.
startedProcesses.push _ back(cpid);
  // Создадим файл, в который новый дочерний процесс запишет
результат своей работы в случае удачного завершения.
92
  // Сформируем имя файла из идентификатора дочернего процесса и расширения «.pi.text».
char fileName[128];
sprintf(fileName, «%d.pi.text», cpid);
FILE* file = fopen(fileName, «w+»);
  // Закроем только что созданный файл.
fclose(file);
 }
}
// Ожидаем 2 секунды.
std::cout << «Created all processes! Sleeping 2 seconds before
the massacre» << std::endl;
sleep(2);
// Принудительно уничтожаем первую половину только что созданных процессов, используя их идентификаторы.
for (int i = 0; i < NUM _ PROCESSES / 2; ++i)
{
 int killresult;
std::cout << «About to kill the child process!» << std::endl;
 killresult = kill(startedProcesses[i], SIGKILL);
 if (killresult)
  std::cout << «Failed to kill the process!» << std::endl;
 else
  std::cout << «Killed the process!» << std::endl;
}
// По очереди ожидаем завершения работы оставшейся половины
дочерних процессов, вычисляющих Пи.
for (int i = NUM _ PROCESSES / 2; i < NUM _ PROCESSES ; ++i)
{
 int status;
 waitpid(startedProcesses[i], &status, 0 );
}
// Выход. После выхода в половине файлов, созданных родительским процессом, будут содержатся результаты вычисления Пи
// дочерними процессами, для тех процессов, которым удалось
«дожить» до этого.
exit(EXIT _ SUCCESS);
}
93
Пример 2. Эта программа выполняет следующие действия.
Во-первых, она вычисляет число Пи методом Лейбница с количеством итераций, заданным как параметр командной строки.
Во-вторых, по окончании вычислений она открывает файл с именем, выведенным из идентификатора процесса, и записывает в него
результат.
Операции с файлами используют низкоуровневые функции,
в то время как ввод и вывод на консоль осуществляется средствами
стандартной библиотеки.
#include
#include
#include
#include
#include
#include
#include
#include
<iostream>
<iomanip>
<stdio.h>
<stdlib.h>
<sys/wait.h>
<assert.h>
<unistd.h>
<string.h>
int main(int argc, char* argv[])
{
std::cout << «Dummy Process Starting!» << std::endl;
int iterations;
if (argc == 1)
{
// В случае, если количество итераций не указано, запрашиваем
его у пользователя.
std::cout << «Computing PI with Leibnitz series. Please enter
number of iterations: « ;
std::cin >> iterations;
}
else if (argc == 2)
{
// Количество итераций для вычисления указано первым параметром командной строки.
iterations = atoi(argv[1]);
}
else
{
// Печать помощи и выход, если параметров командной строки
больше одного.
std::cout << «Usage: DummyProcess [numberOfIterations]» <<
std::endl;
exit(0);
94
}
long double ourPi=4.0;
bool addFlop=true;
unsigned int denom=3;
// Вычисление Пи методом Лейбница. Занимает процессорное
время!
for (unsigned int i=1;i<=iterations;++i)
{
 if (addFlop) {
  ourPi-=(4.0/denom);
  addFlop=false;
  denom+=2;
 } else {
  ourPi+=(4.0/denom);
  addFlop=true;
  denom+=2;
 }
}
// Вывод результата на консоль.
std::cout << std::setiosflags(std::ios::showpoint) << std::seti
osflags(std::ios::fixed) << std::setprecision(20);
std::cout << «Pi calculated with « << iterations << « iterations is: « << ourPi << std::endl;
// Пытаемся открыть файл, состоящий из идентификатора текущего (этого) процесса и расширения «.pi.text», для записи реультата.
// Данный файл должен быть создан родительских процессом!
// Формируем имя файла
pid _ t myPid = getpid();
char fileName[128];
sprintf(fileName, «%d.pi.text», myPid);
// Открываем файл.
FILE* file = fopen(fileName, «r+»);
if (file != NULL)
{
 // Файл удачно открыт. Осуществляем печать результата в файл.
95
 fprintf(file, «Pi calculated
%.20Lf»,iterations, ourPi);
with
%d
iterations
is:
// Закрываем файл.
 fclose(file);
}
return 0;
}
Пример 3. В этом примере представлена программа, функционирующая следующим образом: родительский процесс порождает
дочерний процесс и ждет пока дочерний завершит инициализацию.
Дочерний процесс сообщает родителю, когда он готов, посылая ему
сигнал SIGUSR1, используя функцию kill.
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
volatile sig _ atomic _ t usr _ interrupt = 0;
void
synch _ signal (int sig)
{
usr _ interrupt = 1;
}
/* Дочерний процесс выполняет эту функцию. */
void
child _ function (void)
{
 printf («дочерний процесс запущен, его pid= %d.\n»,
(int) getpid ());
kill (getppid (), SIGUSR1);
puts («дочерний процесс завершен»);
//exit (0);
}
int
main (void)
{
 struct sigaction usr _ action;
sigset _ t block _ mask;
pid _ t child _ id;
sigfillset (&block _ mask);
96
usr _ action.sa _ handler = synch _ signal;
usr _ action.sa _ mask = block _ mask;
usr _ action.sa _ flags = 0;
sigaction (SIGUSR1, &usr _ action, NULL);
/* Создание дочернего процесса. */
 child _ id = fork ();
if (child _ id == 0)
child _ function ();
while (!usr _ interrupt);
 puts («конец»);
return 0;
}
97
4. Основы TCP/IP
4.1. Стек протоколов TCP/IP
Стек означает, что это не просто набор протоколов, а комплект
взаимодействующих (вложенных друг в друга) модулей, находящихся в ядре ОС (см. рис. 16). TCP/IP – это сетевое программное обеспечение низкого уровня, в состав которого входят следущие модули:
– межсетевой протокол (Internet Protocol, IP), который обеспечивает транспортировку без дополнительной обработки данных
с одной машины на другую;
– межсетевой протокол управляющих сообщений (Internet Control Message Protocol, ICMP), который отвечает за различные виды
низкоуровневой поддержки протокола IP, включая сообщения об
ошибках, содействие в маршрутизации, подтверждение получения
сообщения;
– протокол преобразования адресов (Address Resolution Protocol, ARP), выполняющий трансляцию логических сетевых адресов
в аппаратные;
– протокол пользовательских дейтаграмм (User Datagram Protocol, UDP) и протокол управления передачей (Transmission Control
Protocol, TCP), которые обеспечивают пересылку данных из одной
программы в другую с помощью протокола IP. Протокол UDP обеспечивает транспортировку отдельных сообщений без проверки,
тогда как TCP более надежен и предполагает проверку установления соединения.
Со стеком TCP/IP тесно связаны другие сетевые протоколы, например, SNMP, Telnet, FTP и т. п.
TCP/IP предоставляет пользователям однородный интерфейс,
обеспечивающий взаимодействие с сетевыми аппаратными средствами различных типов. Этот протокол гарантирует возможность
обмена данными (взаимодействия) между системами, невзирая на
многочисленные различия, существующие между ними. TCP/IP,
кроме того, позволяет соединять на программном уровне отдельные
физические сети в более крупную и более гибкую логическую сеть.
Как различные компоненты и клиенты TCP/IP вписываются
в общую архитектурупоказано на рис. 17.
Протоколы каждого уровня строятся на основе тех, которые соответствуют более низкому уровню. Данные проходят вниз по стеку
протоколов на машине-отправителе, затем движутся по физической
сети и поднимаются вверх по стеку протоколов на машине-адресате.
98
Таблица 2
Стек протоколов TCP/IP
Название
Назначение
Канальный
Сетевые аппаратные средства и драйверы устройств
Сетевой
Базовые коммуникации, адресация и маршрутизация
Транспортный
Связь между программами в сети
Прикладной
Прикладные программы конечных пользователей
Прикладной
уровень
Транспортный
уровень
Сетевой
уровень
Канальный
уровень
arp
rlogin, talk, ftp
TCP
IP
SNMP, DNS...
..nNsnmpSNMP
ЫТЬЗ
UDP
traceroute
ICMP
ARP, драйверы устройств
Рис. 17
Например, прикладная программа, «думающая», что использует
только протокол UDP, на самом деле вызывает протоколы UDP, IP
и физические сети.
Каждое соединение машины с сетью называется сетевым интерфейсом. Машина, имеющая более одного интерфейса, может принимать данные по одному интерфейсу и передавать их по другому,
осуществляя таким образом пересылку данных между сетями. Эта
функция называется маршрутизацией, а машина, выполняющая
ее, – маршрутизатором, или шлюзом. Данные путешествуют по сети в форме пакетов, каждый из которых состоит из заголовка и полезной нагрузки. Заголовок содержит сведения о том, откуда прибыл пакет и куда он направляется. Заголовок, кроме того, может
включать контрольную сумму, информацию, характерную для конкретного протокола, и другие инструкции по обработке. Полезная
нагрузка – это данные, подлежащие пересылке. Когда пакет путешествует вниз по стеку протоколов, готовясь к отправке, каждый
протокол вводит в него свою собственную информацию заголовка.
99
UDP-пакет (108 байт)
IP-пакет (128 байт)
Заголовок Заголовок Заголовок Прикладные Завершитель
Ethernet
IP
UDP
данные
Ethernet
14 байт
20 байт
8 байт
100 байт
4 байт
Ethernet-кадр (146 байт)
Рис. 18
Законченный пакет одного протокола становится полезной нагрузкой пакета, генерируемого следующим протоколом. Эта операция
известна как инкапсуляция, или оформление. На принимающей
машине инкапсулированные кадры разворачиваются в обратном
порядке.
Например, UDP-пакет, передаваемый по сети Ethernet, содержит
три различных «обертки». В среде Ethernet он «заворачивается»
в простой заголовок, содержащий сведения об аппаратных адресах
источника и получателя, длине кадра и его контрольной сумме. Полезной нагрузкой Ethernet-кадра является IP-пакет. Полезная нагрузка IP-пакета – UDP-пакет, и, наконец, полезная нагрузка UDPпакета состоит собственно из передаваемых данных. Компоненты
типичного сетевого кадра показаны на рис. 18.
4.2. Логическая структура
сетевого программного взаимодействия в IP сетях
Размер пакетов может ограничиваться как характеристиками
аппаратных средств, так и требованиями протоколов. Например,
объем полезной нагрузки Ethernet-кадра не может превышать 1500
байт. В некоторых современных сетях используются пакеты размером менее 100 байт. Предельный размер пакета для конкретной
сети или протокола называется максимальной единицей передачи
(Maximum Transfer Unit, MTU).
В стеке протоколов TCP/IP за разбивку пакетов в соответствии
с MTU конкретной сети отвечает уровень IP. Если пакет направляется через несколько сетей, то у одной из промежуточных сетей
MTU может оказаться меньшим, чем у сети-отправителя. В этом
случае шлюз, ведущий к сети с меньшим MTU, выполнит дальней100
шее деление пакета. ФункПРИКЛАДНЫЕ ПРОЦЕССЫ
цию сегментации могут выполнять и протоколы других
…
…
…
уровней. Например, совреUDP
TCP
менные версии TCP сами настраивают размеры своих
IP
пакетов с целью повыше*
ARP
ния пропускной способности
конкретной среды передачи
данных.
ENET
Логическая структура се@
тевого программного обеспечения, реализующего протоколы семейства TCP/IP
Кабель Ethernet
в каждом узле сети Internet,
Рис. 19
показана на рис. 19. Прямоугольники обозначают обработку данных, а линии,
соединяющие прямоугольники, – пути передачи данных. Горизонтальная линия внизу рисунка обозначает кабель сети Ethernet, которая используется в качестве примера физической среды; «о» – это
трансивер. Знак «*» – обозначает логический адрес (IP) узла сети,
а «@» – физический Ethernet адрес.
Введем ряд базовых терминов, которые мы будем использовать
в дальнейшем.
Драйвер – это программа, непосредственно взаимодействующая
с сетевым адаптером. Модуль – это программа, взаимодействующая с драйвером, сетевыми прикладными программами или другими модулями. Драйвер сетевого адаптера и, возможно, другие
модули, специфичные для физической сети передачи данных, предоставляют сетевой интерфейс для протокольных модулей семейства TCP/IP.
Название блока данных, передаваемого по сети, зависит от того, на каком уровне стека протоколов он находится. Блок данных,
с которым имеет дело сетевой интерфейс, называется кадром; если
блок данных находится между сетевым интерфейсом и модулем IP,
то он называется IP-пакетом; если он – между модулем IP и модулем UDP, то – UDP-датаграммой; если между модулем IP и модулем
TCP, то – ТСР-сегментом (или транспортным сообщением); наконец,
если блок данных находится на уровне сетевых прикладных процессов, то он называется прикладным сообщением.
101
Когда при приеме Ethernet-кадр попадает в драйвер сетевого интерфейса Ethernet, он может быть направлен либо в модуль ARP
(Address Resolution Protocol адресный протокол), либо в модуль IP
(Internet Protocol – межсетевой протокол). На то, куда должен быть
направлен Ethernet-кадр, указывает значение поля типа в заголовке кадра.
Если IP-пакет попадает в модуль IP, то содержащиеся в нем данные могут быть переданы либо модулю TCP, либо UDP, что определяется полем «протокол» в заголовке IP-пакета.
Если UDP-датаграмма попадает в модуль UDP, то на основании
значения поля «порт» в заголовке датаграммы определяется прикладная программа, которой должно быть передано прикладное
сообщение. Если TCP-сообщение попадает в модуль TCP, то выбор
прикладной программы, которой должно быть передано сообщение,
осуществляется на основе значения поля «порт» в заголовке TCPсообщения. При передаче данные от прикладного процесса проходят через модули TCP или UDP, после чего попадают в модуль IP и
оттуда – на уровень сетевого интерфейса.
У шлюза модуль IP сложнее, чем в первом примере, так как может передавать данные между сетями. Данные могут поступать
через любой сетевой
интерфейс и быть ретранслированы через
ПРИКЛАДНЫЕ
ПРОЦЕССЫ
любой другой сете...
...
...
вой интерфейс. ПроTCP
цесс передачи пакета
UDP
в другую сеть называется ретрансляцией
IP
IP-пакета.Машина,выполняющая
ретран*
*
ARP
ARP
сляцию,
называется
шлюзом.
ENET ENET
Как показано на
@
@
рис. 20, где у узла имеется два интерфейса и
ретранслируемый паEthernet 2
кет не передается модулям TCP или UDP.
Ethernet 1
Некоторые шлюзы воРис. 20
обще могут не иметь
модулей TCP и UDP.
102
4.3. Протокол IP
Протокол IP – это простой и ненадежный протокол, в заголовке которого содержится информация об адресации и управляющая информация для маршрутизации пакетов. Протокол IP описан
в RFC 791 и является основным протоколом сетевого уровня в наборе протоколов Internet. Вместе с протоколом управления передачей
(TCP) протокол IP образует основу протоколов Internet. Протокол IP
имеет две основные функции: обеспечение передачи дейтаграмм по
объединенной сети методом негарантированной доставки без подтверждения соединения и обеспечение фрагментации и повторной
сборки дейтаграмм для поддержки каналов передачи данных с различными размерами максимального передаваемого модуля данных (MTU).
IP-пакет содержит несколько видов информации (рис. 21).
– Версия. Версия используемого протокола IP.
– Длина IP-заголовка (IP header length – IHL). Длина заголовка
дейтаграммы в 32-разрядных словах.
– Тип службы. Задает требуемый протоколом верхнего уровня
способ обработки текущей дейтаграммы и присваивает дейтаграммам различные степени важности.
– Общая длина. Длина всего IP-пакета в байтах, включая данные и заголовок.
32 бита
Версия
IHL
Тип службы
Идентификация
Время существования
Общая длина
Смещение
фрагмента
Контрольная сумма
заголовка
Флаги
Протокол
Адрес источника
Адрес получателя
Свойства + заполнение
Данные (переменной длины)
Рис. 21
103
– Идентификация. Целое число, которое идентифицирует данную дейтаграмму. Это поле используется для облегчения соединения фрагментов дейтаграмм.
– Флаги. Трехразрядное поле, в котором 2 младших бита управляют фрагментацией. Младший бит определяет, может ли пакет
быть фрагментирован, а средний – является ли пакет последним
в серии фрагментированных пакетов. Третий (старший) бит не используется.
– Смещение фрагмента. Позиция данных фрагмента относительно начала данных в исходной дейтаграмме, что позволяет IPпроцессу правильно восстановить исходную дейтаграмму.
– Время существования. Счетчик, который постепенно уменьшается до нуля, после чего дейтаграмма отбрасывается во избежание бесконечного цикла передачи пакетов.
– Протокол. Протокол верхнего уровня, принимающий входящие пакеты после окончания обработки их протоколом IP.
– Контрольная сумма заголовка. Используется для проверки целостности IP-заголовка.
– Адрес источника. Определяет узел-отправитель.
– Адрес получателя. Определяет принимающий узел.
– Свойства. Позволяет протоколу IP задавать дополнительные
опции, такие как обеспечение безопасности.
– Данные. Информация верхнего уровня.
4.4. IР-адресация
Схема IP-адресации неразрывно связана с процессом маршрутизации IP-дейтаграмм по объединенной сети. Каждый IP-адрес имеет специфические компоненты и соответствует основному формату.
Как будет описано далее, IP-адреса делятся на категории и используются для создания адресов подсетей. Каждому узлу в сети TCP/
IP присваивается уникальный 32-разрядный логический адрес, который делится на две главные части: номер сети и номер узла. Номер сети определяет сеть и, если сеть является частью Internet, должен присваиваться Информационным центром Internet (Internet
Network Information Center— InterNIC). Провайдер служб Internet
может получить у InterNIC блоки сетевых адресов и самостоятельно выделять адресное пространство по мере необходимости. Номер
узла определяет узел в сети и присваивается администратором локальной сети.
104
32 бита
Сеть
Десятичная
запись
с точками
8 битов
Узел
8 битов
8 битов
8 битов
Рис. 22
32-разрядный IP-адрес представляет собой 4 группы по 8 битов,
разделенные точками и обычно записываемые в десятеричном формате (так называемая десятичная запись с точками – dotted decimal
notation). Каждый бит октета имеет двоичный вес (128, 64, 32, 16, 8,
4, 2, 1). Минимальное значение октета равно 0, максимальное – 255.
Основной формат IP-адреса показан на рис. 22.
4.5. Классы IP-адресов
IP-адреса делятся на пять классов: А, В, С, D и Е. Для коммерческого использования предназначены только классы А, В и С. Класс
сети определяется первыми слева (старшими) битами. Справочная
информация о пяти классах IP-адресов представлена в табл. 3.
На рис. 23 показан формат классов коммерческих IP-адресов.
Следует обратить внимание на старшие биты в каждом классе.
Таблица 3
Класс
IPадреса
Формат
Назначение
A
N.H.H.H*
Несколько
крупных
организаций
B
N.N.H.H
Средние
1, 0
организации
0
Диапазон
адресов
Количество
битов, сеть/
узел
Старший бит
(биты)
Справочная информация о пяти классах IP-адресов
1.0.0.0 –
126.0.0.0
7/24 16777214**
(224–2)
128.1 –
191.254.0.0
14/16
Максимальное
количество
узлов
65543
(216–2)
105
Формат
C
N.N.N.H
D
–
E
–
Назначение
Диапазон
адресов
Количество
битов, сеть/
узел
Класс
IPадреса
Старший
бит (биты)
Окончание табл. 3
Сравнитель- 1, 1, 192.0.1.0 –
22/8
но мелкие
0 223.255.254.0
организации
Максимальное
количество
узлов
245 (28–2)
1, 1,
1, 0
224.0.0.0 –
239.255.255.
255
Не
для
коммерческого
использования
–
Эксперимен- 1, 1,
тальные
1, 1
240.0.0 –
254.255.255.
255
–
–
Многоадресные
группы
(RFC 1112)
*
N – номер сети, H – номер узла.
Один адрес зарезервирован как широковещательный и еще один –
для сети.
**
Биты
Класс А
7
0
24
Сеть
128643216 8 4 2 1
Класс B
1 0
СетьСеть
Узел
Узел
14
16
Сеть
Узел
21
Класс С
1 1 0
Сеть
Узел
8
Сеть
Рис. 23
106
Узел
Сеть
Узел
Класс адреса
Первый октет,
десятичная запись
Старшие биты
Класс A
1–126
0
Класс B
128–191
10
Класс C
192–223
110
Класс D
224–239
1110
Класс E
240–254
1111
Рис. 24
Для коммерческого использования доступны IP-адреса форматов A, В и С. Класс адреса легко определить по его первому пакету,
сравнив это значение с диапазоном классов IP-адресов. Например,
в IP-адресе 172.31.I.2 первый октет 172. Поскольку 172 лежит в пределах от 128 до 191, 172.31.1.2 является адресом класса В. На рис. 24
представлены диапазоны возможных значений первого октета каждого класса адресов.
4.6. IP-адресация подсети
IP-сети иногда делятся на сети меньшего размера, называемые
подсетями. Подсети предоставляют сетевому администратору некоторые преимущества, такие как повышенная гибкость, более эффективное использование сетевых адресов и возможность ограничить широковещательные потоки данных (чтобы они не проходили
через маршрутизатор). Какие признаки определяют принадлежность адреса к определенному классу показано на рис. 23.
У каждого класса адресов существует свой диапазон допустимых значений первого октета. Подсети администрируются локально. При этом внешне вся сеть выглядит единой и информация о внутренней структуре извне недоступна. Сетевой адрес может разбиваться на несколько подсетей. Например, в состав сети 172.16.0.0
могут входить подсети 172.16.1.0, 172.16.2.0, 172.16.3.0 и 172.16.4.0.
(Наличие всех нулей в адресной части узла указывает на то, что
это – не адрес узла, а номер сети).
107
4.7. Маска IP- подсети
Адрес подсети создается «заимствованием» битов из поля узла
и использованием их для поля подсети. Количество заимствованных битов из поля узла не является постоянным и определяется маской подсети. На рис. 25 показано, как заимствуются биты из поля
адреса узла для создания поля адреса подсети.
Маски подсети имеют тот же формат и представление, что и IPадреса. Однако в маске подсети во всех разрядах, определяющих
зоны сети и подсети, стоит двоичная единица, а во всех разрядах,
определяющих поле узла, – двоичный ноль.
Биты маски подсети должны повторять старшие (первые слева)
биты поля узла (рис. 26). Представление о соответствии двоичных
и десятичных чисел в байте дает рис. 27. Подробнее маски подсети
класса В и С будут описаны далее. Адреса класса А здесь не обсуждаются, поскольку они обычно делятся на подсети на границе 8 битов.
Адрес класса В до деления на подсети
1 0
Сеть
Узел
1 0
Подсеть
Узел
Адрес класса В после деления на подсети
Рис. 25
Сеть
Двоичное
представление 11111111
Десятичное
представление
с разделителями
255
Сеть
Подсеть
Узел
11111111
11111111
00000000
255
255
0
Рис. 26
108
128
64
32
16
8
4 4
2
1
1
0
0
0
0
0
0
0
=
128
1
1
0
0
0
0
0
0
=
192
1
1
1
0
0
0
0
0
=
224
1
1
1
1
0
0
0
0
=
240
1
1
1
1
1
0
0
0
=
248
1
1
1
1
1
1
0
0
=
252
1
1
1
1
1
1
1
0
=
254
1
1
1
1
1
1
1
1
=
255
Рис. 27
Для подсетей класса В и С существуют различные типы масок
подсети.
Стандартная маска подсети для адреса класса В без подсетей –
255.255.0.0, а маска подсети для адреса 171.16.0.0 класса В, где для
подсети отводится 8 битов,— 255.255.255.0. Значение этих 8 битов –
28–2 (1 – для сетевого и 1 – для широковещательного адреса) =
254 возможных подсетей по 28 – 2 = 254 узла в каждой.
Маска подсети для адреса 192.168.2.0 класса С, где для подсети отводится 5 битов, имеет вид 255.255.255.248 (табл. 4). Если для
подсети отводится 5 битов, то количество возможных подсетей равно 25 – 2 = 30, при этом в каждой из них будет 23 – 2 = 6 узлов.
При проектировании сетей классов В и С для определения необходимого количества подсетей и узлов, а также выбора соответствующей маски сети можно воспользоваться справочными данными,
приведенными в табл. 3,4 и 5, а также на рис. 24, 25, 26 и 27.
Обработка входящего пакета с использованием маски происходит следующим образом. Вначале из входящего пакета извлекается IP-адрес получателя и восстанавливается маска внутренней подсети. Затем путем логического умножения получается номер сети,
причем IP-адрес узла получателя удаляется, а номер сети получателя остается. После этого находится номер сети получателя и затем
он сравнивается с исходящим интерфейсом. Наконец, кадр передается по заданному IP-адресу.
109
Таблица 4
Параметры подсетей класса В
Количество
битов
Маска подсети
Количество
подсетей
Количество
узлов
2
3
255.255.192.0
2
16382
255.255.224.0
6
8190
4
255.255.240.0
14
4094
5
255.255.248.0
30
2046
6
255.255.252.0
62
1022
7
255.255.254.0
126
510
8
255.255.255.0
254
254
9
255.255.255.128
510
126
10
255.255.255.192
1022
62
11
255.255.255.224
2046
30
12
255.255.255.240
4094
14
13
255.255.255.248
8190
6
14
255.255.255.252
16382
2
Таблица 5
Параметры подсетей класса С
Количество
битов
Маска
подсети
Количество
подсетей
Количество
узлов
2
255.255.192
2
62
3
255.255.224
6
30
4
255.255.240
14
14
5
255.255.248
30
6
6
255.255.252
62
2
4.8. Протокол IСМР
Очевидно, что элементы более или менее сложных сетей должны как-то реагировать на изменение их состояния – исчезновение
и появление новых узлов, колебание нагрузки и так далее. Для этой
цели служат служебные протоколы. Некоторые из них используются непосредственно для формирования таблицы маршрутизации
110
(они называются протоколами маршрутизации), другие служат для
управления сетью в целом.
Строго говоря, сообщения ICMP переносятся в IP-пакетах, и поэтому протокол ICMP в стеке протоколов должен быть на ступеньку
выше IP. Однако поскольку сервис, предоставляемый этим протоколом, имеет смысл только в контексте IP-протокола, в большинстве
ОС реализация ICMP входит в состав IP-модуля.
ICMP предназначен для уведомления об ошибках при маршрутизации пакета и для выявления проблем в сети.
ICMP-сообщения передаются в IP-пакетах с протоколом, равным 1. ICMP не нуждается в гарантированной доставке, однако, его
сообщения защищены контрольной суммой.
Каждое сообщение содержит тип, дополнительный код, уточняющий этот тип, и некоторую дополнительную информацию (в зависимости от типа и кода), позволяющую получателю более надежно обработать полученное сообщение. ICMP версии 4 поддерживает
следующие типы сообщений:
1. Destination Unreachable
Это сообщение посылается получателем или промежуточным
маршрутизатором в случае, когда IP пакет невозможно доставить
по назначению. При этом используются дополнительные коды:
– net unreachable – недоступна вся подсеть получателя;
– host unreachable – недоступна станция-получатель;
– protocol unreachable – станция-получатель не поддерживает
указанный в заголовке пакета протокол четвертого уровня;
– port unreachable – протокол поддерживается, но в данный момент не существует приложения, получающего пакеты по указанному адресу четвертого уровня (этот адрес часто называют портом и он содержится не в заголовке IP-пакета, а в заголовке пакета
4-го уровня, который содержится в этом IP-пакете);
– fragmentation needed and DF set – необходима фрагментация
для доставки пакета получателю, но в заголовке пакета поднят
флаг «Don’t Fragment»;
– source route failed – маршрут, предписанный отправителем
в опции IP-пакета, не годится для доставки пакета получателю.
В качестве дополнительной информации ICMP-пакет содержит
заголовок и первые 64 бита исходного IP-пакета. Кроме того, большинство шлюзов, согласно RFC 1191, возвращают также в сообщении с кодом fragmentation needed and DF set MTU, поддерживаемым на интерфейсе, через который пакет должен был быть послан.
На этом механизме основан алгоритм определения MTU для пути,
111
который станция может использовать, если по какой-то причине не
хочет допускать фрагментации своих пакетов.
2. Time Exceeded
посылается в двух случаях: с кодом 0, если TTL поле в заголовке IP-пакета стало равным нулю; или с кодом 1, если получатель не
может собрать датаграмму из-за слишком долгого отсутствия одного из фрагментов.
В качестве дополнительной информации ICMP-пакет содержит
заголовок и первые 64 бита исходного IP-пакета.
3. Parameter Problem
посылается, когда какой-то из параметров заголовка IP-пакета
содержит ошибку. В качестве дополнительной информации ICMPпакет содержит номер байта, с которого начинается параметр,
содержащий ошибку, заголовок и первые 64 бита исходного IPпакета.
4. Source Quench
посылается в случае, когда отправитель слишком быстро посылает пакеты, или в сети перегрузка, и промежуточный шлюз или
получатель не справляется с их обработкой. Лучшее, что может сделать отправитель в этом случае – снизить темп. В идеале IP-модуль
должен уведомить о сложившейся ситуации транспортный уровень. В качестве дополнительной информации ICMP-пакет содержит заголовок и первые 64 бита исходного IP-пакета.
5. Redirect
Иногда возникает ситуация, когда промежуточный маршрутизатор посылает IP-пакет в тот же сетевой интерфейс, откуда он его
принял (другому маршрутизатору). В этом случае, станции, ответственной за этот крюк, посылается сообщение Redirect, говорящее
о том, что ее таблица маршрутизации некорректна. Получив это сообщение, станция анализирует дополнительную информацию (заголовок и первые 64 бита исходного IP-пакета и адрес маршрутизатора) и обновляет таблицу маршрутизации.
6. Echo и Echo Reply
Эти сообщения используются для определения, присутствует ли
станция в сети и какое качество связи между двумя станциями.
Сообщение Echo содержит идентификатор, порядковый номер и
(иногда) дополнительные данные. Станция, получив ICMP-сообщение Echo, должна отправить полученный пакет назад, не изменяя его данных, а изменив только тип с Echo на Echo Reply и IPзаголовок. Таким образом, можно выяснить, есть ли в сети станция
с указанным адресом и включена ли она. Послав серию пакетов и
112
оценив процент потерявшихся (его можно определить, проанализировав идентификаторы и порядковые номера вернувшихся пакетов), можно судить, насколько хороша связь между станциями.
С помощью ICMP-сообщений Echo и Echo Reply реализована хорошо
известная программа ping.
7. Timestamp и Timestamp Reply
являются аналогами Echo и Echo Reply, но получатель Timestamp в возвращаемом пакете проставляет время (в миллисекундах
от полуночи), соответствующее моментам, когда пакет был принят
и когда он был отправлен назад.
4.9. Протокол ARP
Для отображения IP-адресов в Ethernet-адреса используется протокол ARP (Address Resolution Protocol – адресный протокол). Отображение выполняется только для отправляемых IP-пакетов, так
как только в момент отправки создаются заголовки IP и Ethernet.
Преобразование адресов выполняется путем поиска в таблице.
Эта таблица, называемая ARP-таблицей, хранится в памяти и содержит строки для каждого узла сети. В двух столбцах содержатся IP- и Ethernet-адреса. Если требуется преобразовать IP-адрес
в Ethernet-адрес, то ищется запись с соответствующим IP-адресом.
ARP-таблица необходима потому, что IP-адреса и Ethernetадреса выбираются независимо, и нет какого-либо алгоритма для
преобразования одного в другой. IP-адрес выбирает менеджер сети
с учетом положения машины в сети Internet. Если машину перемещают в другую часть сети Internet, то ее IP-адрес должен быть изменен. Ethernet-адрес выбирает производитель сетевого интерфейсного оборудования из выделенного для него по лицензии адресного
пространства. Когда у машины заменяется плата сетевого адаптера, то меняется и ее Ethernet-адрес.
В ходе обычной работы сетевая программа, такая как telnet, отправляет прикладное сообщение, пользуясь транспортными услугами TCP. Модуль TCP посылает соответствующее транспортное
сообщение через модуль IP. В результате составляется IP-пакет,
который должен быть передан драйверу Ethernet. IP-адрес места
назначения известен прикладной программе, модулю TCP и модулю
IP. Необходимо на его основе найти Ethernet-адрес места назначения. Для определения искомого Ethernet-адреса используется ARPтаблица.
113
Она заполняется автоматически модулем ARP по мере необходимости. Когда с помощью существующей ARP-таблицы не удается
преобразовать IP-адрес, то происходит следующее:
– по сети передается широковещательный ARP-запрос;
– исходящий IP-пакет ставится в очередь.
Каждый сетевой адаптер принимает широковещательные передачи. Все драйверы Ethernet проверяют поле типа в принятом
Ethernet-кадре и передают ARP-пакеты модулю ARP. ARP-запрос
можно интерпретировать так: «Если ваш IP-адрес совпадает с указанным, то сообщите мне ваш Ethernet-адрес».
Каждый модуль ARP проверяет поле искомого IP-адреса в полученном ARP-пакете и, если адрес совпадает с его собственным IPадресом, то посылает ответ прямо по Ethernet-адресу отправителя
запроса. ARP-ответ можно интерпретировать так: «Да, это мой IPадрес, ему соответствует такой-то Ethernet-адрес».
Этот ответ получает машина, сделавшая ARP-запрос. Драйвер
этой машины проверяет поле типа в Ethernet-кадре и передает ARPпакет модулю ARP. Модуль ARP анализирует ARP-пакет и добавляет запись в свою АRР-таблицу.
Новая запись в ARP-таблице появляется автоматически, спустя
несколько миллисекунд после того, как она потребовалась. ранее
на шаге 2 исходящий IP-пакет был поставлен в очередь. Теперь с использованием обновленной ARP-таблицы выполняется преобразование IP-адреса в Ethernet-адрес, после чего Ethernet-кадр передается по сети. Полностью порядок преобразования адресов выглядит так:
– по сети передается широковещательный ARP-запрос;
– исходящий IP-пакет ставится в очередь;
– возвращается ARP-ответ, содержащий информацию о соответствии IP- и Ethernet-адресов; эта информация заносится в ARPтаблицу;
– для преобразования IP-адреса в Ethernet-адрес у IP-пакета, поставленного в очередь, используется ARP-таблица;
– Ethernet-кадр передается по сети Ethernet.
Если с помощью ARP-таблицы не удается сразу осуществить
преобразование адресов, то IP-пакет ставится в очередь, а необходимая для преобразования информация получается с помощью запросов и ответов протокола ARP, после чего IP-пакет передается по назначению. Если в сети нет машины с искомым IP-адресом, то ARPответа не будет и не будет записи в ARP-таблице. Протокол IP будет
уничтожать IP-пакеты, направляемые по этому адресу. Протоколы
114
верхнего уровня не могут отличить случай повреждения сети Ethernet от случая отсутствия машины с искомым IP-адресом.
Некоторые реализации IP и ARP не ставят в очередь IP-пакеты
на то время, пока они ждут ARP-ответов. Вместо этого IP-пакет просто уничтожается, а его восстановление возлагается на модуль TCP
или прикладной процесс, работающий через UDP. Такое восстановление выполняется с помощью таймаутов и повторных передач. Повторная передача сообщения проходит успешно, так как первая попытка уже вызвала заполнение ARP-таблицы.
Следует отметить, что каждый узел имеет отдельную ARPтаблицу для каждого своего сетевого интерфейса.
4.10. Протокол TCP
Протокол управления передачей (Transmission Control Protocol –
TCP) обеспечивает надежную передачу данных в среде IP. TCP относится к транспортному уровню. TCP предоставляет такие службы,
как потоковая передача данных, надежность, эффективное управление потоком, дуплексный режим и мультиплексирование.
При потоковой передаче данных TCP передает неструктурированный поток байтов, идентифицируемых по порядковым номерам.
Эта служба полезна для приложений, поскольку им не приходится
разбивать данные на блоки перед их передачей по протоколу TCP.
TCP группирует байты в сегменты и передаст их на уровень протокола IP для пересылки.
Надежность TCP обеспечивается сквозной, ориентированной на
соединение, передачей пакетов по объединенной сети. Она достигается упорядочением байтов при помощи номеров подтверждения
передачи, по которым получатель определяет, какой байт должен
поступить следующим. Байты, не получившие подтверждения в течение определенного времени, передаются заново. Надежный механизм протокола TCP позволяет устройствам обрабатывать потерянные, задержанные, дублированные и неверно прочитанные пакеты.
Механизм лимита времени позволяет устройствам распознавать потерянные пакеты и запрашивать их повторную передачу.
TCP обеспечивает эффективное управление потоком. При отправке подтверждений источнику данных принимающий TCPпроцесс указывает наибольший порядковый номер, который он может принять без переполнения внутренних буферов.
В дуплексным режиме TCP-процесс может одновременно пересылать и принимать пакеты.
115
Наконец, мультиплексирование TCP означает одновременную
передачу по одному соединению нескольких диалогов верхнего
уровня.
Для использования надежных транспортных служб TCP-узлы
должны устанавливать друг с другом сеансы, ориентированные
на соединение. Установка соединения выполняется по механизму,
называемому трехэтапной синхронизацией (three-way handshake).
Этот механизм синхронизирует обе стороны соединения, позволяя им согласовать начальные порядковые номера. Он также обеспечивает готовность обеих сторон к передаче данных и информированность каждой из сторон о готовности другой. Это необходимо
во избежание передачи или повторной передачи пакетов в процессе
установки сеанса или после его разрыва.
Каждый узел выбирает случайным образом порядковый номер,
чтобы следить за приемом и передачей байтов потока. Затем механизм трехэтапной синхронизации работает следующим образом.
Первый узел (Узел А) инициирует соединение, отправляя пакет
с начальным порядковым номером и битом синхронизации SYN для
индикации запроса соединения. Второй узел (Узел В) получает SYN,
записывает порядковый номер X и отвечает подтверждением SYN
(вместе с АСК = X + 1). Узел В указывает собственный порядковый
номер (SEQ = Y). Тогда, если АСК равен 20, то это означает, что узел
принял байты с 0 по 19 и ожидает следующий байт 20. Эта технология называется подтверждением передачи. Затем Узел А подтверждает прием всех байтов, посланных Узлом В с подтверждением передачи, указывая следующий байт, который Узел А ожидает получить
(АСК = Y + 1). После этого может начаться передача данных.
Простой транспортный протокол может обеспечивать надежность и такую технологию управления потоком, при которой исходный узел посылает пакет, запускает таймер и ждет подтверждения
приема перед отправкой нового пакета. Если подтверждение не получено по истечении времени, узел передает пакет еще раз. Эта технология называется подтверждением приема и повторной передачей (Positive Acknowledgment and Retransmission – PAR).
Присваивая каждому пакету порядковый номер, PAR позволяет
узлам отслеживать пакеты, потерянные или дублированные вследствие сетевых задержек и преждевременной повторной передачи.
Номера последовательностей посылаются обратно как уведомления
в возможности отслеживания подтверждений приема.
Однако PAR неэффективно использует пропускную способность,
потому что перед отправкой нового пакета узел должен ждать под116
тверждения и, следовательно, пакет можно передавать только один
за другим.
Скользящее окно TCP позволяет использовать пропускную способность сети более эффективно, чем PAR, поскольку с его помощью
узлы могут отправлять несколько байтов или пакетов, не дожидаясь подтверждения.
В TCP принимающий узел определяет текущий размер окна
каждого пакета. Поскольку по TCP-соединению данные передаются в виде потока байт, размеры окон тоже выражаются в байтах.
Таким образом, окно представляет собой количество байт данных,
которые отправитель может послать до ожидания подтверждения
приема. Начальные размеры окон определяются при настройке соединения, но могут изменяться при передаче данных для управления потоком. Например, нулевой размер окна означает запрет на передачу данных.
Предположим, что TCP-отправителю надо послать с помощью
скользящего окна последовательность байт (пронумерованных от 1
до 10) получателю с размером окна 5. Отправитель помещает в окно
первые 5 байт, передает их все сразу и ждет подтверждения приема.
Получатель отвечает с АСК, равным 6, показывает, что получил
байты с 1 по 5 и ждет байт 6. В том же пакете получатель показывает, что размер его окна равен 5. Отправитель сдвигает скользящее
окно на 5 байт вправо и передает байты с 6 по 10. Получатель отвечает АСК, равным 11, показывая, что он ожидает байт 11. В этом
пакете получатель может указать, что его размер окна равен 0 (поскольку, например, его внутренние буферы заполнены). Тогда отправитель больше не сможет посылать байты, пока получатель не
пошлет другой пакет с ненулевым размером окна.
Поля и полный формат TCP-пакета показаны на рис. 28.
Далее описаны поля TCP-пакета, показанные на рис. 28.
– Порт источника и порт получателя. Точки, в которых процессы
верхнего уровня источника и получателя принимают услуги TCP.
– Порядковый номер. Обычно это – номер, присвоенный первому
байту данных в текущем сообщении. При установке соединения может также использоваться для обозначения исходного порядкового
номера в предстоящей передаче.
– Номер подтверждения. Порядковый номер следующего байта
данных, который ожидает получить получатель.
– Сдвиг данных. Число 32-разрядных слов в заголовке TCP.
– Резервные. Область, зарезервированная для использования
в будущем.
117
32 бита
Порт источника
Порт получателя
Порядковый номер
Номер подтверждения
Сдвиг данных Резервные
Флаги
Контрольная сумма
Окно
Указатель срочности
Параметры (и заполнение)
Данные (переменной длины)
Рис. 28
– Флаги. Различная управляющая информация, в том числе
биты SYN и АСК, используемые для установки соединения, и бит
FIN – для разрыва соединения.
– Окно. Размер приемного окна получателя (объем буфера для
входящих данных).
– Контрольная сумма. Показывает, не был ли заголовок поврежден при передаче.
– Указатель срочности. Указывает на первый байт срочных данных в пакете.
– Параметры. Различные дополнительные параметры TCP.
– Данные. Информация верхнего уровня.
4.11. Протокол UDP
Протокол передачи дейтаграмм пользователя UDP (User Datagram Protocol – UDP) представляет собой протокол транспортного уровня (уровень 4), не требующий подтверждения соединения и
принадлежащий семейству протоколов Internet. В сущности, UDP
является интерфейсом между IP и протоколами верхнего уровня.
Порты протокола UDP различают приложения, запущенные на одном устройстве.
В отличие от TCP, UDP не добавляет IP надежности, управления
потоком, или функций исправления ошибок. Из-за простоты UDP
его заголовки короче и требуют меньше сетевых ресурсов, чем TCP.
118
32 бита
Порт источника
Порт получателя
Длина
Контрольная сумма
Рис. 29
UDP полезен в ситуациях, когда мощные механизмы обеспечения надежности протокола TCP не обязательны, например, когда
управление потоком и коррекцию ошибок можно возложить на протокол верхнего уровня.
UDP является транспортным протоколом для нескольких известных протоколов уровня приложений, в том числе NFS, SNMP,
DNS и TFTP.
Как показано на рис. 29, формат пакета UDP содержит четыре поля: порт источника, порт получателя, длина и контрольная
сумма.
Поля портов источника и получателя содержат 16-разрядные
номера портов протокола UDP для демультиплексирования дейтаграмм при приеме процессов уровня приложений. Поле длины
определяет размер UDP-заголовка и данных. Поле контрольной
суммы может служить для проверки целостности UDP-заголовка
и данных.
Вопросы к четвертому разделу
1. Зачем нужен протокол UDP? Почему программа пользователя
не может напрямую обратиться к протоколу IP?
2. Значения полей «Идентификатор» и «Время жизни», а также
флага DF (отсутствие фрагментации), как правило, задаются пользователем протокола IP, переносятся в IP-дейтаграмме через объединенную сеть к получающей IP-сущности, но не доставляются получающему пользователю IP, так как они интересуют только сам
протокол IP. Для каждого из этих значений укажите, интересует
оно IP-сущность источника, IP-сущности промежуточных маршрутизаторов или IP-сущность получающей системы. Обоснуйте свои
ответы.
119
3. Укажите достоинства и недостатки промежуточного восстановления фрагментированных дейтаграмм по сравнению со сборкой конечным получателем?
4. Чему равны накладные расходы на заголовок протокола IP?
5. Требуется фрагментировать IP-дейтаграмму. Какие параметры в поле параметров следует скопировать в заголовок каждого
фрагмента, а какие должны быть помещены только в первый фрагмент? Обоснуйте свой ответ для каждого параметра.
6. Какой уровень определяет выбор маршрута в объединенной
сети?
7. Что определяют спецификации физического уровня?
8. Что определяют спецификации транспортного уровня?
9. Что определяют спецификации канального уровня?
10. Что определяют спецификации сетевого уровня?
11. Какие существуют методы преобразования сетевых адресов
в адреса MAC?
12. Опишите способы доступа к среде передачи, используемые
в сетях Ethernet.
13. Чем управляет канальный уровень?
14. Какой метод доступа к среде передачи данных используется
в сетях Ethernet?
15. Каковы две основные задачи IP?
16. В каком виде обычно представляется IP-адрес?
17. Как определяется класс IP-адреса?
18. Каково назначение маски подсети в IP-адресе?
19. Каково назначение протокола ARP?
20. Каково назначение протокола ICMP?
21. Каково назначение таймера ожидания в ТСР?
21. Какой адрес изменяется при передачи пакета от источника
к получателю?
120
5. Сокеты
Инструмент сокетов используется в основном для настройки сетевых приложений, запущенных на разных компьютерах через
сеть Интернет. Такое взаимодействие между разными прикладными программами возможно только при посредстве стека TCP/IP.
А именно – его транспортного уровня. На этом уровне находится два
протокола – UDP и ТСP. Какой именно протокол следует использовать, решает прикладной программист, исходя из того, какие именно задачи и как должна решать его прикладная программа (подразд. 3.10 и 3.11). Модуль ТСР в ядре ОС всегда имеет две части –
клиентскую и серверную. Cокет стал стандартным интерфейсом
для настройки клиентской и серверной части модуля ТСР при установлении соединения и использует API.  В случае UDP – есть особенности – необходимо позаботиться о том, чтобы запущенный
клиент нашел своего партнера. Клиентское приложение (например, браузер) использует только клиентские сокеты, а серверное
(например, веб-сервер, которому браузер посылает запросы) – как
клиентские, так и серверные сокеты. Приложение пишет данные
в сокет; их дальнейшая буферизация, отправка и транспортировка осуществляется используемым стеком протоколов и сетевой аппаратурой. Чтение данных из сокета происходит аналогичным образом. Socket API был впервые реализован в операционной системе Berkley UNIX. Сейчас этот программный интерфейс доступен
практически в любой модификации Unix, в том числе в Linux. Хотя все реализации чем-то отличаются друг от друга, основной набор функций в них совпадает. Изначально сокеты использовались
в программах на C/C++, но в настоящее время средства для работы
с ними предоставляют многие языки (Perl, Java и др.). Сокеты предоставляют весьма мощный и гибкий механизм межпроцессного
взаимодействия. Они могут использоваться для организации взаимодействия программ на одном компьютере, по локальной сети или
через Internet, что позволяет создавать распределённые приложения различной сложности. Кроме того, с их помощью можно организовать взаимодействие с программами, работающими под управлением разных операционных систем. Например, под Windows существует интерфейс Window Sockets, спроектированный на основе
socket API. Библиотека Winsock поддерживает два вида сокетов –
синхронные (блокируемые) и асинхронные (неблокируемые). Синхронные сокеты задерживают управление на время выполнения
операции, а асинхронные возвращают его немедленно, продолжая
121
выполнение в фоновом режиме, и, закончив работу, уведомляют об
этом вызывающий код. Например, первые версии Windows поддерживали только асинхронные сокеты, поскольку в среде с корпоративной многозадачностью захват управления одной задачей «подвешивает» все остальные, включая и саму операционную систему.
ОС Windows 9x\NT поддерживали оба вида сокетов, однако, в силу того, что синхронные сокеты программируются более просто,
чем асинхронные, последние не получили большого распространения. Независимо от вида, сокеты делятся на два типа – потоковые и
дейтаграммные. Потоковые сокеты работают с установкой соединения, обеспечивая надежную идентификацию обоих сторон и гарантируют целостность и успешность доставки данных. Дейтаграммные сокеты работают без установки соединения и не обеспечивают
ни идентификации отправителя, ни контроля успешности доставки данных, зато они заметно проще потоковых и быстрее работают. Выбор того или иного типа сокетов определяется транспортным
протоколом на котором работает сервер, – клиент не может по своему желанию установить с дейтаграммным сервером потоковое соединение. Дейтаграмные сокеты опираются на протокол UDP, а потоковые на ТСР. После физического подключения выбирается протокол высокого уровня, который зависит от используемого порта.
Согласно спецификации протокола TCP/IP, первые 1024 порта резервируются для определенных задач. Порт номер 21 предназначен
для протокола FTP, 23 – для Telnet, 25 – для электронной почты,
79 – для протокола finger, 80 – для HTTP и т. д. Каждый протокол определяет, каким образом клиент должен взаимодействовать
с портом. Например, протокол HTTP используется Web-браузерами
и серверами для передачи гипертекстовых страниц и изображений.
Когда клиент запрашивает файл от сервера HTTP (это действие известно под названием попадание – hit), он просто записывает имя
файла в специальном формате в определенный порт и получает обратно содержимое файла. Сервер также возвращает код состояния, сообщающий клиенту о возможности удовлетворения запроса
и причину отказа.
5.1. Программирование сокетов
Каждый использующийся сокет имеет тип и ассоциированный
с ним процесс. Сокеты существуют внутри коммуникационных доменов. Домены – это абстракции, которые подразумевают конкретную структуру адресации и множество протоколов, которое опреде122
ляет различные типы сокетов внутри домена. Примерами коммуникационных доменов могут быть: UNIX домен, Internet домен и т. д.
В Internet домене сокет – это комбинация IP адреса и номера порта, которая однозначно определяет отдельный сетевой процесс во
всей глобальной сети Internet. Два сокета, один для хоста-получателя, другой для хоста-отправителя, определяют соединение для протоколов, ориентированных на установление связи, таких, как TCP.
Cоздание сокета
Для создания сокета используется системный вызов socket.
s = socket(domain, type, protocol);
Этот вызов основывается на информации о коммуникационном
домене и типе сокета. Для использования особенностей Internet,
значения параметров должны быть следующими:
– communication domain – AF _ INET (Internet протоколы).
– type of the socket – SOCK _ STREAM; Этот тип обеспечивает последовательный, надежный, ориентированный на установление двусторонней связи поток байтов.
Нами был упомянут сокет с типом stream. Краткое описание других типов сокетов приведено далее:
– Datagram socket – поддерживает двусторонний поток данных.
Не гарантируется, что этот поток будет последовательным, надежным и что данные не будут дублироваться. Важной характеристикой данного сокета является то, что границы записи данных предопределены.
– Raw socket – обеспечивает возможность пользовательского доступа к низлежащим коммуникационным протоколам, поддерживающим сокет-абстракции. Такие сокеты обычно являются дейтаграммориентированными.
Функция socket создает конечную точку для коммуникаций и
возвращает файловый дискриптор, ссылающийся на сокет, или –1
в случае ошибки. Данный дискриптор используется в дальнейшем
для установления связи.
Для создания сокета типа stream с протоколом TCP, обеспечивающим коммуникационную поддержку, вызов функции socket должен быть следующим:
s = socket(AF _ INET, SOCK _ STREAM, 0);
Интерфейс сокета позволяет реализовывать взаимодействие
между компьютерами или между процессами на одном компьютере. Данная технология может работать со множеством различных
устройств ввода-вывода и драйверов, несмотря на то, что их под123
держка зависит от реализации ОС. Подобная реализация интерфейса лежит в основе TCP/IP, благодаря чему считается одной из
фундаментальных технологий, на которых основывается Интернет.
Технология сокетов впервые была разработана в Калифорнийском
университете Беркли для применения на UNIX-системах. Все современные операционные системы имеют ту или иную реализацию
интерфейса сокетов Беркли, так как это стало стандартным интерфейсом для подключения к сети Интернет.
Программисты могут получать доступ к интерфейсу сокетов
на трех различных уровнях, наиболее мощным и фундаментальным из которых является уровень сырых сокетов. Довольно небольшое число приложений нуждается в ограничении контроля
над исходящими соединениями, реализуемыми ими, поэтому поддержка сырых сокетов задумывалась быть доступной только на
компьютерах, применяемых для разработки на основе технологий, связанных с Интернет. Впоследствии в большинстве ОС была реализована их поддержка. Интерфейс сокетов находится над
транспортным и сетевым уровнями стека TCP/IP и имеет прямое взаимодействие с прикладной программой пользовательского
уровня (рис. 30). Прикладной программе могут понадобиться некоторые сведения о режиме работы программных модулей низлежащих уровней (TCP, UDP, IP), а также возможность изменения
значений некоторых полей IP, TCP и UDP заголовков. Для этого
в интерфейсе сокетов имеются специальные функции исследования и изменения опций сокета. Они объявлены в «sys/socket.h».
Следует сразу отметить, что в этом разделе приводятся наиболее
общеупотребительные опции, которые, как правило, поддержива-
Уровень сокетов
Приложение
Уровень TCP
Уровень UDP
Уровень IP
Уровень IP
Рис. 30
124
Программный
интерфейс сокетов
TCP-модуль
UDP-модуль
IP-модуль
IGMP-модуль
ются реализациями стеков TCP/IP на большинстве операционных
платформ. Однако многие реализации модулей стека вводят свои дополнительные опции, меняют тип параметров функций и т. д. Здесь
не освещаются опции сокетов Windows, которые часто существенно отличаются даже в лексике, например, опция UDP_CHECKSUM
в Windows имеет название UDP_CHECKSUM _COVERAGE.
Функция getsockopt() получает информацию относительно значения опции optname заданного уровня для указанного первым параметром открытого сокета.
int getsockopt (int sd, int level, int optname, void *optval,
size _ t *optlen _ ptr);
Значение опции сохраняется в буфере, на который указывает
optval. Перед обращением необходимо обеспечить в *optlen _ ptr
размер этого буфера; по возвращении он содержит число байтов информации, фактически сохраненной в буфере.
Большинство опций интерпретирует буфер optval как одиночное значение типа int.
Фактически возвращаемое значение getsockopt() – 0 – при успехе и –1 – в случае неудачи. В переменной errno отражены следующие возможные причины:
EBADF – аргумент sd – недопустимый описатель файла.
ENOTSOCK – дескриптор sd – не дескриптор сокета.
ENOPROTOOPT – оptname не имеет смысла для данного уровня.
Функция setsockopt() используется, чтобы установить опцию
сокета optname заданного уровня для указанного первым параметром открытого сокета.
int setsockopt (int sd, int level, int optname, void *optval,
size _ t optlen);
Значение опции передается в буфере optval, который имеет размер optlen. Имеются два типа опций сокета: булевского характера,
которые включают или отключают данную опцию, и опции, которые
требуют целочисленного значения или структуры. Для активации
опции булевского характера (on/off) параметр optval указывает на
целое число, отличное от нуля, а для отключения – указывает на целое число, равное нулю. Параметр optlen для булевских опций должен быть равен sizeof (int). Для других параметров optval указывает
на целое число или структуру, которая содержит желательное значение для опции, а optlen – длина целого числа или структуры. Вместе с тем, для сокетов Windows для булевских опций надо указывать
тип BOOL, несмотря на то, что в MSDN они определены как DWORD.
В системах типа BSD 4.* он имеет тип int. Возвращаемое значение
125
Таблица 5
Значения констант указания уровня опции
Описание аргумента
Назначение
int SOL_SOCKET
int IPPROTO_TCP
int IPPROTO_IP
int IPPROTO_UDP
int IPPROTO_ICMPV6
int IPPROTO_IPV6
Уровень сокетов
Уровень TCP
Уровень IPv4
Уровень UDP
Уровень ICMPv6
Уровень IPv6
и коды ошибки для setsockopt() такие же, как и для getsockopt().
Параметр уровня доступа level кодируется с помощью следующих
констант (табл. 5).
Уровни протоколов семейства IPv6 здесь не рассматриваются.
Уровень ICMP для IPv4 включен в уровень IPv4. Опции функций
setsockopt() и getsockopt() не обязательно симметричны – есть некоторые опции, разрешенные только для одной из функций.
5.2. Клиент-серверное взаимодействие
Механизм установления соединения между двумя сетевыми процессами является клиент-серверным (рис. 31).
В начальный момент времени один из процессов готов принимать соединение (сервер), а другой – готов инициировать соединение (клиент).
Интерфейс сокета Беркли – API, позволяющий реализацию взаимодействия между компьютерами или между процессами на одном компьютере. Данная технология может работать со множеством различных устройств ввода-вывода и драйверов, несмотря на
то, что их поддержка зависит от реализации ОС. Подобная реализация интерфейса лежит в основе TCP/IP, благодаря чему считается
одной из фундаментальных технологий, на которых основывается
Интернет. Все современные ОС имеют ту или иную реализацию интерфейса сокетов Беркли, так как это стало стандартным интерфейсом для подключения к сети Интернет.
126
Создать сокет
для входящего запроса:
S =socket ()
Выполнить привязку
сокета к заданному
порту:
bind(S, port)
Создать сокет для
соединения с сервером:
S
= socket()
Перевод сокета в режим
прослушивание порта:
listen(S)
Ждать входящего
запроса на
установление
соединения:
NS = accept(S)
Чтение/запись потока
данных через новый
сокет:
recv(NS)
send(NS)
Тройное
рукопожатие
write
write
Закрыть сокет:
closesocket(NS)
Послать запрос на
установление
соединения:
connect(S, server_addr)
Запись/чтение потока
данных через новый
сокет:
send(S)
recv(S)
Закрыть сокет:
closesocket(S)
Рис. 31
5.3. Пример клиента и сервера, использующих протокол TCP
Протокол TCP реализует концепцию соединения. Процесс создает TCP-сокет вызовом функции socket() с параметрами PF INET
или PF_INET6, а также SOCK_STREAM (Потоковый сокет) и
IPPROTO_TCP.
127
Сервер
Создание простейшего TCP-сервера состоит из следующих шагов:
– Создание TCP-сокетов вызовом функции socket().
– Привязывание сокета к прослушиваемому порту вызовом
функции bind(). Перед вызовом bind() программист должен объявить структуру sockaddr _ in, очистить её (при помощи memset()),
затем sin _ family (PF _ INET или PF _ INET6) и заполнить поля sin _
port (прослушиваемый порт, указать в виде последовательности
байтов). Преобразование short int в порядок байтов может быть
выполнено при помощи вызова функции htons() (сокращение от «от
хоста в сеть»).
– Подготовка сокета к прослушиванию на предмет соединений
(создание прослушиваемого сокета) при помощи вызова listen().
– Принятие входящих соединений через вызов accept(). Это блокирует сокет до получения входящего соединения, после чего возвращает дискриптор сокета для принятого соединения. Первоначальный дискриптор остаётся прослушиваемым дискриптором,
а accept() может быть вызван вновь для этого сокета в любое время
(пока он закрыт).
– Соединение с удаленным хостом, которое может быть создано
при помощи send() и recv() или write() и read().
– Итоговое закрытие каждого открытого сокета, который больше не нужен, происходит при помощи close(). Необходимо отметить, что если были любые вызовы fork(), то каждый процесс
должен закрыть известные ему сокеты (ядро отслеживает количество процессов, имеющих открытый дескриптор), а кроме того,
два процесса не должны использовать один и тот же сокет в одно
время.
/* Код сервера на языке Си */
#include
#include
#include
#include
#include
#include
#include
#include
<sys/types.h>
<sys/socket.h>
<netinet/in.h>
<arpa/inet.h>
<stdio.h>
<stdlib.h>
<string.h>
<unistd.h>
int main( void )
128
{
struct sockaddr _ in stSockAddr;
int i32SocketFD = socket( PF _ INET, SOCK _ STREAM, IPPROTO _
TCP );
if ( i32SocketFD == -1 )
{
perror( «ошибка при создании сокета» );
exit( EXIT _ FAILURE );
}
memset( &stSockAddr, 0, sizeof( stSockAddr ) );
stSockAddr.sin _ family = PF _ INET;
stSockAddr.sin _ port = htons( 1100 );
stSockAddr.sin _ addr.s _ addr = htonl(INADDR _ ANY);
if ( bind( i32SocketFD, ( sockaddr* )&stSockAddr, sizeof(
stSockAddr ) ) == -1 )
{
perror( «ошибка связывания» );
CloseSocketAndExitWithFailure:
close( i32SocketFD );
exit( EXIT _ FAILURE );
}
if ( listen( i32SocketFD, 10 ) == -1 )
{
perror( «ошибка прослушивания» );
goto CloseSocketAndExitWithFailure;
}
for(;;)
{
int i32ConnectFD = accept( i32SocketFD, 0, 0 );
if ( i32ConnectFD < 0 )
{
perror( «ошибка принятия» );
close( i32ConnectFD ); /* нужно ли закрывать сокет после ошибки? */
goto CloseSocketAndExitWithFailure;
129
}
/* выполнение операций чтения и записи ... */
shutdown( i32ConnectFD, SHUT _ RDWR );
close( i32ConnectFD );
}
return 0;
}
Клиент
Создание TCP-клиента происходит следующим образом:
– Создание TCP-сокета вызовом socket().
– Соединение с сервером при помощи connect(), передача структуры sockaddr _ in с sin _ family с указанными PF _ INET или PF _
INET6, sin _ port для указания порта прослушивания (в байтовом
порядке), и sin _ addr для указания IPv4 или IPv6 адреса прослушиваемого сервера (также в байтовом порядке).
– Взаимодействие с сервером при помощи send() и recv() или
write() и read().
– Завершение соединения и сброс информации при вызове
close(). Аналогично, если были какие-либо вызовы fork(), каждый
процесс должен закрыть (close()) сокет.
/* Код клиента на языке Си */
#include
#include
#include
#include
#include
#include
#include
#include
<sys/types.h>
<sys/socket.h>
<netinet/in.h>
<arpa/inet.h>
<stdio.h>
<stdlib.h>
<string.h>
<unistd.h>
int main( void )
{
struct sockaddr _ in stSockAddr;
int i32Res;
int i32SocketFD = socket( PF _ INET, SOCK _ STREAM, IPPROTO _
TCP );
130
if ( i32SocketFD == -1 )
{
perror( «Ошибка: невозможно создать сокет» );
exit( EXIT _ FAILURE );
}
memset( &stSockAddr, 0, sizeof( stSockAddr ) );
stSockAddr.sin _ family = PF _ INET;
stSockAddr.sin _ port = htons( 1100 );
i32Res = inet _ pton( PF _ INET, «192.168.1.3», &stSockAddr.
sin _ addr );
if ( i32Res < 0 )
{
perror( «Ошибка: первый параметр не относится к категории
корректных адресов» );
CloseSocketAndExitWithFailure:
close( i32SocketFD );
exit( EXIT _ FAILURE );
}
else
if ( !i32Res )
{
perror( «Ошибка: второй параметр не содержит корректного IPадреса» );
goto CloseSocketAndExitWithFailure;
}
if (
connect(
i32SocketFD,
( const void* )&stSockAddr, /* зачем приведение типа? */
sizeof( stSockAddr )
) == -1
)
{
perror(«Ошибка соединения»);
goto CloseSocketAndExitWithFailure;
}
/* выполнение операций чтения и записи ... */
131
shutdown( i32SocketFD, SHUT _ RDWR );
close( i32SocketFD );
return 0;
}
Следует помнить, что если клиент запущен раньше сервера,
connect() вернет «Ошибка соединения».
3.4. Пример клиента и сервера, использующих UDP
UDP-дейтаграммы могут приходить не в указанном порядке, дублироваться и приходить более одного раза, или даже не доходить
до адресата вовсе. Адресное пространство UDP, область номеров
UDP-портов (в терминологии ISO – TSAP) полностью отделены от
TCP-портов.
Сервер
Код может создавать UDP-сервер на порту 7654 следующим образом:
int sock = socket( PF _ INET, SOCK _ DGRAM, IPPROTO _ UDP );
struct sockaddr _ in sa;
int bound;
ssize _ t recsize;
socklen _ t *address _ len=NULL;
sa.sin _ addr.s _ addr = htonl(INADDR _ ANY);
sa.sin _ port = htons( 7654 );
bound = bind( sock, ( struct sockaddr* )&sa, sizeof( struct
sockaddr ) );
if ( bound < 0 )
fprintf( stderr, «bind(): ошибка %s\n», strerror( errno ) );
bind() связывает сокет с парой адрес/порт.
while( 1 )
{
printf( «recv test....\n» );
recsize = recvfrom( sock, ( void* )Hz, 100, 0, ( struct sockaddr* )&sa, address _ len );
if ( recsize < 0 )
fprintf( stderr, «Ошибка %s\n», strerror( errno ) );
132
printf( «recsize: %d\n «, recsize );
sleep( 1 );
printf( «datagram: %s\n», Hz );
}
Такой бесконечный цикл получает все UDP-датаграммы, приходящие на порт 7654, при помощи recvfrom(). Функция использует
параметры:
– cокет;
– указатель на буфер данных;
– размер буфера;
– флаги (аналогично при чтении или других сокетных функциях получения);
– адресная структура отправителя;
– длина адресной структуры отправителя.
Клиент
Простейшая демонстрация отправки UDP-пакета, содержащего
«Hello!» на адрес 127.0.0.1 порт 7654, может выглядеть так:
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<errno.h>
<string.h>
<sys/socket.h>
<sys/types.h>
<netinet/in.h>
<unistd.h> /* для вызова close() для сокета */
int main( void )
{
int sock;
struct sockaddr _ in sa;
int bytes _ sent;
const char* buffer = «Hello!»;
int buffer _ length;
buffer _ length = strlen( buffer ) + 1;
sock = socket( PF _ INET, SOCK _ DGRAM, IPPROTO _ UDP );
if ( sock == -1 )
{
printf(«Ошибка создания сокета»);
return 0;
}
133
sa.sin _ family = PF _ INET;
sa.sin _ addr.s _ addr = htonl( 0x7F000001 );
sa.sin _ port = htons( 7654 );
bytes _ sent =
sendto(
sock,
buffer,
strlen( buffer ) + 1,
0,
( struct sockaddr* )&sa,
sizeof( struct sockaddr _ in )
);
if ( bytes _ sent < 0 )
printf( «Ошибка отправки пакета: %s\n», strerror( errno ) );
close( sock );
return 0;
}
Можно запустить только клиента, который будет отправлять
дейтаграммы в сеть, где они и пропадут, если никто на другом конце не вызовет recvfrom(), потому что UDP-сокет не даёт гарантии,
что они будут доставлены. Чтобы этого не произошло, необходимо
превратить это приложение в устанавливающее соединение, где
cервер должен вызывать connect() и указать адрес клиента. После
этого клиент сможет отсылать и принимать данные только с адреса,
указанного при connect(). Вместо sendto() и recvfrom() можно использовать send() и recv().
134
Библиографический список
1. Гук М. Аппаратные средства локальных сетей: Энциклопедия.
СПб.: Питер, 2005.
2. Калюжный В. П., Осипов Л. А. Администрирование информационных сетей: учеб. пособие. CПб.: ГУАП, 2010.
3. Калюжный В. П., Калюжный И. В. Технические основы удаленного доступа: учеб. пособие. СПб.: ГУАП, 2005.
4. Крейг Закер. Компьютерные сети. Модернизация и поиск неисправностей. СПб.: БХВ-Петербург, 2005.
5. Кульгин М. В. Компьютерные сети. Практика построения. Для
профессионалов: 2-е изд. СПб.: Питер, 2003.
6. Литвинов Д. Г. Операционная система UNIX. Улан–Уде: Издво Вост-Сиб. гос. технолог. ун-та, 2000.
7. Люсин О. Б. Сетевое программирование для OC UNIX и
WINDOWS: учеб. пособие. Рига: ИТС, 2006.
8. Олифер В. Г., Олифер Н. А. Компьютерные сети: принципы,
технологии, протоколы. СПб.: Питер, 2000.
9. Пантелеичев Д. Разработка программного обеспечения для
Linux. URL: www.forum.russ2.com.>index.php?showtopic=3751 (дата обращения: 15.06.2015).
10. Робачевский А. Операционная система Unix. СПб.: БХВПетербург, 2005.
11. Стивенс У. UNIX: взаимодействие процессов. СПб.: Питер,
2002.
12. Стивенс У. Ричард, Раго Стивен А. UNIX. Профессиональное
программирование. М.: Символ-Плюс, 2007.
13. Linux network programming. P. 1. From Issue 46 // Linux Journal February 1998.
14. Programming UNIX Sockets in C – Frequently Asked Questions – 1996. URL: www.tlab.ntua.gr/ (дата обращения: 16.06.2015).
15. Porting Berkeley Socket programs to Winsock – MSDN. URL:
www.msdn.microsoft.com (дата обращения: 16.06.2015).
135
Содержание
Список сокращений.......................................................... Предисловие................................................................... 1. Универсальные операционные системы........................... 1.1. Функции ядра....................................................... 1.2. Вспомогательные модули операционной системы....... 1.3. Ядро в привилегированном режиме.......................... 1.4. Процесс и модель процесса...................................... 1.5. Создание процессов................................................ 1.6. Завершение процесса.............................................. 1.7. Иерархия процессов............................................... 1.8. Состояния процессов.............................................. 1.9. Переключение процессов........................................ 1.10. Потоки и модель потока........................................ 1.11. Межпроцессорное взаимодействие.
Состояние состязания. ........................................... 1.12. Критические области............................................ 1.13. Запрещение прерываний и переменные блокировки.. 1.14. Алгоритм Петерсона и команда TSL........................ 1.15. Примитивы межпроцессорного взаимодействия....... 1.16. Проблема производителя и потребителя.................. 1.17. Семафоры и решение проблемы производителя
и потребителя....................................................... 1.18. Мьютексы........................................................... 1.19. Функции ОС по управлению памятью..................... 1.20. Типы адресов....................................................... 1.21. Образ процесса и виртуальное адресное пространство.
1.22. Методы распределения памяти............................... 1.23. Swapping и виртуальная память............................. 1.24. Страничное распределение памяти......................... 1.25. Преобразование виртуальной страницы в физическую.
1.26. Сегментное распределение памяти.......................... 1.27. Сегментно-страничное распределение памяти.......... 1.28. КЭШ память........................................................ 1.29. Устройства ввода-вывода....................................... 1.30. Способы организации ввода-вывода ....................... 1.31. Использование нескольких шин для ввода-вывода.... 1.32. Прямой доступ к памяти....................................... 1.33. Процедура прерываний. Контроллер прерываний..... 1.34. Принципы программного обеспечения ввода-вывода. 1.35. Програмный ввод-вывод........................................ 1.36. Управляемый прерываниями ввод-вывод.
Использование DМА.............................................. 136
3
5
6
6
6
7
10
11
12
12
13
15
17
18
19
20
21
21
22
22
24
24
25
26
27
30
31
33
34
36
39
41
43
46
48
50
51
52
52
1.37. Программные уровни ввода-вывода........................ 1.38. Обработка прерываний и драйверы......................... 1.39. Независимое от устройств
программное обеспечение ввода-вывода.................... 1.40. Буферизация ввода-вывода.................................... 1.41. Вопросы к первому разделу.................................... 2. UNIX-подобные операционные системы........................... 2.1. Структура ядра операционной системы UNIX............ 2.2. Загрузка UNIX...................................................... 2.3. Оболочка UNIX..................................................... 2.4. Процессы в системе UNIX....................................... 2.5. Управление процессами в UNIХ............................... 2.6. Системные вызовы в UNIХ...................................... 2.7. Системные вызовы управления потоками.................. 2.8. Сигналы............................................................... 2.9. Вопросы ко второму разделу.................................... 3. Задания и примеры....................................................... 3.1. Описание некоторых команд и системных
вызовов linux........................................................ 3.2. Системные вызовы управления процессами............... 3.3. Компилятор GCC. .................................................. 3.4. Упражнения......................................................... 4. Основы TCP/IP ............................................................ 4.1. Стек протоколов TCP/IP......................................... 4.2. Логическая структура
сетевого программного взаимодействия в IP сетях...... 4.3. Протокол IP.......................................................... 4.4. IР-адресация......................................................... 4.5. Классы IP-адресов.................................................. 4.6. IP-адресация подсети............................................. 4.7. Маска IP- подсети.................................................. 4.8. Протокол IСМР...................................................... 4.9. Протокол ARP ...................................................... 4.10. Протокол TCP...................................................... 4.11. Протокол UDP..................................................... Вопросы к четвертому разделу....................................... 5. Сокеты.............................................................................
5.1. Программирование сокетов..................................... 5.2. Клиент-серверное взаимодействие............................ 5.3. Пример клиента и сервера, использующих протокол TCP.
3.4. Пример клиента и сервера, использующих UDP..............
Библиографический список............................................... 53
53
55
56
57
59
60
62
64
66
68
70
74
76
77
78
78
80
83
84
98
98
100
103
104
105
107
108
110
113
115
118
119
121
122
126
128
132
135
137
Учебное издание
Калюжный Виталий Павлович,
Осипов Леонид Андронникович
Инструментальные средства
информационных систем
Учебное пособие
Редактор В. П. Зуева
Компьютерная верстка Н. Н. Караваевой
Сдано в набор 20.02.15. Подписано к печати 16.11.15.
Формат 60×84 1/16. Бумага офсетная. Усл. печ. л. 8,1.
Уч.-изд. л. 8,63. Тираж 100 экз. Заказ № 423.
Редакционно-издательский центр ГУАП
190000, Санкт-Петербург, Б. Морская ул., 67
138
Документ
Категория
Без категории
Просмотров
0
Размер файла
2 795 Кб
Теги
kaluzhnay
1/--страниц
Пожаловаться на содержимое документа