| |
Базовой моделью системы ввода/вывода UNIX является последовательность байт, доступ к которым может осуществляться как последовательно, так и в в произвольном порядке. В типичном пользовательском процессе UNIX нет таких понятий, как методы доступа или управляющие блоки.
Различные программы используют разнообразные структуры данных, но ядро не связывает ввод/вывод с используемыми структурами. Например, текстовым файлом считается файл из строк символов набора ASCII, которые разделены одним символом новой строки (символ ASCII перевода строки), но ядро не знает ничего об этом соглашении. Для удовлетворения потребностей большинства программ модель еще более упрощена и сводится к потоку байт данных, или потоку ввода/вывода. Такое единое представление данных позволяет работать характерному для UNIX подходу на основе инструментов Kernighan & Pike, 1984. Поток ввода/вывода одной программы может быть подан в качестве входной информации практически любой другой программе. (Этот тип традиционных для UNIX потоков ввода/выводы не нужно путать с потоковой системой ввода/вывода из Eighth Edition или с потоками из System V, Release 3 (STREAMS), оба из которых доступны как обычные потоки ввода/вывода.)
Процессы UNIX для работы с потоками ввода/вывода используют дескрипторы. Дескрипторы представляют собой беззнаковые целые числа, получаемые после выполнения системных вызовов open и socket. Системный вызов open получает в качестве аргументов имя файла и режим доступа, который определяет, должен ли файл открываться для чтения, для записи или для обеих операций. Этот системный вызов может также использоваться для создания нового пустого файла. Системные вызовы read и write могут применяться к дескриптору для переноса данных. Системный вызов close может использоваться для уничтожения любого дескриптора.
Дескрипторы представляют низкоуровневые объекты, поддерживаемые ядром, и создаваемые системными вызовами, специфичными для каждого типа объектов. В 4.4BSD дескрипторы могут представлять три типа таких объектов: файлы, каналы и сокеты.
Файл представляет собой линейную последовательность байт, имеющую по крайней мере одно имя. Файл существует, пока все его имена не удалены и ни один из процессов не хранит его дескриптор. Процесс получает дескриптор файла, открывая имя файла посредством системного вызова open. Работа с устройствами ввода/вывода осуществляется как с файлами.
Каналом является линейная последовательность байт, такая же, как файл, но используемая исключительно как поток ввода/вывода, причем однонаправленный. У канала нет имени, и поэтому он не может быть открыт при помощи open. Вместо этого он создается посредством системного вызова pipe, который возвращает два дескриптора, один из которых принимает входные данные, без искажений, без повторений и в той же самой последовательности посылаемый на другой дескриптор. Система также поддерживает именованный канал, или FIFO. FIFO имеет те же самые свойства, что и канал, за исключением того, что он располагается в файловой системе; поэтому он может быть открыт системным вызовом open. Процессы, которые хотят обмениваться данными, открывают FIFO: Один процесс открывает его для чтения, а другой для записи.
Сокет является промежуточным объектом, который используется для межпроцессных коммуникаций; он существует, пока какой-либо процесс хранит дескриптор, ссылающийся на него. Сокет создается системным вызовом socket, который возвращает его дескриптор. Имеется несколько типов сокетов, которые поддерживают различные коммуникационные возможности, такие, как надежную доставку данных, сохранение последовательности передаваемых сообщений, и сохранение границ сообщений.
В системах, предшествующих 4.2BSD, каналы были реализованы в файловой системе, когда в 4.2BSD появились сокеты, то каналы были повторно реализованы как сокеты.
Для каждого процесса ядро хранит таблицу дескрипторов, которая является таблицей, используемой ядром для преобразования внешнего представления дескриптора в его внутреннее представление. (Дескриптор является просто индексом в этой таблице.) Таблица дескрипторов процесса наследуется от родительского процесса, и вместе с ней наследуется и доступ к объектам, на которые ссылаются дескрипторы. Основными способами, при помощи которых процесс может получить дескриптор, является открытие или создание объекта, а также наследование от родительского процесса. Кроме того, межпроцессные коммуникации при помощи сокетов позволяют передавать дескрипторы в сообщениях между несвязанными процессами на одной и той же машине.
Любой рабочий дескриптор имеет связанное с ним смещение в файле в байтах от начала объекта. Операции чтения и записи начинаются от этого смещения, который обновляется после каждой передачи данных. Для объектов, к которым разрешен произвольный доступ, смещение в файле может быть установлено посредством системного вызова lseek. Обычные файлы, а также некоторые устройства, разрешают произвольный доступ к ним. Каналы и сокеты этого делать не позволяют.
Когда процесс завершается, ядро освобождает все дескрипторы, которые использовались этим процессом. Если процесс хранил последнюю ссылку на объект, то менеджер объектов уведомляется для выполнения всех необходимых действий, таких, как окончательное удаление файла или уничтожение сокета.
Большинство процессов ожидают, что перед началом их работы уже будут открыты три дескриптора. Это дескрипторы 0, 1 и 2, больше известные как стандартный ввод, стандартный вывод и стандартный поток диагностических сообщений, соответственно. Как правило, все они связываются с пользовательским терминалом по время входа в систему (смотри Раздел 14.6) и наследуются через вызовы fork и exec процессами, запускаемыми пользователем. Таким образом, программа может считывать то, что набирает пользователь, из стандартного ввода, и программа может выдавать результат на экран пользователя, осуществляя запись в стандартный вывод. Дескриптор потока диагностических сообщений также открыт для записи и используется для вывода ошибок, когда как стандартный вывод используется для обычного вывода.
Эти (и другие) дескрипторы могут отображаться на объекты, отличающиеся от терминала; такое отображение называется перенаправлением ввода/вывода, и все стандартные командные процессоры позволяют пользователю это делать. Оболочка может направить вывод программы в файл, закрывая дескриптор 1 (стандартный вывод) и открывая выбранный выходной файл для создания нового дескриптора 1. Подобным же образом стандартный ввод может браться из файла, при этом закрывается дескриптор 0 и открывается файл.
Каналы позволяют выводу одной программы становиться вводом другой программы без переписывания и даже перекомпоновки программ. Вместо того, чтобы дескриптор 1 (стандартный вывод) исходной программы был настроен на запись на терминал, он настраивается на входной дескриптор канала. Аналогично дескриптор 0 (стандартный ввод) принимающей программы настраивается на обращение к выводу канала, а не к клавиатуре терминала. Результирующий набор двух процессов и соединяющий канал называется конвейером. Конвейеры могут быть весьма большими последовательностями процессов, соединенных каналами.
Системные вызовы open, pipe и socket порождают новые дескрипторы с наименьшим неиспользуемым номером, подходящим для дескриптора. Для того, чтобы конвейеры могли работать, должен существовать механизм для отображения таких дескрипторов в 0 и 1. Системный вызов dup создает копию дескриптора, которая указывает на ту же самую запись в таблице файлов. Новый дескриптор также является наименьшим неиспользуемым, но если нужный дескриптор сначала закрыть, то dup можно использовать для выполнения нужного отображения. Однако здесь требуется некоторая осторожность: если нужен дескриптор 1, а дескриптор 0 уже закрыт, то в результате получится дескриптор 0. Во избежание этой проблемы в системе имеется системный вызов dup2; он похож на dup, но воспринимает дополнительный аргумент, указывающий номер нужного дескриптора (если нужный дескриптор уже открыт, то dup2 его закроет перед повторным использованием).
Аппаратные устройства имеют связанные с ними имена файлов, и к ним может обращаться пользователь при помощи тех же самых системных вызовов, что используются для обычных файлов. Ядро может различать специальный файл устройства или просто специальный файл, и может определять, к какому устройству он относится, но большинство процессов не выполняют такого распознавания. Терминалы, принтеры и стримеры все доступны как последовательности байт, как дисковые файлы 4.4BSD. Таким образом, особенности работы устройств максимально скрываются ядром, и даже в ядре большинство из них отличаются в драйверах.
Аппаратные устройства могут быть разделены на структурированные или неструктурированные; они известны под названиями блочные и посимвольные, соответственно. Как правило, процессы обращаются к устройствам посредством специальных файлов в файловой системе. Операции ввода/вывода, выполняемые с такими файлами, обрабатываются постоянно находящимися в ядре программными модулями, называемыми драйверами устройств. Большинство аппаратных устройств для сетевых коммуникаций доступны только при помощи механизмов межпроцессного взаимодействия, и не имеют специальных устройств в пространстве имен файловой системы, так как интерфейс низкоуровневых сокетов дает более естественный интерфейс, чем специальный файл.
Структурированные или блочные устройства разделяются на диски и магнитные ленты и включают в себя большинство устройств с произвольным доступом. Ядро поддерживает операции буферизации типа чтение-изменение-запись с блочными структурированными устройствами для того, чтобы разрешить последним осуществлять чтение и запись полностью произвольным образом, как с обычными файлами. Файловые системы создаются на блочных устройствах.
Неструктурированными устройствами являются те, что не поддерживают блочную структуру. Типичными неструктурированными устройствами являются линии связи, растровые графопостроители и небуферизируемые магнитные ленты и диски. Неструктурированные устройства, как правило, поддерживают перенос больших объемов данных.
Неструктурированные файлы называют символьными устройствами, потому что первые из них являлись драйверами терминальных устройств. Интерфейс ядра к драйверу для этих устройств доказал удобство его использования для других неструктурированных устройств.
Специальные файлы устройств создаются системным вызовом mknod. Имеется дополнительный системный вызов, ioctl, для управления низкоуровневыми параметрами специальных файлов. Выполняемые операции для каждого устройства различны. Этот системный вызов позволяет осуществлять доступ к специальным характеристикам устройств, не перегружая смысл других системных вызовов. Например, для стримера существует ioctl для записи метки конца ленты, но нет особой или измененной версии функции write.
В ядре 4.2BSD появился механизм межпроцессного взаимодействия, более гибкий, чем каналы, основанный на сокетах. Сокет является конечной точкой коммуникаций, доступный через дескриптор, как файл или канал. Каждый из двух процессов может создать сокет, а затем соединить эти конечные точки для получения надежного канала передачи потока байт. После соединения процесс может выполнять с дескрипторами операции чтения и записи, как это делалось с каналами. Прозрачность сокетов позволяет ядру перенаправить вывод одного процесса на вход другого, работающего на другой машине. Большим различием между каналами и сокетами является то, что каналы требуют наличия общего родительского процесса для установки коммуникации. Соединение между сокетами может быть установлено двумя несвязанными процессами, возможно, работающими на разных машинах.
System V предоставляет механизм локального межпроцессного взаимодействия через FIFO (также называемые именованными каналами). FIFO отображаются как объекты файловой системы, которые могут быть открыты несвязанными процессами, и в которые можно открывать и посылать данные так же, как в случае каналов. Таким образом, FIFO не требуют общего родительского процесса для установки соединения; они могут быть соединены после того, как будут запущены два процесса. В отличие от сокетов, FIFO могут быть использованы только на локальной машине; они не могут быть использованы для связи между процессами, работающими на разных машинах. FIFO реализованы в 4.4BSD, потому что это требует стандарт POSIX.1. Их функциональность является подмножеством функций интерфейса сокетов.
Механизм сокетов требует расширения традиционных для UNIX системных вызовов ввода/вывода для обеспечения соответствующих имен и смыслов соединениям. Вместо того, чтобы перегружать существующий интерфейс, разработчики использовали существующие интерфейсы, расширив их так, что они продолжили работать без изменений, и разработали новые интерфейсы для работы с новыми возможностями. Системные вызовы read и write использовались для соединений типа потока байт, и было добавлено шесть новых системных вызовов, что позволило посылать и принимать адресованные сообщения, такие, как сетевые датаграммы. Системные вызовы для записи сообщений включают в себя send, sendto и sendmsg. Системные вызовы для чтения сообщений включают recv, recvfrom и recvmsg. В ретроспективе, первые два в каждом классе являются особыми случаями других; recvfrom и sendto, наверное, должны были быть добавлены как библиотечные интерфейсы к recvmsg и sendmsg, соответственно.
Кроме традиционных системных вызовов read и write, в 4.2BSD появилась возможность выполнять множественный ввод/вывод. Множественный ввод использует системный вызов readv для размещения результата единственной операции чтения в нескольких различных буферах. Обратно, системный вызов writev позволяет осуществлять запись нескольких различных буферов за одну атомарную операцию записи. Вместо передачи одного буфера и его длины в качестве параметров, как это делается при использовании системных вызовов read и write, процесс передает указатель на массив буферов и их длин, а также счетчик, определяющий размер массива.
Такой механизм позволяет буферам в различных областях адресного пространства процесса записываться атомарно, без необходимости копировать их в один буфер. Атомарные операции записи необходимы в случае, когда низкоуровневые абстракции основаны на записях, например, стримеры, которые выводят блок ленты при каждом запросе на запись. Также полезна возможность помещать результат одного запроса на чтение в нескольких различных буферах (например, заголовок записи в одно место, а данные в другое). Хотя приложение может симулировать возможность выполнять множественные операции посредством чтения данных в большой буфер с последующим копированием их частей в нужные области, и накладные расходы на копирование в памяти в таких случаях часто увеличивает время выполнения приложения чуть ли не вдвое.
Так же, как send и recv могут быть реализованы в виде библиотечных интерфейсов к sendto и recvfrom, возможно симулирование read через readv и write через writev. Однако read и write используются столь часто, что накладные расходы на такую симуляцию не стоят того.
Вместе с распространением сетевых вычислений возникла потребность в поддержке как локальных, так и удаленных файловых систем. Для облегчения поддержки нескольких файловых систем разработчики добавили в ядро интерфейс виртуальных узлов файловой системы, или интерфейс vnode. Набор операций, экспортируемых через интерфейс vnode, похож на операции файловой системы, ранее поддерживаемые локальной файловой системой. Однако они могут поддерживаться широким спектром типов файловых систем:
Локальные файловые системы, использующие диск
Файлы, импортируемые при помощи разнообразных протоколов удаленных файловых систем
Файловые системы CD-ROM, доступные только для чтения
Файловые системы, предоставляющие специализированные услуги -- к примеру, файловая система /proc
Некоторые варианты 4.4BSD, такие, как FreeBSD, позволяют выполнять динамическую загрузку файловых систем при первом обращении к ним при помощи системного вызова mount. Интерфейс vnode описан в Разделе 6.5; вдобавок он поддерживает функции, описанные в Разделе 6.6; некоторые из файловых систем специального назначения описаны в Разделе 6.7.
Этот, и другие документы, могут быть скачаны с ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.
По вопросам связанными с FreeBSD, прочитайте документацию прежде чем писать в <[email protected]>.
По вопросам связанным с этой документацией, пишите <[email protected]>.
По вопросам связанным с русским переводом документации, пишите <[email protected]>.
Закладки на сайте Проследить за страницей |
Created 1996-2024 by Maxim Chirkov Добавить, Поддержать, Вебмастеру |