Сожалеем, но текст оригинала доступен только зарегистрированным пользователям. |
|
|
Чтобы ядро могло работать с драйвером блочного устройства, он, как и драйвер символьного устройств, должен быть зарегистрирован. Процессы регистрации схожи, но некоторые детали отличаются. Вы познакомитесь с различыми структурами данных и с операциями над устройством.
hardorsoft
25.10.10 в 13:07
|
|
Регистрация блочного устройства
hardorsoft
25.10.10 в 13:07
|
|
Первым делом большинство драйверов блочных устройств регистрируют себя в ядре. Для этого используется функция register_blkdev (которая декларируется в <linux></linux>fs.h>).
hardorsoft
25.10.10 в 13:07
|
|
int register_blkdev(unsigned int major, const char *name);
hardorsoft
25.10.10 в 13:08
|
|
Аргументами являются старший номер устройства, и ассоциируемое имя, которое ядро изображает в /proc/devices. Если старший номер устройства задан равным нулю, то он динамически выделяется ядром и функция возвращает его значение. Как всегда, возврат отрицательного значения сигнализирует об ошибке.
hardorsoft
25.10.10 в 13:08
|
|
Для отмены регистрации используется функция
hardorsoft
25.10.10 в 13:08
|
|
int unregister_blkdev(unsigned int major, const char *name);
hardorsoft
25.10.10 в 13:09
|
|
Аргументы функции должны быть такими же, как и в register_blkdev, иначе функция не отменит регистрацию и возвратит значение -EINVAL.
hardorsoft
25.10.10 в 13:09
|
|
В ядре 2.6 вызов register_blkdev - опционален. Со временем, функций выполняющих этот вызов становится меньше. Функция выполняет динамическое выделение старшего номера, если требуется, и создает точку входа в /proc/devices. В будущих версиях ядра функция register_blkdev может быть убрана. Но традиционно многие драйверы вызывают ее.
hardorsoft
25.10.10 в 13:10
|
|
Регистрация диска Функция register_blkdev используется для получения старшего номера, но не делает диск доступным в системе. Происходит разделение регистрационного интерфейса, которое должно вами использоваться для индивидуального управления драйверами. Использование этого интерфейса подразумевает знание пары новых структур, с них мы и начнем.
hardorsoft
25.10.10 в 13:10
|
|
Операции блочного устройства
hardorsoft
25.10.10 в 13:10
|
|
Символьные устройства используют структуру file_operations, чтобы сообщить системе о доступных операциях. Аналогичная структура используется и для блочных устройств. Она называется block_device_operations, и описана в <linux></linux>fs.h>. Ниже приведено краткое описание полей структуры. Позже, при подробном рассмотрении драйвера sbull, мы вернемся к описанию этой структуры.
hardorsoft
25.10.10 в 13:11
|
|
int (*open)(struct inode *inode, struct file *filp); int (*release)(struct inode *inode, struct file *filp); Функции работают подобно их эквивалентам для символьных устройств. Вызываются для открытия и закрытия устройства. При открытии драйвер может раскручивать устройство, блокировать лоток (для сменных носителей). Если при открытии выполняется блокирование устройства, то при закрытии необходимо его разблокировать.
hardorsoft
25.10.10 в 13:12
|
|
int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); Метод реализует системный вызов ioctl. Блочный уровень первым перехватывает старший номер стандартного запроса, однако метод ioctl у многих драйверов довольно краток.
hardorsoft
25.10.10 в 13:12
|
|
int (*media_changed) (struct gendisk *gd); Метод вызывается ядром при проверке, не заменил ли пользователь носитель в устройстве. Возвращает не нулевое значение, если заменил. Очевидно, что метод применяется только для устройств поддерживающих сменные носители (для этого достаточно установить флаг «сменный носитель»). В других случаях метод можно опустить.
hardorsoft
25.10.10 в 22:11
|
|
Аргумент struct gendisk, информирующей ядро об одном диске, будет рассмотрен в следующем подразделе.
hardorsoft
25.10.10 в 22:13
|
|
struct module *owner; Указатель на модуль, который является владельцем этой структуры. Обычно инициализируется в значение THIS_MODULE.
hardorsoft
25.10.10 в 13:14
|
|
При внимательном прочтении можно обратить внимание на оплошность в списке: там нет функций чтения и записи данных. В подсистеме блочного ввода-вывода эти операции обрабатываются функцией request, которая заслуживает большого подраздела и будет расмотрена позже в этой главе. Но перед тем как поговорить об обслуживании запросов, мы должны закончить разговор о регистрации дисков.
hardorsoft
25.10.10 в 13:17
|
|
|
|
|
Структура struct gendisk определена в <linux></linux>genhd.h> и служит для предоставления ядру индивидуального дискового устройства. В действительности, ядро использует структуру struct gendisk для получения информации о разделах, но для разработчиков драйвера это не важно. Вот поля, которые должны инициализироваться драйвером:
hardorsoft
25.10.10 в 22:15
|
|
int major; int first_minor; int minors; Эти поля описывают номера устройств используемых диском. Как минимум используется один младший (minor) номер. Если ваше устройство имеет разделы, вы можете использовать по одному младшему номеру на раздел. Для minors обычно устанавливается значение 16, которое позволяет представить диск целиком и 15 разделов. Некоторые дисковые драйвера используют 64 младших номера для устройства.
hardorsoft
25.10.10 в 22:15
|
|
char disk_name[32]; Поле задает имя дискового /proc/partitions и в sysfs.
hardorsoft
25.10.10 в 22:16
|
|
struct block_device_operations *fops; Содержит множество операций над устройством из предыдущего подраздела.
hardorsoft
25.10.10 в 22:16
|
|
struct request_queue *queue; Структура используется ядром для управления запросами ввода- вывода. Мы рассмотрим это в подразделе «Выполнение запросов».
hardorsoft
25.10.10 в 22:16
|
|
int flags; Множество флагов, описывающих состояние устройства (малоиспользуется). Для устройств со сменным носителем должен устанавливаться флаг GENHD_FL_REMOVABLE. Для устройства CD-ROM – GENHD_FL_CD. Если по каким-то соображениям вы не хотите показывать информацию о разделах в /proc/partitions, установите GENHD_FL_SUPPRESS_PARTITION_INFO.
hardorsoft
25.10.10 в 22:17
|
|
sector_t capacity; Емкость устройства в секторах по 512 байт. sector_t может иметь разрядность 64 бита. Драйвер не устанавливает это поле непосредственно, для этого используется функция set_capacity.
hardorsoft
25.10.10 в 22:17
|
|
void *private_data; Драйвер блочного устройства заносит в это поле указатель на внутренние данные.
hardorsoft
25.10.10 в 22:17
|
|
Ядро предоставляет несколько функций для работы со структурой gendisk. Мы приведем их здесь, а затем покажем, как sbull их использует, чтобы сделать доступным в системе дисковое устройство. Структура gendisk создается динамически при инициализации драйвера, с помощью специальной функции ядра. Драйвер не должен создавать ее по-другому. Вот эта функция: struct gendisk *alloc_disk(int minors);
hardorsoft
25.10.10 в 22:18
|
|
Аргумент minors должен содержать значение младшого номера, используемое этим диском. Обратите внимание, что впоследствии нельзя менять это значение, и надеяться на корректную работу.
hardorsoft
25.10.10 в 22:19
|
|
Когда диск больше не нужен, он может быть удален вызовом:
hardorsoft
25.10.10 в 22:19
|
|
void del_gendisk(struct gendisk *gd);
hardorsoft
25.10.10 в 22:19
|
|
Структура gendisk содержит счетчик использования (kobject). Имеются функции get_disk и put_disk для управления счетчиком, но драйвер их не использует. Обычно вызов del_gendisk удаляет заключительную ссылку на структуру, но не гарантирует этого. Поэтому возможно, что структура будет продолжать существовать (и ваши методы могут быть вызваны) и после вызова del_gendisk. Тем не менее, если вы удалите структуру, когда она не используется (после последнего release или функции cleanup вашего модуля), то можете быть уверены что не услышете о ней больше.
hardorsoft
25.10.10 в 22:20
|
|
Выделение структуры не делает диск доступным. Для этого нужно инициализировать структуру и вызвать функцию add_disk:
hardorsoft
25.10.10 в 22:20
Выделение структуры не делает диск доступным. Для этого нужно инициализировать структуру и вызвать функцию add_disk:
hardorsoft
25.10.10 в 22:20
|
|
void add_disk(struct gendisk *gd);
hardorsoft
25.10.10 в 22:20
|
|
Наиболее важное из того, что вы должны понять здесь, это то, что сразу после вызова add_disk диск начинает «жить» и его методы могут быть вызваны. Фактически, первый вызов произойдет до возврата из add_disk, Ядро прочитает несколько первых секторов, пытаясь найти таблицу разделов. Так что не вызывайте add_disk до тех пор, пока драйвер не закончит инициализацию и не будет готов отвечать на запросы.
hardorsoft
25.10.10 в 22:20
|
|
|
|
|
Наступило время привести пример. Драйвер sbull (доступен на FTP сайта O'Reilly вместе с исходными кодами) реализует множество виртуальных дисков. Для каждого накопителя выделяется массив памяти (с помощью vmalloc, для упрощения), затем этот массив делается доступным как блочное устройство. Для проверки работы драйвера sbull, можно разбить виртуальное устройства на разделы, построить файловую систему, и выполнить монтирование.
hardorsoft
25.10.10 в 22:21
|
|
Как и другие примеры наших драйверов, sbull позволяет определить старший номер устройства и во время компиляции, и при загрузке модуля. Если номер не определен, он выделяется динамически. Вызов register_blkdev для динамического выделения производится так:
hardorsoft
25.10.10 в 22:21
|
|
sbull_major = register_blkdev(sbull_major, "sbull"); if (sbull_major <= 0) { printk(KERN_WARNING "sbull: unable to get major number\n"); return -EBUSY; }
hardorsoft
25.10.10 в 22:21
|
|
Как и другие виртуальные устройства из этой книги, у sbull есть структура для внутреннего использования:
hardorsoft
25.10.10 в 22:21
|
|
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; /* Для симуляции смены носителя */ };
hardorsoft
25.10.10 в 22:22
|
|
Необходимо несколько шагов, чтобы инициализировать структуру и сделать устройство доступным в системе. Начнем с базовой инициализации выделения небольшого количества памяти:
hardorsoft
25.10.10 в 22:22
|
|
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);
hardorsoft
25.10.10 в 22:23
|
|
Важно отметить выделение и инициализация spinlock перед следующим шагом, на котором выделяется очередь запросов. Мы рассмотрим этот процесс подробнее, когда дойдем до обработки запросов. Итак, подытожив сказанное:
hardorsoft
25.10.10 в 22:23
|
|
dev->queue = blk_init_queue(sbull_request, &dev->lock);
hardorsoft
25.10.10 в 22:23
|
|
Здесь sbull_request – наша функция обработки запросов request. Она выполняет запросы на чтение и запись. Когда мы выделили очередь запросов, мы можем предоставить spinlock, контролирующий доступ к очереди. Объект для блокировки предоставляется драйвером, а не ядром, т.к. очередь запросов и другие структуры данных драйвера находятся в одной критической секции, и часто обращение к ним происходит ко всем вместе. Как и любая функция, выделяющая память, blk_init_queue может потерпеть неудачу, поэтому необходимо проверять возвращаемое значение.
hardorsoft
25.10.10 в 22:23
|
|
Раз мы имеем устройство хранения, и выделили очередь запросов, мы можем выделять, инициализировать и инсталлировать соответствующую структуру gendisk. Вот код который это производит:
hardorsoft
25.10.10 в 22:24
|
|
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);
hardorsoft
25.10.10 в 22:24
|
|
SBULL_MINORS – это младшее число устройства. При назначении младшего номера устройству, мы должны учитывать задействованные ранее номера. Имя диска устанавливается так: первый – sbulla, второй – sbullb, и т.д. Затем, в пространстве пользователя, можно добавить разделы. Так третий раздел второго устройства может быть /dev/sbullb3.
hardorsoft
25.10.10 в 22:24
|
|