Добавлен: 29.10.2018
Просмотров: 48215
Скачиваний: 190
10.5. Ввод-вывод в системе Linux
841
124-й блок (и для этого не надо читать сначала блоки с 0-го по 123-й). Блочные специ-
альные файлы обычно используются для дисков.
Символьные специальные файлы
(character special files) обычно используются для
устройств ввода или вывода символьного потока. Символьные специальные файлы
используются такими устройствами, как клавиатуры, принтеры, сети, мыши, плоттеры
и т. д. Невозможно (и даже бессмысленно) искать на мыши 124-й блок.
С каждым специальным файлом связан драйвер устройства, осуществляющий работу
с соответствующим устройством. У каждого драйвера есть так называемый номер стар-
шего устройства
(major device) , предназначенный для его идентификации. Если драйвер
одновременно поддерживает несколько устройств (например, два диска одного типа),
то каждому диску присваивается идентифицирующий его номер младшего устройства
(minor device) . Вместе взятые номера главного устройства и младшего устройства одно-
значно определяют каждое устройство ввода-вывода. В некоторых случаях один драйвер
может управлять двумя связанными устройствами. Например, соответствующий сим-
вольному специальному файлу
/dev/tty
драйвер управляет и клавиатурой, и экраном,
которые часто воспринимаются как единое устройство — терминал.
Хотя к большинству символьных специальных файлов невозможен произвольный до-
ступ, ими часто бывает нужно управлять такими способами, которые не используются
для блочных специальных файлов. Рассмотрим, например, введенную с клавиатуры
и отображенную на экране строку. Когда пользователь делает ошибку и хочет стереть
последний символ, он нажимает определенную клавишу. Некоторые пользователи
предпочитают использовать для этого клавишу
Backspace
, другие любят пользоваться
клавишей
Del
. Для удаления всей только что набранной строки тоже имеется большой
выбор средств. Традиционно использовался символ @, но с распространением элек-
тронной почты (использующей символ @ в почтовом адресе) многие системы перешли
на использование комбинации клавиш
Ctrl+U
или других символов. Особая клавиша
требуется и для прерывания работающей программы. Обычно для этого используется
Ctrl+C
, но это не универсальный вариант.
В операционной системе Linux эти символы не заданы жестко, а могут быть настроены
пользователем. Для установки этих параметров обычно предоставляется специальный
системный вызов. Этот системный вызов также управляет преобразованием знака
табуляции в пробел, включением и выключением эха символов при вводе, преобра-
зованиями символов перевода каретки в перенос строки и т. д. Для обычных файлов
и блочных специальных файлов этот системный вызов недоступен.
10.5.2. Работа с сетью
Другим примером ввода-вывода является работа с сетью, впервые появившаяся
в Berkeley UNIX и перенятая системой Linux почти без изменений . Ключевым поняти-
ем в схеме Berkeley UNIX является сокет (socket) . Сокеты подобны почтовым ящикам
и телефонным розеткам в том смысле, что они образуют пользовательский интерфейс
с сетью, как почтовые ящики формируют интерфейс с почтовой системой, а телефон-
ные розетки позволяют абоненту подключить телефон и соединиться с телефонной
системой. Схематично расположение сокетов показано на рис. 10.12.
Сокеты могут динамически создаваться и уничтожаться. При создании сокета вызы-
вающему процессу возвращается дескриптор файла, требующийся для установления
соединения, чтения и записи данных, а также разрыва соединения.
842
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
Рис. 10.12. Использование сокетов для работы сети
Каждый сокет поддерживает определенный тип работы в сети, указываемый при со-
здании сокета. Наиболее распространенными типами сокетов являются:
1. Надежный ориентированный на соединение поток байтов.
2. Надежный ориентированный на соединение поток пакетов.
3. Ненадежная передача пакетов.
Первый тип сокетов позволяет двум процессам на различных машинах установить между
собой эквивалент некой трубы. Байты подаются в канал с одного конца и в том же по-
рядке выходят с другого. Такая система гарантирует, что все посланные байты прибудут
на другой конец канала, и прибудут именно в том порядке, в котором были отправлены.
Второй тип сокетов отличается от первого тем, что он сохраняет границы пакетов.
Если отправитель пять раз сделал системный вызов write, каждый раз отправляя по
512 байтов, а получатель запрашивает 2560 байтов, то при типе сокета 1 он получит все
2560 байтов сразу. При использовании сокета типа 2 ему будут выданы только первые
512 байтов. Чтобы получить остальные байты, получателю придется выполнить си-
стемный вызов read еще четыре раза. Третий тип сокета предоставляет пользователю
доступ к «голой» сети. Этот тип сокета особенно полезен для приложений реального
времени и для тех ситуаций, в которых пользователь хочет реализовать специальную
схему обработки ошибок. Сеть может терять пакеты или доставлять их в неверном по-
рядке. В отличие от сокетов первых двух типов, сокет типа 3 не предоставляет никаких
гарантий. Преимущество этого режима заключается в более высокой производительно-
сти, что иногда важнее надежности (например, для доставки мультимедиа, при которой
скорость ценится существенно выше, чем сохранность данных).
При создании сокета один из параметров указывает используемый для него про-
токол. Для надежных байтовых потоков, как правило, используется протокол TCP
(Transmission Control Protocol — протокол управления передачей). Для ненадежной
передачи пакетов обычно применяется протокол UDP (User Data Protocol — поль-
зовательский протокол данных). Оба они работают поверх протокола IP (Internet
Protocol). Все эти протоколы были разработаны для сети ARPANET Министерства
обороны США и теперь составляют основу Интернета. Для надежного потока пакетов
общепринятого протокола нет.
10.5. Ввод-вывод в системе Linux
843
Прежде чем сокет может быть использован для работы в сети, с ним должен быть
связан адрес. Этот адрес может принадлежать к одному из нескольких пространств
адресов. Наиболее распространенным пространством является пространство адресов
Интернета, использующее 32-разрядные целые числа (для идентификации конеч-
ных адресатов) в протоколе IP v 4 и 128-разрядные целые числа в протоколе IP v 6
(5-я версия протокола IP была экспериментальной системой, так и не выпущенной
в свет).
Как только сокеты созданы на компьютере-источнике и компьютере-приемнике, между
ними может быть установлено соединение (для ориентированного на соединение
обмена). Одна сторона делает системный вызов listen, указывая в качестве параметра
локальный сокет (при этом системный вызов создает буфер и блокируется до тех пор,
пока не прибудут данные). Другая сторона делает системный вызов connect, задавая
в параметрах дескриптор файла для локального сокета и адрес удаленного сокета. Если
удаленный компьютер принимает вызов, то система устанавливает соединение между
двумя сокетами.
После установления соединения оно работает аналогично каналу. Процесс может
читать из канала и писать в него, используя дескриптор файла для своего локального
сокета. Когда соединение более не нужно, оно может быть закрыто обычным спосо-
бом — при помощи системного вызова close.
10.5.3. Системные вызовы ввода-вывода в Linux
С каждым устройством ввода-вывода в операционной системе Linux обычно связан
специальный файл. Большую часть операций ввода-вывода можно выполнить при
помощи соответствующего файла, что позволяет избежать необходимости использо-
вания специальных системных вызовов. Тем не менее иногда возникает необходимость
в обращении к неким специфическим устройствам. До принятия стандарта POSIX
в большинстве версий систем UNIX был системный вызов ioctl, выполнявший со спе-
циальными файлами большое количество операций, специфических для различных
устройств. С течением времени все это привело к путанице. В стандарте POSIX здесь
был наведен порядок, для чего функции системного вызова ioctl были разбиты на от-
дельные функциональные вызовы, главным образом для управления терминалом.
В системе Linux и современных UNIX-системах только от конкретной реализации за-
висит, является ли каждый из них отдельным системным вызовом или они все вместе
используют один системный вызов.
Первые четыре перечисленных в табл. 10.6 вызова используются для установки и опре-
деления скорости терминала. Для управления вводом и выводом предоставлены разные
вызовы, так как некоторые модемы работают в несимметричном режиме. Например,
старые системы videotex предоставляли пользователям доступ к открытым базам
данных с короткими запросами (от дома до сервера) на скорости 75 бит/с и ответами,
посылаемыми со скоростью 1200 бит/с. Этот стандарт был принят в то время, когда
скорость 1200 бит/с в обоих направлениях была слишком дорогой для домашнего
использования. Однако времена в сетевом мире изменились. Но асимметрия все еще
сохраняется на многих линиях связи. Например, некоторые телефонные компании
предоставляют услуги цифровой связи ADSL (Asymmetric Digital Subscriber Line —
асимметричная цифровая абонентская линия) с входящим потоком на скорости
20 Мбит/с и исходящим потоком на скорости 2 Мбит/с.
844
Глава 10. Изучение конкретных примеров: Unix, Linux и Android
Таблица 10.6. Основные вызовы стандарта POSIX для управления терминалом
Вызов
Описание
s=cfsetospeed(&termios, speed)
Задать исходящую скорость
s=cfsetispeed(&termios, speed)
Задать входящую скорость
s=cfgetospeed(&termios, speed)
Получить исходящую скорость
s=cfgtetispeed(&termios, speed)
Получить входящую скорость
s=tcsetattr(fd, opt, &termios)
Задать атрибуты
s=tcgetattr(fd, &termios)
Получить атрибуты
Последние два системных вызова в списке предназначены для установки и считывания
всех специальных символов, используемых для стирания символов и строк, прерыва-
ния процессов и т. д. Помимо этого, они позволяют разрешить и запретить вывод эха,
осуществлять управление потоком, а также выполнять другие связанные с символь-
ным вводом-выводом функции. Существуют также дополнительные функциональные
вызовы ввода-вывода, но они несколько специализированные, поэтому мы не станем
рассматривать их в дальнейшем. Следует также отметить, что системный вызов ioctl
по-прежнему существует.
10.5.4. Реализация ввода-вывода в системе Linux
Ввод-вывод в операционной системе Linux реализуется набором драйверов устройств,
по одному для каждого типа устройств. Функция драйвера заключается в изолирова-
нии остальной части системы от особенностей аппаратного обеспечения. При помощи
стандартных интерфейсов между драйверами и остальной операционной системой
основная часть системы ввода-вывода может быть помещена в машинно независимую
часть ядра.
Когда пользователь обращается к специальному файлу, файловая система определяет
номера старшего и младшего устройств, а также выясняет, является файл блочным
специальным файлом или символьным специальным файлом. Номер старшего устрой-
ства используется в качестве индекса для одной из двух внутренних хэш-таблиц, со-
держащих структуры данных для блочных (или символьных) специальных файлов.
Найденная таким образом структура содержит указатели на процедуры открытия
устройства, чтения из устройства, записи на устройство и т. д. Номер младшего устрой-
ства передается в виде параметра. Добавление нового типа устройства в системе Linux
означает добавление нового элемента к одной из этих таблиц, а также предоставление
соответствующих процедур выполнения различных операций с устройством.
Некоторые операции (выполняемые с различными символьными устройствами) по-
казаны в табл. 10.7. Каждый ряд таблицы относится к одному устройству ввода-вывода
(то есть к одному драйверу). Колонки соответствуют функциям, которые должны под-
держиваться всеми символьными драйверами. Существуют также некоторые другие
функции. Когда выполняется операция с символьным специальным файлом, то система
делает поиск по индексу в хэш-таблице символьных устройств (выбирая соответству-
ющую структуру), а затем вызывает соответствующую функцию (чтобы выполнить
требуемое действие). Таким образом, каждая операция с файлом содержит указатель
на функцию, содержащуюся в соответствующем драйвере.
10.5. Ввод-вывод в системе Linux
845
Таблица 10.7. Некоторые из файловых операций, поддерживаемых для типичных
символьных устройств
Устройство
Open
Close
Read
Write
Ioctl
Other
Null
null
null
null
null
null
ј
Память
null
null
mem_read
mem_write
null
ј
Клавиатура
k_open
k_close
k_read
error
k_ioctl
ј
Tty
tty_open
tty_close
tty_read
tty_write
tty_ioctl
ј
Принтер
lp_open
lp_close
error
lp_write
lp_ioctl
ј
Каждый драйвер разделен на две части, причем обе они являются частью ядра Linux
и работают в режиме ядра. Верхняя часть драйвера работает в контексте вызывающей
стороны и служит интерфейсом к остальной системе Linux. Нижняя часть работает
в контексте ядра и взаимодействует с устройством. Драйверам разрешается делать вы-
зовы процедур ядра для выделения памяти, управления таймером, управления DMA
и т. д. Набор функций ядра, которые они могут вызывать, определен в документе под
названием «Интерфейс драйвер — ядро» (Driver-Kernel Interface) . Создание драйве-
ров для системы Linux подробно описано в работах Cooperstein (2009) и Corbet et al.
(2009).
Система ввода-вывода разделена на два основных компонента: обработку блочных
специальных файлов и обработку символьных специальных файлов. Сейчас мы рас-
смотрим эти компоненты по очереди.
Цель той части системы, которая занимается операциями ввода-вывода с блочными
специальными файлами (например, дисками), заключается в минимизации количества
операций передачи данных. Для достижения данной цели в Linux-системах между
дисковыми драйверами и файловой системой имеется кэш (рис. 10.13). До версии
ядра 2.2 система Linux поддерживала отдельные кэш страниц и буферный кэш, так что
находящийся в дисковом блоке файл мог кэшироваться в обоих кэшах. Более новые
версии Linux имеют единый кэш. Обобщенный уровень блоков связывает эти компо-
ненты вместе и выполняет необходимые преобразования между дисковыми секторами,
блоками, буферами и страницами данных.
Кэш представляет собой таблицу в ядре, в которой хранятся тысячи недавно использо-
ванных блоков. Когда файловой системе требуется блок диска (например, блок i-узла,
каталога или данных), то сначала проверяется кэш. Если нужный блок есть в кэше,
он берется оттуда, при этом обращения к диску удается избежать (что значительно
улучшает производительность системы).
Если же блока в кэше страниц нет, то он считывается с диска в кэш, а оттуда копируется
туда, куда нужно. Поскольку в кэше страниц есть место только для фиксированного
количества блоков, то используется описанный в предыдущем разделе алгоритм за-
мещения страниц.
Кэш страниц работает не только при чтении, но и при записи. Когда программа пишет
блок, то этот блок не попадает напрямую на диск, а отправляется в кэш. Демон pdflush
сбросит блок на диск тогда, когда кэш вырастет свыше установленного значения. Чтобы
блоки не хранились в кэше слишком долго, принудительный сброс на диск «грязных»
блоков производится каждые 30 с.