Понимание файлов символов (или символов)

Я пытаюсь понять специальные файлы символов. Из википедии я понимаю, что эти файлы «предоставляют интерфейс» для устройств, которые передают данные по одному символу за раз. Я понимаю, что система каким-то образом вызывает символьное устройство, а не напрямую вызывает драйвер устройства. Но как этот файл предоставляет этот интерфейс? Это исполняемый файл, который переводит системный вызов? Может кто-нибудь объяснить, что случилось.

На самом деле это просто интерфейсы. Закодированные «основным» и «второстепенным» числом, они обеспечивают привязку к ядру.

Они представлены в двух вариантах (ну, три, но названные каналы на данный момент выходят за рамки этого объяснения): Устройства символов и блокирующие устройства.

Блочные устройства, как правило, представляют собой устройства хранения данных, способные к буферизации вывода и хранения данных для последующего поиска.

Устройства символов – это такие вещи, как аудио или видеокарты, или устройства ввода, такие как клавиатура и мышь.

В каждом случае, когда ядро ​​загружает правильный драйвер (либо во время загрузки, либо через такие программы, как udev ), он сканирует различные шины, чтобы увидеть, действительно ли какие-либо устройства, обработанные этим драйвером, присутствуют в системе. Если это так, он устанавливает устройство, которое «прослушивает» соответствующий основной / младший номер.

(Например, цифровой сигнальный процессор первой звуковой карты, найденный вашей системой, получает пару основных / младших чисел 14/3, а второй – 14,35 и т. Д.),

Это нужно для udev, чтобы создать запись в /dev именем dsp в качестве символьного устройства с отметкой 14 майор 3.

(В версиях Linux с более старыми или минимальными значениями /dev/ может не загружаться динамически, а просто ставить все возможные файлы устройств).

Затем, когда программа пользовательского пространства пытается получить доступ к файлу, помеченному как «специальный файл символов», с соответствующим основным / второстепенным номером (например, ваш аудиоплеер пытается отправить цифровой звук в /dev/dsp ), ядро ​​знает, что эти данные должны передаваться через водителя, к которому прикреплен основной / младший номер; предположительно сказал, что водитель знает, что с ним делать по очереди.

Каждый файл, устройство или другое поддерживает 6 основных операций в VFS:

  1. открыто
  2. Закрыть
  3. Читать
  4. Написать
  5. Искать
  6. Расскажи

Кроме того, файлы устройств поддерживают управление вводом / выводом, что позволяет выполнять другие разные операции, не охватываемые первыми 6.

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

 echo 'foo' > /dev/some/char sed ... < /dev/some/char 

«Персонаж за раз» является неправильным (как и идея, что персональные устройства обязательно не поддерживают поиск и рассказ). Фактически, «блокировать за раз» (то есть строго ориентированные на запись, например, ленточный накопитель *), устройства должны быть символьными устройствами. Идея состоит в том, что устройство символов обязательно должно быть не поддающимся описанию. Драйверы персональных устройств определяют полную структуру file_operations которая может свободно определять llseek или нет в зависимости от того, поддерживает ли устройство эту операцию. Символьные устройства, которые большинство людей считают примерами, – это null, urandom, TTY-устройства, звуковая карта, мышь и т. Д., Которые являются неизученными из-за специфики того, что эти устройства, но / dev / vcs, / dev / fb0 , и / dev / kmem также являются символьными устройствами, и все они доступны для поиска.

Как я уже упоминал, драйвер устройства символов определяет структуру file_operations, которая имеет указатели на функции для всех операций, которые кто-то может захотеть вызвать в файле – искать, читать, писать, ioctl и т. Д. – и каждый из них вызывается один раз, когда соответствующий системный вызов выполняется с открытием этого файла устройства. Поэтому чтение и запись могут делать все, что угодно, с его аргументами – он может отказаться принять слишком большую запись или написать только то, что подходит; он может читать только данные, соответствующие одной записи, а не все запрошенное количество байтов.

Итак, что такое блок-устройство? В основном, блочными устройствами являются дисковые накопители. Никакое другое устройство (кроме виртуальных дисков, таких как ramdisk и loopback) не является блочным устройством. Они интегрированы в систему запросов ввода-вывода, уровень файловой системы, систему буфера / кэш-памяти и систему виртуальной памяти таким образом, что символьные устройства нет, даже когда вы обращаетесь к примеру / dev / sda из пользовательского процесса , Даже «необработанные устройства», которые эта страница упоминает как исключение, являются символьными устройствами .

* В некоторых системах UNIX реализовано то, что теперь называется «режим фиксированного блока», который позволяет группе ядра и разделить запросы ввода-вывода в соответствии с сконфигурированными границами блоков более или менее таким же образом, как и для дисковых накопителей, – как блок устройство. Для режима «переменный блок» требуется символьное устройство, которое сохраняет границы блоков из пользовательской программы, поскольку один вызов записи (2) записывает один блок, а один вызов чтения (2) возвращает один блок. Поскольку переключение режимов теперь реализовано как ioctl, а не отдельный файл устройства, используется символьное устройство. Сменные накопители с переменной записью в основном «недоступны для поиска», потому что поиск включает в себя подсчет количества записей, а не количество байтов, а операция поиска на месте реализована как ioctl.

Пример минимального runnable file_operations

Когда вы видите минимальный пример, все становится очевидным.

Ключевыми идеями являются:

  • file_operations содержит обратные вызовы для каждого файла, связанного с syscall
  • mknod path c major minor создает символьное устройство, которое использует эти file_operations
  • для символьных устройств, которые динамически распределяют номера устройств (норма, чтобы избежать конфликтов), найдите номер с cat /proc/devices

Ядро модуля character_device.ko :

 #include <asm/uaccess.h> /* copy_from_user, copy_to_user */ #include <linux/errno.h> /* EFAULT */ #include <linux/fs.h> /* register_chrdev, unregister_chrdev */ #include <linux/jiffies.h> #include <linux/module.h> #include <linux/printk.h> /* printk */ #include <uapi/linux/stat.h> /* S_IRUSR */ #define NAME "lkmc_character_device" MODULE_LICENSE("GPL"); static int major; static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off) { size_t ret; char kbuf[] = {'a', 'b', 'c', 'd'}; ret = 0; if (*off == 0) { if (copy_to_user(buf, kbuf, sizeof(kbuf))) { ret = -EFAULT; } else { ret = sizeof(kbuf); *off = 1; } } return ret; } static const struct file_operations fops = { .owner = THIS_MODULE, .read = read, }; static int myinit(void) { major = register_chrdev(0, NAME, &fops); return 0; } static void myexit(void) { unregister_chrdev(major, NAME); } module_init(myinit) module_exit(myexit) 

Программа тестирования Userland:

 insmod /character_device.ko dev="lkmc_character_device" major="$(grep "$dev" /proc/devices | cut -d ' ' -f 1)" mknod "/dev/$dev" c "$major" 0 cat /dev/lkmc_character_device # => abcd rm /dev/lkmc_character_device rmmod character_device 

GitHub QEMU + Buildroot вверх по течению с шаблоном для его запуска:

Более сложные примеры:

Символьные устройства могут быть созданы модулями ядра (или самим ядром). Когда устройство создается, создатель предоставляет указатели на функции, которые реализуют стандартные вызовы дескрипторов, такие как открытые, прочитанные и т. Д. Ядро Linux затем связывает эти функции с символьным устройством, например, когда приложение пользовательского режима вызывает чтение () функции в файле символьного устройства, это приведет к сбою системы, а затем ядро ​​направит этот вызов на функцию чтения, указанную при создании драйвера. Здесь есть пошаговое руководство по созданию символьного устройства, вы можете создать образец проекта и шаг через него с помощью отладчика, чтобы понять, как создается объект устройства и когда вызываются обработчики.