Linux что такое символьное устройство

Драйверы устройств в Linux

Часть 4: Символьные драйверы Linux

В этой статье, которая является частью серии статей о драйверах устройств в Linux, речь идет о различных понятиях, относящихся к символьным драйверам и их реализаций.

Светлана перед тем, как в классе изучать символьные драйвера Linux, подготовила все для их изучения на своем компьютере у себя в комнате общежития. Она вспомнила следующую фразу профессора Гопи, которую он сказал в классе: «. сегодняшний первый драйвер будет шаблоном для всех драйверов, которые вы напишете в Linux. Написание любого специализированного / расширенного драйвера — это вопрос лишь того, чем заполнить его конструктор и деструктор . «

Поэтому для того, чтобы самостоятельно начать писать символьный драйвер, она взяла код первого драйвера и вытащила различные справочники. Она также скачала из интернета книгу «Драйверы устройств Linux» Джонатана Корбета, Алессандро Рубини и Грега Кроа-Хартмана (Linux Device Drivers Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman). Вот итог того, что она узнала.

Все о символьных драйверах

Мы уже знаем, что такое драйверы и для чего они нам нужны. Что же такого особенного в символьных драйверах? Если мы пишем драйверы для байт-ориентированных операций (или, на жаргоне языка C, символьно-ориентированных операций), то мы называем их символьными драйверами. Поскольку большинство устройств является байт-ориентированными, то большинство драйверов устройств являются символьными драйверами.

Возьмем, к примеру, драйверы последовательного порта, аудио драйверы, видео драйверы, драйверы для фотокамеры и драйверы базового ввода/вывода. На самом деле, все драйверы устройств, которые не являются ни драйверами устройств хранения данных, ни драйверами сетевых устройств, будут символьными драйверами некоторого вида. Давайте рассмотрим общие особенности этих символных драйверов и познакомимся с тем, как Светлана написала один из них.

Подключение устройств

Рис.1: Общий взгляд на символьный драйвер

Как показано на рис.1, для любого приложения пользовательского пространства, предназначенного для работы с байт-ориентированным устройством (в пространстве аппаратных средств), следует использовать соответствующий драйвер символьного устройства (в пространстве ядра). Использование символьных драйверов осуществляется через соответствующие файлы символьных устройств, которые прикомпонованы к виртуальной файловой системе (VFS). Это означает, что приложение выполняет обычные файловые операции с файлом символьного устройства. Эти операции будут перетранслированы виртуальной файловой системой VFS в соответствующие функции в прикомпонованном драйвере символьного устройства. Затем для того, чтобы получить нужные результаты, с помощью этих функций осуществляется окончательный низкоуровневый доступ к реальному устройству.

Обратите внимание, что если приложение выполняет обычные файловые операции, их результат не должен отличаться от обычных случаев. Просто для того, чтобы выполнить эти операции, в драйвере устройства будут использоваться соответствующие функции. Например, операция записи с последующей операцией чтения может, в отличие от работы с обычными файлами, не получить то, что только что было записано в файл символьного устройства. Помните, что это обычное явление для файлов устройств. Давайте в качестве примера возьмем файл аудио устройства. То, что мы записываем в него, является аудиоданными, которые мы хотим воспроизвести, скажем, через громкоговоритель. Однако при чтении данных мы получим аудио данные, которые мы записываем, например, через микрофон. Записанные данные не обязательно должны быть теми, которые мы воспроизводили.

В этом полном подключении из приложения к устройству участвуют следующие четыре основных компонента:

  1. Приложение
  2. Файл символьного устройства
  3. Драйвер символьного устройства
  4. Символьное устройство

Интересно, что все они могут существовать независимо от системы и без всякого обязательного наличия других компонентов. Само существование их в системе не означает, что для того, чтобы сформировать полное соединение, они должны быть скомпонованы вместе. Правильнее сказать, что они должны явно подключаться друг с другом. Приложение подключается к файлу устройства при помощи системного вызова open, открывающего файл устройства.

Читайте также:  Windows upgrade offer что это

Файлы устройств подключаются к драйверу устройства с помощью специального механизма регистрации, что осуществляется драйвером. Драйвер связывается с устройством с помощью специальных низкоуровневых операций, характерных для конкретного устройства. Таким образом, мы формируем полное соединение. При этом, обратите внимание, что файл символьного устройства не является реальным устройством, это просто специальная методика (place-holder) подключения реального устройства.

Старший и младший номера файлов устройств

При подключении приложения к файлу устройства используется имя файла устройства. Но при подключении файла устройства к драйверу устройства используется номер файла устройства, а не имя файла. В результате приложение пользовательского пространства может использовать для файла устройства любое имя, а в пространстве ядра для связи между файлом устройства и драйвером устройства можно использовать тривиальный механизм индексации. Таким номером файла обычно является пара , т. е. или старший и младший номера файла устройства.

Ранее (вплоть до ядра 2.4) каждый старший номер использовался в качестве указания на отдельный драйвер, а младший номер использовался для указания на конкретное подмножество функциональных возможностей драйвера. В ядре 2.6 такое использование номеров не является обязательным; с одним и тем же старшим номером может быть несколько драйверов, но, очевидно, с различными диапазонами младших номеров.

Однако, такое использование больше характерно для незарезервированных старших номеров, а стандартные старшие номера обычно резервируются для вполне определенных конкретных драйверов. Например, 4 — для последовательных интерфейсов, 13 — для мышей, 14 — для аудио-устройств и так далее. С помощью следующей команды можно будет выдать список файлов различных символьных устройств, имеющихся в вашей системе:

Использование чисел в ядре 2.6

Тип (определен в заголовке ядра linux/types.h ):

  • dev_t — содержит старший и младший номера

Макрос (определен в заголовке ядра linux/kdev_t.h ):

  • MAJOR(dev_t dev) — из dev извлекается старший номер
  • MINOR(dev_t dev ) — из dev извлекается младший номер
  • MKDEV(int major, int minor) — из старшего и младшего номеров создается dev

Подключение файла устройства к драйверу устройства осуществляется за два шага:

  1. Выполняется регистрация файлов устройств для диапазона
  2. Подключение операций, выполняемых над файлом устройства, к функциям драйвера устройства.

Первый шаг выполняется с помощью одного из следующих двух API, определенных в заголовке ядра linux/fs.h :

С помощью первого API число cnt регистрируется как среди номеров файлов устройств, которые начинаются с first и именем файла name . С помощью второго API динамически определяется свободный старший номер и регистрируется число cnt среди номеров файлов устройств, начинающиеся с , с заданным именем файла name. В любом случае в директории /proc/devices указывается список имен с зарегистрированным старшим номером. Имея эту информацию, Светлана добавила в код первого драйвера следующее:

В конструктор она добавила:

В деструктор она добавила:

И собрала все это вместе следующим образом:

Затем Светлана повторила обычные шаги, которые она узнала при изучении первого драйвера:

  • Собрала драйвер (файл .ko ), выполнив команду make .
  • Загрузила драйвер с помощью команды insmod .
  • Выдала список загруженных модулей с помощью команды lsmod .
  • Выгрузила драйвер с помощью команды rmmod .

Подведем итог

Кроме того, перед выгрузкой драйвера она заглянула в директорий /proc/devices для того, чтобы с помощью команды cat /proc/devices найти зарегистрированный старший номер с именем «Shweta». Он там был. Тем не менее, она не смогла в директории /dev найти ни одного файла устройств с таким же старшим номером, т.к. она создала этот номер вручную с помощью команды mknod , а затем пыталась выполнить операции чтения и записи. Все эти действия показаны на рис.2.

Читайте также:  Для установки windows 10 требует драйвера для дисковода

Рис.2: Эксперименты с файлом символьного устройства

Обратите внимание, что в зависимости от номеров, уже используемых в системе, старший номер 250 может варьироваться от системы к системе. На рис.2 также показаны результаты, которые получила Светлана при чтении и записи одного из файлов устройств. Это напомнило ей, что все еще не сделан второй шаг подключения файла устройства к драйверу устройства, при котором операции над файлом устройства связываются с функциями драйвера устройства. Она поняла, что ей для того, чтобы завершить этот шаг, нужно поискать дополнительную информацию, а также выяснить причины отсутствия файлов устройств в директории /dev .

Источник

Классы устройств и модулей

Способ видения устройств в Linux разделяется на три фундаментальных типа. Каждый модуль обычно реализован как один из этих типов и таким образом классифицируется как символьный модуль , блочный модуль , или сетевой модуль . Такое разделение модулей на разные типы или классы не является жёстким; программист может при желании создавать большие модули, содержащие разные драйверы в одном куске кода. Хорошие программисты, тем не менее, обычно создают разные модули для каждой новой функциональности, потому что декомпозиция является ключом к масштабируемости и расширяемости.

Этими тремя классами являются:

Символьное устройство — это такое устройств, к которому можно обращаться как к потоку байтов (так же как к файлу); драйвер символьного устройства отвечает за реализацию такого поведения. Такой драйвер обычно, по крайней мере, поддерживает системные вызовы open , close , read и write . Текстовый экран ( /dev/console ) и последовательные порты ( /dev/ttyS0 и подобные) являются примерами символьных устройств, так как они хорошо представлены абстракцией потока. Для обращения к символьным устройствам используют узлы (node) файловой системы, такие как /dev/tty1 и /dev/lp0 . Единственное важное отличие между символьными устройствами и обычными файлами — вы всегда можете двигаться вперед и назад в обычном файле, в то время как большинство символьных устройств — это только каналы данных, к которым вы можете обращаться только последовательно. Существуют, однако, символьные устройства, которые выглядят как области данных, и вы можете двигаться по ним назад и вперёд; к примеру, это обычно используется в грабберах экрана, где приложения могут получать доступ ко всему полученному изображению используя mmap или lseek .

Так же как символьные устройства, блочные устройства доступны через узлы файловой системы в директории /dev . Блочное устройство — это устройство (например, диск) который может содержать файловую систему. В большинстве систем Unix блочное устройство может поддерживать только операции ввода-вывода, которые передают один или более целых блоков, обычно равных 512 байт (или большей степени числа два). Linux, однако, разрешает приложению читать и писать в блочное устройство, так же как и в символьное устройство — это позволяет передавать любое число байт за раз. В результате, блочные и символьные устройства отличаются только способом управления данными внутри ядра и, соответственно, программным интерфейсом в ядре/драйвере. Как и символьное устройство, каждое блочное устройство доступно через узел файловой системы, так что различия между ними не видны пользователю. Блочные драйверы имеют интерфейс для ядра полностью отличный от символьных устройств.

Любой сетевой обмен данными делается через интерфейс, то есть устройство, которое в состоянии обменяться данными с другими узлами сети. Обычно, интерфейс — это аппаратное устройство, но также он может быть чисто программным устройством, наподобие интерфейса loopback (локальное петлевое устройство). Сетевой интерфейс отвечает за отсылку и приём пакетов данных, управляемых подсистемой сети в ядре, без знания кому предназначены передаваемые пакеты.

Читайте также:  Приложение play machine для windows

Многие сетевые соединения (особенно использующие TCP) являются поточно-ориентированными, но сетевые устройства обычно разработаны для передачи и приёма пакетов. Сетевой драйвер ничего не знает об отдельных соединениях; он только обрабатывает пакеты. Не будучи поточно-ориентированным устройством, сетевой интерфейс нелегко представить как узел в файловой системе наподобие /dev/tty1 . Unix всё же обеспечивает доступ к интерфейсам через назначение им уникальных имён (таких как eth0 ), но это имя не имеет соответствующего элемента в файловой системе. Обмен между ядром и сетевым устройством сильно отличается от используемого в символьных и блочных драйверах. Вместо read и write ядро вызывает функции, относящиеся к передаче пакетов.

Есть другие пути классификации модулей драйверов, которые по-другому подразделяют устройства. Вообще, некоторые типы драйверов работают с дополнительными наборами функций ядра для данного типа устройств. К примеру, можно говорить о модулях универсальной последовательной шины (USB), последовательных модулях, модулях SCSI, и так далее. Каждое USB устройство управляется модулем USB, который работает с подсистемой USB, но само устройство представлено в системе или как символьное устройство (последовательный порт USB, к примеру), или как блочное устройство (USB устройство чтения карт памяти), или как сетевой интерфейс (например, сетевой USB интерфейс).

В последнее время в ядро были добавлены другие классы драйверов устройств, включающие драйверы FireWire и I2C. Таким же образом, как они добавили поддержку драйверов USB и SCSI, разработчики ядра собрали особенности всего класса и передали их разработчикам драйверов, чтобы избежать двойной работы и ошибок, упростив и стабилизировав таким образом процесс написания этих драйверов.

В дополнение к драйверам устройств в ядре в виде модулей реализованы и другие функциональные возможности, включающие и аппаратные средства и программное обеспечение. Общий пример — файловые системы. Тип файловой системы определяет, как организована информация на блочном устройстве, чтобы показать дерево файлов и директорий. Это не драйвер устройства, здесь нет какого-либо устройства, связанного со способом размещения информации; вместо этого, тип файловой системы — это программный драйвер, потому что он отображает структуры данных нижнего уровня на структуры данных верхнего уровня. Он и является файловой системой, которая определяет, какой длины может быть имя файла и какая информация о каждом файле хранится в записи каталога. Модуль файловой системы должен осуществить самый низкий уровень системных вызовов, которые обращаются к каталогам и файлам, отображая имена файла и пути (так же как другую информацию, такую как режимы доступа) к структурам данных, сохранённым в блоках данных. Такой интерфейс полностью независим от фактической передачи данных на и от диска (или другого носителя), что достигнуто с помощью драйвера блочного устройства.

Если вы подумаете о том, как сильно система Unix зависит от нижележащей файловой системы, то вы поймете, что такое программное понятие жизненно важно для функционирования системы. Способность декодировать информацию файловой системы остаётся на самом низком уровне иерархии ядра и имеет предельно важное значение; даже если вы напишете блочный драйвер для своего нового CD-ROM, это будет бесполезно, если вы не в состоянии выполнить команды ls или cp для данных этого устройства. Linux поддерживает понятие модуля файловой системы, программный интерфейс которого декларирует различные операции, которые могут быть выполнены с индексом файловой системы (inode), каталогом, файлом и суперблоком. Вряд ли в действительности программисту потребуется написать модуль файловой системы, потому что официальное ядро уже включает код для самых важных типов файловых систем.

Источник

Оцените статью