Марк Митчелл - Программирование для Linux. Профессиональный подход
6.1. Типы устройств
Файлы устройств не являются обычными файлами: с ними не связаны блоки данных на диске. Данные, помещаемые в такой файл или извлекаемые из него, передаются соответствующему драйверу устройства или принимаются от него, а драйвер, в свою очередь, осуществляет обмен данными с обслуживаемым устройством. Устройства классифицируются по двум типам.
■ Символьные (байт-ориентированные) устройства читают и записывают данные в виде потока байтов. Сюда входят последовательные и параллельные порты, накопители на магнитной ленте, терминалы и звуковые платы.
■ Блочные (блок-ориентированные) устройства читают и записывают данные блоками фиксированного размера. В отличие от символьных устройств блочные устройства предоставляют произвольный доступ к своим данным. В качестве примера можно назвать жесткий диск.
Как правило, приложения не работают с блочными устройствами. В каждом разделе жесткого диска содержится файловая система, которая монтируется к дереву корневой файловой системы Linux. Лишь ядро, реализующее функции файловой системы, получает прямой доступ к блочному устройству. Программы обращаются к содержимому диска через обычные файлы и каталоги.
Опасность доступа к блочному устройствуДрайверы блочных устройств имеют прямой доступ к данным, хранящимся на диске. В большинстве Linux-систем прямой доступ к таким устройствам разрешен лишь процессам, выполняющимся от имени пользователя root, но и они способны нанести непоправимый ущерб, изменив содержимое диска. Осуществляя запись в блочное устройство, программа может модифицировать или уничтожить не только управляющую информацию, хранящуюся в файловой системе, но и таблицу разделов диска и даже главную загрузочную запись. Вследствие этого жесткий диск или вся система может оказаться разрушенной.
Приложениям иногда приходится иметь дело с символьными устройствами: об этом пойдет речь в разделе 6.5, "Специальные устройства".
6.2. Номера устройств
ОС Linux идентифицирует устройства двумя числами: старшим номером устройства и младшим номером устройства. Старший номер указывает на то, какой драйвер соответствует устройству. Соответствие между старшими номерами устройств и драйверами жестко зафиксировано в исходных файлах ядра Linux. Двум разным драйверам может соответствовать одинаковый старший номер. Это значит, что один драйвер управляет символьным устройством, а второй — блочным. Младшие номера позволяют различать отдельные устройства или аппаратные компоненты, управляемые одним драйвером. Значение младшего номера зависит от драйвера.
Например, устройству со старшим номером 3 соответствует основной контроллер IDE. К этому контроллеру могут быть подключены два устройства (жесткие диски, накопитель на магнитной лейте или дисковод CD-ROM). "Главному" устройству будет соответствовать младший номер 0, а "подчиненному" устройству — номер 64. Отдельные разделы главного устройства (если он поддерживает разбивку на разделы) будут иметь младшие номера 1, 2, 3 и т.д. Разделы подчиненного устройства представляются младшими номерами 65, 66, 67 и т.д.
Список старших номеров устройств можно узнать в документации к исходным текстам ядра Linux. Во многих дистрибутивах эта информация хранится в файле /usr/src/Linux/Documentation/devices.txt. В специальном файле /proc/devices перечислены старшие номера устройств, соответствующие загруженным в данный момент драйверам (о файловой системе /proc рассказывается в главе 7, "Файловая система /proc").
6.3. Файловые ссылки на устройства
Ссылки на устройства напоминают обычные файлы. Их можно перемещать с помощью команды mv и удалять командой rm. Правда, если попытаться скопировать такую ссылку с помощью команды cp, из устройства будут прочитаны данные (при условии что устройство поддерживает операцию чтения) и эти данные перенесутся в указанный файл. При попытке перезаписи ссылки в соответствующее устройство будут записаны данные.
Ссылка на устройство создается с помощью команды mknod (документация вызывается так. man 1 mknod) или функции mknod() (документация вызывается так: man 2 mknod). Создание ссылки не означает, что драйвер устройства или само устройство автоматически станут доступными. Ссылка является лишь своего рода порталом, через который происходит взаимодействие с драйвером. Создавать такие ссылки разрешается только процессам суперпользователя.
Первый аргумент команды mknod задает путь, под которым ссылка появится в файловой системе. Второй аргумент равен b для блочного устройства и с для символьного устройства. Старший и младший номера устройства задаются в третьем и четвертом аргументах соответственно. Например, следующая команда создает в текущем каталоге ссылку на символьное устройство lp0. Старший номер устройства — 6, младший — 0. Эти номера соответствуют первому параллельному порту Linux.
% mknod ./lp0 с 6 0
Помните, что лишь суперпользователю разрешено создавать ссылки на устройства, поэтому для успешного выполнения показанной команды необходимо зарегистрироваться в системе под именем root.
Команда ls особым образом помечает ссылки на устройства. Если вызвать ее с флагом -l или -o, то первый символ в каждой строке будет обозначать тип записи. Знак - (дефис) соответствует обычному файлу, буква d — каталогу, b — блочному устройству, c — символьному устройству. В последних двух случаях команда ls вместо размера файла отображает старший и младший номера устройства. Давайте, к примеру, получим информацию о ссылке на символьное устройство, которую мы только что создали:
% ls -l lp0
crw-r----- 1 root root 6, 0 Mar 7 17:03 lp0
В распоряжении программ имеется функция stat(), которая позволяет не только узнать, какому устройству — символьному или блочному— соответствует ссылка, но и определить номера устройства. Эта функция описана в приложении Б, "Низкоуровневый ввод-вывод".
Удалить ссылку на устройство (не сам драйвер) можно с помощью команды rm:
% rm ./lp0
6.3.1. Каталог /dev
В Linux имеется каталог /dev, в котором содержатся ссылки на все символьные и блочные устройства, известные системе. Имена этих ссылок стандартизированы
Например, главное устройство, подключенное к основному контроллеру IDE, имеет старший и младший номера 3 и 0 соответственно, а его стандартное имя — /dev/hda. Если данное устройство поддерживает разделы, то первый раздел (младший номер 1) будет называться /dev/hda1. Проверим это:
% ls -l /dev/hda /dev/hda1
brw-rw---- 1 root disk 3, 0 May 5 1998 /dev/hda
brw-rw---- 1 root disk 3, 1 May 5 1998 /dev/hda1
Здесь же будет находиться и ссылка на параллельный порт, которую мы создали выше:
% ls -l /dev/lp0
crw-rw---- 1 root daemon 6, 0 May 5 1998 /dev/lp0
В большинстве случаев нет необходимости с помощью команды mknod создавать собственные ссылки. Достаточно скопировать нужные ссылки из каталога /dev. У программ, не располагающих привилегиями суперпользователя, нет другого выбора, кроме как пользоваться имеющимися ссылками. Обычно новые ссылки создаются только системными администраторами и разработчиками драйверов. В Linux имеются специальные средства, упрощающие администраторам процесс создания ссылок с правильными именами.
6.3.2. Доступ к устройству путем открытия файла
Как работать с аппаратными устройствами? В случае символьного устройства ответ прост: откройте ссылку на устройство как обычный файл и осуществляйте чтение-запись традиционным образом. Например, если к первому параллельному порту подключен принтер, то распечатать файл document.txt можно, направив его непосредственно на устройство /dev/lp0:
% cat document.txt > /dev/lp0
Чтобы эта команда завершилась успешно, необходимо иметь право записи в файл принтера. Во многих Linux-системах таким правом обладают лишь пользователь root и системный демон печати (lpd). Кроме того, результат работы принтера зависит от того, как он интерпретирует посылаемые ему данные. Одни принтеры распечатывают текстовые файлы,[18] другие — нет. PostScript-принтеры распечатывают файлы формата PostScript.
Послать устройству данные из программы несложно. В приведенном ниже фрагменте программы с помощью низкоуровневых функций ввода-вывода содержимое буфера направляется в устройство /dev/lp0:
int fd = open("/dev/lp0", O_WRONLY);
write(fd, buffer, bufffer_length);
close(fd);
6.4. Аппаратные устройства