Файлы устройств
Файл - основа любой операционной системы, поскольку именно с ним производится наибольшее число действий. В UNIX- и POSIX-системах существуют файлы следующих типов:
Блок-ориентированный файл устройства служит для представления физического устройства, которое передает данные блоками. Примером блок-ориентированного устройства является жесткий диск. Байт-ориентированный файл устройства служит для представления физического устройства, которое передает данные побайтово (например, модем).
Прикладная программа может выполнять операции чтения и записи с файлом устройства так же, как с обычным файлом, а операционная система будет автоматически вызывать соответствующий драйвер устройства для выполнения фактической передачи данных между физическим устройством и приложением.
Файл устройства создается командой mknod, одним из аргументов которой является старший номер устройства (major device number). По сути старший номер - это индекс в таблице ядра, которая содержит адреса всех драйверов, известных системе. В ОС Linux создаются две таблицы - таблица блочных устройств (block device switch) и таблица символьных устройств (character device switch). Обе таблицы являются массивом структур и проиндексированы при помощи значения старшего номера устройства. Таблица блочных устройств определена в файле fs/block_dev.c следующим образом:
static struct { const char *name; struct block_device_operations *bdops; } blkdevs[MAX_BLKDEV];
Этот массив заполняется во время регистрации блочного устройства в системе. Для регистрации устройства соответствующий драйвер вызывает функцию register_blkdev (см. файл fs/block_dev.c):
int register_blkdev(unsigned int major, const char * name, struct block_device_operations *bdops) { .... blkdevs[major].name = name; blkdevs[major].bdops = bdops; return 0; }
Аргумент major - старший номер устройства, name - имя файла устройства, структура struct block_device_operations содержит функции, выполняемые драйвером устройства. Однако функции read и write в этой структуре отсутствуют. Дело в том, что пользовательский процесс не выполняет напрямую операции чтения/записи в блочное устройства. Для этой цели драйвер предоставляет системе механизм request, и все операции ввода/вывода выполняются через буферный кеш системы, но это тема для отдельной статьи.
При снятии регистрации соответствующий элемент массива blkdevs обнуляется:
int unregister_blkdev(unsigned int major, const char * name) { .... blkdevs[major].name = NULL; blkdevs[major].bdops = NULL; return 0; }
Таблица символьных устройств определена в файле fs/devices.c и также является массивом структур, который заполняется при регистрации устройства в системе:
struct device_struct { const char * name; struct file_operations * fops; };
static struct device_struct chrdevs[MAX_CHRDEV];
Структура struct file_operations определена в файле linux/fs.h и содержит функции, выполняемые драйвером символьного устройства.
Когда пользовательский процесс читает данные из файла устройства или записывает их, ядро, используя старший номер устройства в качестве индекса, находит в соответствующей таблице нужную процедуру драйвера и выполняет запрашиваемое действие.
Кроме операций чтения/записи, драйвер также предоставляет возможность управления устройством. Операция управления осуществляется при помощи функции ioctl. Эта функция вызывается пользовательским процессом и имеет следующий прототип:
int ioctl(int fd, int cmd, ...);
Аргументы функции:
int fd - файловый дескриптор устройства;
int cmd - команда, посылаемая устройству.
Третий параметр является специфичным для каждого устройства, поэтому в прототипе функции не указан.