На самом деле это просто интерфейсы. Кодированные «старшим» и «второстепенным» числом, они предоставляют хук ядру.
Они бывают двух видов (ну, в общем, три, но именованные каналы пока не входят в объем этого объяснения): Символьные устройства и Блочные устройства.
Блочные устройства, как правило, являются устройствами хранения, способными буферизовать выходные данные и хранить данные для последующего извлечения.
Символьные устройства - это такие вещи, как аудио или видеокарты, или устройства ввода, такие как клавиатура и мышь.
В каждом случае, когда ядро загружает правильный драйвер (либо во время загрузки, либо с помощью таких программ, как udev ), оно сканирует различные шины, чтобы увидеть, действительно ли в системе присутствуют какие-либо устройства, обрабатываемые этим драйвером. Если это так, он устанавливает устройство, которое «прослушивает» соответствующий старший / младший номер.
(Например, процессор цифровых сигналов первой аудиоплаты, обнаруженной вашей системой, получает пару старших / младших номеров 14/3; второй получает 14,35 и т. Д.)
Это зависит от udev, чтобы создать запись в /dev
названном dsp
как символьное устройство, помеченное как Major 14 minor 3.
(В значительно более старых или минимальных версиях Linux /dev/
может не загружаться динамически, а статически содержать все возможные файлы устройств.)
Затем, когда пользовательская программа пытается получить доступ к файлу, который помечен как «специальный символьный файл» с соответствующим старшим / младшим номером (например, ваш аудиоплеер пытается отправить цифровое аудио на /dev/dsp
), ядро знает, что эти данные должны передаваться через драйвер, к которому прикреплен основной / вспомогательный номер; предположительно сказал водитель знает что делать с ним по очереди.
Каждый файл, устройство или иное, поддерживает 6 основных операций в VFS:
Кроме того, файлы устройств поддерживают управление вводом-выводом, что позволяет выполнять другие операции, не описанные в первых 6.
В случае специального символа поиск и передача не реализованы, поскольку они поддерживают потоковый интерфейс . То есть чтение или запись напрямую, например, с помощью перенаправления в оболочке:
источник
Минимальный исполняемый
file_operations
примерКак только вы видите минимальный пример, все становится очевидным.
Ключевые идеи:
file_operations
содержит обратные вызовы для каждого связанного с файлом системного вызоваmknod <path> c <major> <minor>
создает символьное устройство, которое использует теfile_operations
cat /proc/devices
character_device.ko
модуль ядра:Пользовательская программа тестирования:
GitHub QEMU + Buildroot вверх по течению с образцом для запуска:
Более сложные примеры:
read
,write
,lseek
С фиксированным размером внутреннего буфера и debugfs вместо символьного устройства: https://github.com/cirosantilli/linux-kernel-module-cheat/blob/6788a577c394a2fc512d8f3df0806d84dc09f355/kernel_module/fops.cpoll
: https://github.com/cirosantilli/linux-kernel-module-cheat/blob/6788a577c394a2fc512d8f3df0806d84dc09f355/kernel_module/poll.cioctl
: https://github.com/cirosantilli/linux-kernel-module-cheat/blob/6788a577c394a2fc512d8f3df0806d84dc09f355/kernel_module/poll.canon_inode_getfd
ассоциируетfile_operations
к файловому дескриптору без любого файла в файловой системе: /programming/4508998/what-is-anonymous-inode/44388030#44388030источник
*off = 1;
, и почему он установлен1
?read
обращений к одному и тому жеopen(
дескриптору файла. Водитель может делать с ним все, что захочет. Обычная семантика должна содержать количество прочитанных байтов. Однако в этом примере у нас просто более простая семантика:0
для первого чтения,1
после первого чтения. Попробуйте запустить его и поставить шаг printk или GDB для его отладки.«Персонаж за один раз» является неправильным (как и идея, что символьные устройства обязательно не поддерживают поиск и рассказ). Фактически устройства «блок за раз» (то есть строго ориентированные на запись, такие как стример *) должны быть символьными устройствами. Такова идея о том, что символьное устройство обязательно должно быть недоступным - драйверы символьного устройства определяют полную
file_operations
структуру, которая может свободно определять или нет в зависимости от того, поддерживает ли устройство операцию. Символьные устройства, которые большинство людей считают примерами, - это null, urandom, TTY-устройства, звуковая карта, мышь и т. Д., Которые невозможно найти из-за специфики этих устройств, но / dev / vcs, / dev / fb0 и / dev / kmem также являются символьными устройствами, и все они доступны для поиска.Как я уже упоминал, драйвер символьного устройства определяет структуру file_operations, в которой есть указатели на функции для всех операций, которые кто-то может захотеть вызвать над файлом - поиск, чтение, запись, ioctl и т. Д., И каждый из них вызывается один раз, когда соответствующий системный вызов выполняется с открытым файлом этого устройства. И поэтому чтение и запись могут делать со своими аргументами все, что захотят - он может отказаться принимать слишком большую запись или писать только то, что подходит; он может читать только данные, соответствующие одной записи, а не все запрошенное количество байтов.
Итак, что же такое блочное устройство? По сути, блочные устройства - это дисководы. Никакое другое устройство (кроме виртуальных дисков, таких как ramdisk и loopback) не является блочным устройством. Они интегрированы в систему запросов ввода-вывода, уровень файловой системы, систему буферов / кэш-памяти и систему виртуальной памяти так, как это делают символьные устройства, даже когда вы обращаетесь, например, к / dev / sda из пользовательского процесса. , Даже «необработанные устройства», упомянутые на странице в качестве исключения, являются символьными устройствами .
* В некоторых системах UNIX реализован так называемый «режим с фиксированными блоками», который позволяет ядру группировать и разбивать запросы ввода-вывода так, чтобы они соответствовали настроенным границам блоков более или менее таким же образом, как это делается для дисковых накопителей, - как блок. устройство. Символьное устройство необходимо для «режима переменных блоков», который сохраняет границы блоков из пользовательской программы, так как один вызов write (2) записывает один блок, а один вызов read (2) возвращает один блок. Поскольку переключение режимов теперь реализовано как ioctl, а не как отдельный файл устройства, используется символьное устройство. Ленточные накопители с переменными записями в большинстве случаев не доступны для поиска, поскольку поиск включает в себя подсчет количества записей, а не байтов, а собственная операция поиска реализована в виде ioctl.
источник
Символьные устройства могут быть созданы модулями ядра (или самим ядром). Когда устройство создано, создатель предоставляет указатели на функции, которые реализуют стандартные вызовы, такие как open, read и т. Д. Затем ядро Linux связывает эти функции с символьным устройством, например, когда приложение пользовательского режима вызывает read () функция в файле символьного устройства, это приведет к системному вызову, а затем ядро направит этот вызов функции чтения, указанной при создании драйвера. Там это шаг за шагом учебник по созданию устройства символов здесь , вы можете создать образец проект и шаг через него , используя отладчик , чтобы понять , как создается объект устройства и когда обработчики вызываются.
источник