Как работают символьные устройства или специальные файлы символов?

22

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

bernie2436
источник

Ответы:

19

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

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

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

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

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

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

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

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

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

Shadur
источник
1
1. Значит старшие / младшие номера аналогичны портам?
bernie2436
2. Итак, когда программы обращаются к какому-либо файлу, ядро ​​считывает эти специальные интерфейсы, чтобы узнать, должна ли программа получать прерывания от определенного устройства? Пример: если программа открывает файл слова, она читает специальный файл символьного устройства, чтобы узнать, должна ли программа реагировать на ввод с клавиатуры?
bernie2436
1) В некоторой степени . Это аналогия с бедняком, но это подойдет.
Шадур
2
2) Вам не хватает трех или четырех слоев абстракции там. Программа, в которой вы открываете текстовый файл, не знает и не заботится о том, какое устройство клавиатуры. Связь с базовым оборудованием происходит либо через эмулятор терминала (если вы находитесь в режиме консоли), либо через уровень событий X (если вы находитесь в графическом режиме), который будет прослушивать клавиатуру и другие диски и решать, что делать. если что, чтобы перейти к программе. Я суммирую довольно сложную многослойную систему здесь; Вы могли бы хорошо читать систему X Window в целом.
Шадур
1
Также обратите внимание, что в некоторых разновидностях UN * X есть специальные символьные файлы для устройств хранения; чтение или запись в специальный файл превращается в чтение или запись в последовательность блоков на устройстве. (В последних версиях FreeBSD, те только специальные файлы для устройств хранения данных, там нет ни одного блока специальных файлов.)
10

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

  1. открыто
  2. близко
  3. Читать
  4. Напишите
  5. Стремиться
  6. Расскажи

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

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

echo 'foo' > /dev/some/char
sed ... < /dev/some/char
Игнасио Васкес-Абрамс
источник
6

Минимальный исполняемый file_operationsпример

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

Ключевые идеи:

  • file_operations содержит обратные вызовы для каждого связанного с файлом системного вызова
  • 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)

Пользовательская программа тестирования:

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 вверх по течению с образцом для запуска:

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

Ciro Santilli 新疆 改造 中心 法轮功 六四 事件
источник
Это было очень полезно, спасибо! Только один вопрос, что именно это делает *off = 1;, и почему он установлен 1?
SilverSlash
1
@SilverSlash, что значение передается через несколько readобращений к одному и тому же open(дескриптору файла. Водитель может делать с ним все, что захочет. Обычная семантика должна содержать количество прочитанных байтов. Однако в этом примере у нас просто более простая семантика: 0для первого чтения, 1после первого чтения. Попробуйте запустить его и поставить шаг printk или GDB для его отладки.
Сиро Сантилли 新疆 改造 中 at 法轮功 六四 事件
4

«Персонаж за один раз» является неправильным (как и идея, что символьные устройства обязательно не поддерживают поиск и рассказ). Фактически устройства «блок за раз» (то есть строго ориентированные на запись, такие как стример *) должны быть символьными устройствами. Такова идея о том, что символьное устройство обязательно должно быть недоступным - драйверы символьного устройства определяют полную 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.

Random832
источник
1

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

Базис
источник