Готовый перевод Linux Device Drivers 3 / Драйверы устройств Линукс (третье издание): 16.1. Регистрация

Внимание! Этот перевод, возможно, ещё не готов, так как модераторы установили для него статус «перевод редактируется»

Регистрация

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

Регистрация блочного устройства

Первым делом большинство драйверов блочных устройств
регистрируют себя в ядре. Для этого используется функция
register_blkdev (которая декларируется в <linux></linux>fs.h>).

int register_blkdev(unsigned int major, const char *name);

Аргументами являются старший номер устройства, и
ассоциируемое имя, которое ядро изображает в /proc/devices. Если
старший номер устройства задан равным нулю, то он динамически
выделяется ядром и функция возвращает его значение. Как всегда,
возврат отрицательного значения сигнализирует об ошибке.

Для отмены регистрации используется функция

int unregister_blkdev(unsigned int major, const char *name);

Аргументы функции должны быть такими же, как и в
register_blkdev, иначе функция не отменит регистрацию и возвратит
значение -EINVAL.

В ядре 2.6 вызов register_blkdev - опционален. Со временем,
функций выполняющих этот вызов становится меньше. Функция
выполняет динамическое выделение старшего номера, если требуется, и
создает точку входа в /proc/devices. В будущих версиях ядра функция
register_blkdev может быть убрана. Но традиционно многие драйверы
вызывают ее.

Регистрация диска
Функция register_blkdev используется для получения старшего
номера, но не делает диск доступным в системе. Происходит разделение
регистрационного интерфейса, которое должно вами использоваться для
индивидуального управления драйверами. Использование этого
интерфейса подразумевает знание пары новых структур, с них мы и
начнем.

Операции блочного устройства

Символьные устройства используют структуру file_operations,
чтобы сообщить системе о доступных операциях. Аналогичная структура
используется и для блочных устройств. Она называется
block_device_operations, и описана в <linux></linux>fs.h>. Ниже приведено
краткое описание полей структуры. Позже, при подробном рассмотрении драйвера sbull, мы вернемся к описанию этой структуры.

int (*open)(struct inode *inode, struct file *filp);
int (*release)(struct inode *inode, struct file *filp);
Функции работают подобно их эквивалентам для символьных
устройств. Вызываются для открытия и закрытия устройства. При
открытии драйвер может раскручивать устройство, блокировать
лоток (для сменных носителей). Если при открытии выполняется
блокирование устройства, то при закрытии необходимо его
разблокировать.

int (*ioctl)(struct inode *inode, struct file *filp, unsigned int
cmd, unsigned long arg);
Метод реализует системный вызов ioctl. Блочный уровень первым
перехватывает старший номер стандартного запроса, однако метод
ioctl у многих драйверов довольно краток.

int (*media_changed) (struct gendisk *gd);
Метод вызывается ядром при проверке, не заменил ли
пользователь носитель в устройстве. Возвращает не нулевое
значение, если заменил. Очевидно, что метод применяется только
для устройств поддерживающих сменные носители (для этого
достаточно установить флаг «сменный носитель»). В других
случаях метод можно опустить.

Аргумент struct gendisk, информирующей ядро об одном диске,
будет рассмотрен в следующем подразделе.

struct module *owner;
Указатель на модуль, который является владельцем этой
структуры. Обычно инициализируется в значение THIS_MODULE.

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

Структура gendisk

Структура struct gendisk определена в <linux></linux>genhd.h> и служит
для предоставления ядру индивидуального дискового устройства. В
действительности, ядро использует структуру struct gendisk для
получения информации о разделах, но для разработчиков драйвера это не
важно. Вот поля, которые должны инициализироваться драйвером:

int major;
int first_minor;
int minors;
Эти поля описывают номера устройств используемых диском. Как минимум используется один младший (minor) номер. Если ваше
устройство имеет разделы, вы можете использовать по одному
младшему номеру на раздел. Для minors обычно устанавливается
значение 16, которое позволяет представить диск целиком и 15
разделов. Некоторые дисковые драйвера используют 64 младших
номера для устройства.

char disk_name[32];
Поле задает имя дискового
/proc/partitions и в sysfs.

struct block_device_operations *fops;
Содержит множество операций над устройством из предыдущего
подраздела.

struct request_queue *queue;
Структура используется ядром для управления запросами ввода-
вывода. Мы рассмотрим это в подразделе «Выполнение запросов».

int flags;
Множество флагов, описывающих состояние устройства
(малоиспользуется). Для устройств со сменным носителем должен
устанавливаться флаг GENHD_FL_REMOVABLE. Для устройства CD-ROM
– GENHD_FL_CD. Если по каким-то соображениям вы не хотите
показывать информацию о разделах в /proc/partitions,
установите GENHD_FL_SUPPRESS_PARTITION_INFO.

sector_t capacity;
Емкость устройства в секторах по 512 байт. sector_t может иметь
разрядность 64 бита. Драйвер не устанавливает это поле
непосредственно, для этого используется функция set_capacity.

void *private_data;
Драйвер блочного устройства заносит в это поле указатель на
внутренние данные.

Ядро предоставляет несколько функций для работы со структурой
gendisk. Мы приведем их здесь, а затем покажем, как sbull их
использует, чтобы сделать доступным в системе дисковое устройство.
Структура gendisk создается динамически при инициализации
драйвера, с помощью специальной функции ядра. Драйвер не должен
создавать ее по-другому. Вот эта функция:
struct gendisk *alloc_disk(int minors);

Аргумент minors должен содержать значение младшого номера,
используемое этим диском. Обратите внимание, что впоследствии нельзя
менять это значение, и надеяться на корректную работу.

Когда диск больше не нужен, он может быть удален вызовом:

void del_gendisk(struct gendisk *gd);

Структура gendisk содержит счетчик использования (kobject).
Имеются функции get_disk и put_disk для управления счетчиком, но
драйвер их не использует. Обычно вызов del_gendisk удаляет
заключительную ссылку на структуру, но не гарантирует этого. Поэтому
возможно, что структура будет продолжать существовать (и ваши методы
могут быть вызваны) и после вызова del_gendisk. Тем не менее, если вы
удалите структуру, когда она не используется (после последнего release
или функции cleanup вашего модуля), то можете быть уверены что не
услышете о ней больше.

Выделение структуры не делает диск доступным. Для этого нужно
инициализировать структуру и вызвать функцию add_disk:

void add_disk(struct gendisk *gd);

Наиболее важное из того, что вы должны понять здесь, это то, что
сразу после вызова add_disk диск начинает «жить» и его методы могут
быть вызваны. Фактически, первый вызов произойдет до возврата из
add_disk, Ядро прочитает несколько первых секторов, пытаясь найти
таблицу разделов. Так что не вызывайте add_disk до тех пор, пока
драйвер не закончит инициализацию и не будет готов отвечать на
запросы.

Инициализация sbull

Наступило время привести пример. Драйвер sbull (доступен на FTP
сайта O'Reilly вместе с исходными кодами) реализует множество
виртуальных дисков. Для каждого накопителя выделяется массив памяти
(с помощью vmalloc, для упрощения), затем этот массив делается
доступным как блочное устройство. Для проверки работы драйвера sbull,
можно разбить виртуальное устройства на разделы, построить файловую
систему, и выполнить монтирование.

Как и другие примеры наших драйверов, sbull позволяет
определить старший номер устройства и во время компиляции, и при
загрузке модуля. Если номер не определен, он выделяется динамически.
Вызов register_blkdev для динамического выделения производится так:

sbull_major = register_blkdev(sbull_major, "sbull");
if (sbull_major &lt;= 0) {
printk(KERN_WARNING "sbull: unable to get major number\n");
return -EBUSY;
}

Как и другие виртуальные устройства из этой книги, у sbull есть
структура для внутреннего использования:

struct sbull_dev {
int size; /* Размер устройства в секторах */
u8 *data; /* Массив даных */
short users; /* Количество пользователей */
short media_change; /* Флаг смены носителя */
spinlock_t lock; /* Взаимо исключающая блокировка*/
struct request_queue *queue; /* Очередь запросов */
struct gendisk *gd; /* Структура gendisk */
struct timer_list timer; /* Для симуляции смены носителя */
};

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

memset (dev, 0, sizeof (struct sbull_dev));
dev->size = nsectors*hardsect_size;
dev->data = vmalloc(dev->size);
if (dev->data = = NULL) {
printk (KERN_NOTICE "vmalloc failure.\n");
return;
}
spin_lock_init(&dev->lock);

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

dev->queue = blk_init_queue(sbull_request, &dev->lock);

Здесь sbull_request – наша функция обработки запросов request.
Она выполняет запросы на чтение и запись. Когда мы выделили очередь
запросов, мы можем предоставить spinlock, контролирующий доступ к
очереди. Объект для блокировки предоставляется драйвером, а не ядром,
т.к. очередь запросов и другие структуры данных драйвера находятся в
одной критической секции, и часто обращение к ним происходит ко всем
вместе. Как и любая функция, выделяющая память, blk_init_queue
может потерпеть неудачу, поэтому необходимо проверять возвращаемое
значение.

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

dev->gd = alloc_disk(SBULL_MINORS);
if (! dev->gd) {
printk (KERN_NOTICE "alloc_disk failure\n");
goto out_vfree;
}
dev->gd->major = sbull_major;
dev->gd->first_minor = which*SBULL_MINORS;
dev->gd->fops = &sbull_ops;
dev->gd->queue = dev->queue;
dev->gd->private_data = dev;
snprintf (dev->gd->disk_name, 32, "sbull%c", which + 'a');
set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
add_disk(dev->gd);

SBULL_MINORS – это младшее число устройства. При назначении
младшего номера устройству, мы должны учитывать задействованные
ранее номера. Имя диска устанавливается так: первый – sbulla, второй –
sbullb, и т.д. Затем, в пространстве пользователя, можно добавить
разделы. Так третий раздел второго устройства может быть /dev/sbullb3.

Все это заканчивается вызовом add_disk. Могут произойти
несколько вызовов наших методов до возврата из add_disk, поэтому вызов add_disk производится последним шагом при инициализации нашего
устройства.

Замечание по размеру сектора

Как мы сказали раньше, ядро работает с каждым диском как с
линейным массивом, состоящим из 512-байтных секторов. Однако не все
устройства используют этот размер сектора. Мы обсудим, как
использовать устройства с другим размером сектора, это не очень сложно.
Устройство sbull экспортирует параметр hardsect_size, который может
быть использован для определения размера аппаратного сектора
устройства. Рассмотрев наш драйвер, вы поймете, как вам сделать
поддержку таких устройств.

В первую очередь нужно проинформировать ядро о размере сектора
вашего устройства. Сначала аппаратный размер сектора устанавливается
для очереди запросов, а затем задается в структуре gendisk.
Непосредственно после создания очереди, задайте размер сектора,
используя вызов blk_queue_hardsect_size:

blk_queue_hardsect_size(dev->queue, hardsect_size);
После этого ядро закрепляет за вашим устройством указанный
размер сектора. Все запросы ввода-вывода выравниваются по началу
аппаратного сектора. И длина каждого запроса составляет целое
количество секторов. Однако вы должны помнить, что внутри ядра всегда
используется 512-байтный сектор. Это необходимо при передаче общего
количества секторов. Например, sbull устанавливает емкость устройства
в структуре gendisk, так:

set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));

KERNEL_SECTOR_SIZE – локально определенная константа, которую мы
используем для масштабирования между 512-байтными секторами ядра, и
нашим размером. Подобные вычисления производятся часто, что мы и
увидим в логике обработки запросов sbull.

Внимание! Этот перевод, возможно, ещё не готов.
Его статус: перевод редактируется

Переведено на Нотабеноиде
http://notabenoid.org/book/11832/38375

Переводчики: hardorsoft

Настройки

Готово:

100.00% КП = 1.0