Добавлен: 29.10.2018
Просмотров: 48075
Скачиваний: 190
846
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
Рис. 10.13. Система ввода-вывода в Linux (подробно показана одна файловая система)
Чтобы минимизировать латентность перемещений дисковых головок, Linux использует
планировщик ввода-вывода
(I/O scheduler). Цель планировщика — переупорядочить
или собрать в пакеты запросы ввода-вывода к блочным устройствам. Есть много ва-
риантов планировщика, оптимизированных для рабочих нагрузок различных типов.
Базовый планировщик Linux основан на исходном планировщике Linus Elevator
scheduler
. Работу этого планировщика можно описать следующим образом: дисковые
операции сортируются в дважды связанном списке, упорядоченном по адресам сек-
тора дискового запроса. Новые запросы вставляются в этот сортированный список.
Это позволяет избежать дорогих перемещений головок. Этот список запросов затем
последовательно объединяется, чтобы смежные операции выполнялись за один запрос
к диску. Базовый планировщик может вызвать голодание. Поэтому обновленная версия
дискового планировщика содержит два дополнительных списка (с упорядоченными
по срокам выполнения операциями чтения и записи). Срок по умолчанию равен 0,5 с
для запросов чтения и 5 с для запросов записи. Если определенный системой срок
для самой старой операции записи истекает, то этот запрос записи будет обслужен до
любых других запросов из основного дважды связанного списка.
В дополнение к обычным дисковым файлам существуют также блочные специальные
файлы (называемые также сырыми блочными файлами — raw block files). Эти файлы
позволяют программам обращаться к диску при помощи абсолютных номеров блоков
(помимо файловой системы). Чаще всего они используются для таких вещей, как под-
качка и обслуживание системы.
Взаимодействие с символьными устройствами простое. Поскольку символьные устрой-
ства потребляют или производят потоки символов (или байтов данных), то поддержка
произвольного доступа не имеет смысла. Исключение — использование дисциплины
линии
(line disciplines). Дисциплина линии связи может быть связана с терминальным
устройством (представленным структурой tty_struct) и работает как интерпретатор
10.5. Ввод-вывод в системе Linux
847
для данных, обмен которыми происходит с терминальным устройством. Например,
можно делать локальное строковое редактирование (удалять стертые пользователем
символы и строки), символы возврата каретки можно заменять символами переноса
строки, а также выполнять другие специальные операции обработки. Однако если
процесс желает воспринимать каждый введенный пользователем символ, то он может
перевести линию в необрабатываемый режим (минуя дисциплину линии связи). Не все
устройства имеют дисциплину линии связи.
Вывод работает аналогично, заменяя знаки табуляции пробелами, добавляя перед
символами переноса строки символы возврата каретки, добавляя для медленных ме-
ханических терминалов символы-заполнители, за которыми следует символ возврата
каретки и т. д. Как входной, так и выходной символьный поток может быть пропущен
через дисциплину линии связи (обработанный режим) или миновать ее (необработан-
ный режим). Необработанный режим особенно полезен при отправке двоичных данных
на другие компьютеры по последовательной линии или для графических интерфейсов
пользователя. Здесь не требуется никакого преобразования.
Взаимодействие с сетевыми устройствами (network devices) несколько отличается от
рассмотренного. Сетевые устройства также производят и потребляют потоки символов,
однако асинхронная природа делает их не очень подходящими для интеграции в один
интерфейс с другими символьными устройствами. Драйвер сетевого устройства про-
изводит пакеты, состоящие из большого количества байтов (они имеют также сетевые
заголовки). Эти пакеты затем маршрутизируются через несколько драйверов сетевых
протоколов и в конечном итоге передаются в пользовательское приложение. Ключевой
структурой данных здесь является структура буфера сокета skbuff, которая исполь-
зуется для представления областей памяти, заполненных данными пакетов. Данные
в буфере skbuff не всегда начинаются с начала буфера. При их обработке различными
протоколами сетевого стека могут как добавляться, так и удаляться заголовки прото-
колов. Пользовательские процессы взаимодействуют с сетевыми устройствами через
вызов sockets, который в Linux поддерживает исходный интерфейс прикладного про-
граммирования BSD. Драйверы протоколов можно обойти и получить прямой доступ
к сетевому устройству (при помощи raw_sockets). Такие «необработанные» сокеты
может создавать только суперпользователь.
10.5.5. Модули в Linux
В течение десятилетий драйверы устройств в UNIX статически компоновались в ядро,
так что все они постоянно находились в памяти при каждой загрузке системы. Такая
схема хорошо работала в условиях мало меняющихся конфигураций факультетских
мини-компьютеров, а затем и сложных рабочих станций, то есть в тех условиях, в ко-
торых росла и развивалась операционная система UNIX. Как правило, компьютерный
центр собирал ядро операционной системы (содержащее драйверы для всех необхо-
димых устройств ввода-вывода), которым потом и пользовался. Если на следующий
год покупался новый диск, то компьютерный центр пересобирал ядро, что было не так
уж сложно.
Все изменилось с появлением системы Linux, ориентированной на персональные ком-
пьютеры. Количество всевозможных устройств ввода-вывода у персональных компью-
теров на порядок больше, чем у мини-компьютеров. Кроме того, хотя у пользователей
системы Linux есть (или они легко могут его получить) полный набор исходных кодов
848
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
операционной системы, подавляющее большинство пользователей будут испытывать
существенные трудности с добавлением нового драйвера, обновлением всех связанных
с драйвером структур данных, пересборкой ядра и установкой его как загружаемой си-
стемы (не говоря уже о последствиях, которые возникнут в том случае, когда собранное
новое ядро откажется загружаться).
В операционной системе Linux подобные проблемы были решены при помощи кон-
цепции подгружаемых модулей (loadable modules) . Это фрагменты кода, которые
могут быть загружены в ядро во время работы операционной системы. Как правило,
это драйверы символьных или блочных устройств, но подгружаемым модулем могут
быть также файловые системы, сетевые протоколы, программы для отслеживания
производительности системы и все что угодно.
При загрузке модуля должны выполняться несколько действий. Во-первых, модуль
должен быть на лету (во время загрузки) перенастроен на новые адреса. Во-вторых,
система должна проверить, доступны ли необходимые драйверу ресурсы (например,
определенные уровни запросов прерываний), и, если они доступны, пометить их как
используемые. В-третьих, должны быть настроены все необходимые векторы прерыва-
ний. В-четвертых, для поддержки нового типа старшего устройства следует обновить
таблицу переключения драйверов. И наконец, драйверу позволяется выполнить любую
специфическую для данного устройства процедуру инициализации. Когда все эти
этапы выполнены, драйвер является полностью установленным (как и любой другой
драйвер, установленный статически). Некоторые современные системы UNIX также
поддерживают подгружаемые модули.
10.6. Файловая система UNIX
В любой операционной системе (включая Linux) пользователь прежде всего видит
файловую систему . В следующих разделах мы рассмотрим основные идеи файловой
системы Linux, системные вызовы и реализацию файловой системы. Некоторые идеи
пришли из операционной системы MULTICS (и многие из них были скопированы
в MS-DOS, Windows и другие системы), хотя имеются и уникальные для UNIX-систем
идеи. Устройство файловой системы Linux особенно интересно тем, что оно отчетливо
иллюстрирует красоту простых решений (Small is Beautiful). При минимальном алго-
ритме и сильно ограниченном количестве системных вызовов операционная система
Linux предоставляет мощную и элегантную файловую систему.
10.6.1. Фундаментальные принципы
Первоначально файловой системой в Linux была файловая система MINIX 1. Однако
из-за того обстоятельства, что имена файлов были ограничены в ней 14 символами
(чтобы поддерживать совместимость с UNIX Version 7), а максимальный размер файла
составлял 64 Мбайт (что было даже слишком много для жестких дисков того времени,
размер которых составлял 10 Мбайт), интерес к более совершенным файловым си-
стемам появился сразу же после начала разработки системы Linux (которая началась
примерно через 5 лет после выпуска MINIX 1). Первым улучшением стала файловая
система ext, которая позволяла использовать имена файлов длиной 255 символов
и размер файлов 2 Гбайт (однако она была медленнее, чем файловая система MINIX 1,
так что поиски продолжались еще некоторое время). В итоге была изобретена файло-
10.6. Файловая система UNIX
849
вая система ext2 (с длинными именами файлов, большими файлами и более высокой
производительностью), которая и стала основной файловой системой. Однако Linux
поддерживает несколько десятков файловых систем при помощи уровня виртуальной
файловой системы Virtual File System (VFS), описанного в следующем разделе. Когда
система Linux собирается, вам предлагается сделать выбор тех файловых систем, ко-
торые будут встроены в ядро. Другие можно загружать динамически (как модули) во
время выполнения (если будет такая необходимость).
Файл в системе Linux — это последовательность байтов произвольной длины (от 0 до
некоторого максимума), содержащая произвольную информацию. Не делается раз-
личия между текстовыми (ASCII) файлами, двоичными файлами и любыми другими
типами файлов. Значение битов в файле целиком определяется владельцем файла.
Системе это безразлично. Имена файлов ограничены 255 символами. В именах файлов
разрешается использовать все ASCII-символы, кроме символа NUL, поэтому допусти-
мо даже состоящее из трех символов возврата каретки имя файла (хотя такое имя и не
слишком удобно в использовании).
По соглашению многие программы ожидают, что имена файлов будут состоять из
основного имени и расширения, разделенных точкой (которая также считается сим-
волом). Так,
prog.c
— это обычно программа на языке C,
prog.py
— это обычно про-
грамма на языке Python, а
prog.o
— чаще всего объектный файл (выходные данные
компилятора). Эти соглашения никак не регламентируются операционной системой,
но некоторые компиляторы и другие программы ожидают файлов именно с такими
расширениями. Расширения имеют произвольную длину, причем файлы могут иметь
по несколько расширений, например
prog.java.Z
, что, скорее всего, представляет собой
сжатую программу на языке Java.
Для удобства файлы могут группироваться в каталоги. Каталоги хранятся на диске
в виде файлов, и с ними можно работать практически так же, как с файлами. Катало-
ги могут содержать подкаталоги, что приводит к иерархической файловой системе.
Корневой каталог называется
/
и всегда содержит несколько подкаталогов. Символ
/
используется также для разделения имен каталогов, поэтому имя
/usr/ast/x
обозначает
файл
x
, расположенный в каталоге
ast
, который в свою очередь находится в каталоге
usr
. Некоторые основные каталоги (находящиеся у вершины дерева каталогов) по-
казаны в табл. 10.8.
Таблица 10.8. Некоторые важные каталоги, существующие в большинстве систем
Linux
Каталог
Содержание
bin
Двоичные (исполняемые) программы
dev
Специальные файлы для устройств ввода-вывода
etc
Разные системные файлы
lib
Библиотеки
usr
Каталоги пользователей
Существует два способа задания имени файла в системе Linux (как в оболочке, так и при
открытии файла из программы). Первый способ заключается в использовании абсолют-
ного пути
(absolute path) , указывающего, как найти файл от корневого каталога. Пример
850
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
абсолютного пути:
/usr/ast/books/mos4/chap-10
. Он сообщает системе, что в корневом
каталоге следует найти каталог
usr
, затем в нем найти каталог
ast
, который содержит
каталог
books
, в котором содержится каталог
mos4
, а в нем расположен файл
chap-10
.
Абсолютные имена путей часто бывают длинными и неудобными. По этой причине
операционная система Linux позволяет пользователям и процессам обозначить каталог,
в котором они работают в данный момент, как рабочий каталог (working directory) .
Имена путей могут указываться относительно рабочего каталога. Путь, заданный от-
носительно рабочего каталога, называется относительным путем (relative path) . На-
пример, если каталог
/usr/ast/books/mos4
является рабочим каталогом, тогда команда
оболочки
cp chap-10 backup-10
имеет тот же самый эффект, что и более длинная команда
cp /usr/ast/books/mos4/chap-10 /usr/ast/books/mos4/backup-10
Пользователям часто бывает необходимо обратиться к файлу, принадлежащему друго-
му пользователю, или к своему файлу, расположенному в другом месте дерева файлов.
Например, если два пользователя совместно используют один файл, то он будет на-
ходиться в каталоге, принадлежащем одному из них, поэтому другому пользователю
для обращения к этому файлу понадобится использовать абсолютное имя пути (или
изменить свой рабочий каталог). Если абсолютный путь довольно длинный, то необ-
ходимость вводить его каждый раз может весьма сильно раздражать. В системе Linux
эта проблема решается при помощи так называемых ссылок (link) , представляющих
собой записи каталога, указывающие на существующие файлы.
В качестве примера рассмотрим ситуацию, изображенную на рис. 10.14, а. Фред и Лиза
вместе работают над одним проектом, и каждому из них нужен доступ к файлам друго-
го. Если рабочий каталог Фреда
/usr/fred
, то он может обращаться к файлу
x
в каталоге
Лизы как
/usr/lisa/x
. Однако Фред может также создать новую запись в своем каталоге
(рис. 10.14, б), после чего сможет обращаться к этому файлу просто как к
x
.
В только что обсуждавшемся примере мы предположили, что до создания ссылки
единственный способ, которым Фред мог обратиться к файлу
x
Лизы, заключался
Рис. 10.14. а — до создания ссылки;б — после создания ссылки