16.2. Операции блочного устройства |
В предыдущем разделе мы кратко рассмотрели структуру block_device_operations. Теперь нам потребуется некоторое время, чтобы взглянуть на эти операции немного более подробно, прежде чем переходить к обработке запроса. Теперь настало время отметить ещё одну особенностью драйвера sbull: он претендует быть сменным устройством. Всякий раз, когда последний пользователь закрывает устройство, запускается 30-ти секундный таймер; если устройство не открыли в течение этого времени, содержимое устройства очищается и ядру будет сообщено, накопитель сменили. 30-ти секундная задержка даёт пользователю время, например, смонтировать устройство sbull после создания на нём файловой системы.
Для реализации моделирования смены носителя информации, sbull должен знать, когда устройство закроет последний пользователь. Драйвером поддерживается учёт пользователей. Работа по сохранению текущего значения счётчика выполняется методами open и close.
Метод open очень похож на свой эквивалент для символьного драйвера; он принимает в качестве аргументов соответствующие указатели на структуры inode и file. Когда inode ссылается на блочное устройство, поле i_bdev->bd_disk содержит указатель на соответствующую структуру gendisk; этот указатель может быть использован для получения внутренних структур данных драйвера устройства. Это, фактически, первое, что делает sbull в методе open:
static int sbull_open(struct inode *inode, struct file *filp)
{
struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
del_timer_sync(&dev->timer);
filp->private_data = dev;
spin_lock(&dev->lock);
if (! dev->users)
check_disk_change(inode->i_bdev);
dev->users++;
spin_unlock(&dev->lock);
return 0;
}
После того, как sbull_open получила указатель на свою структуру устройства, она вызывает del_timer_sync для удаления таймера "носитель заменён", если таковой является активным. Обратите внимание, что мы не выполняем спин-блокировку устройства, пока таймер не был удалён; если поступить иначе, возможна взаимоблокировка, если таймерная функция запустится, прежде чем мы сможем её удалить. При заблокированном устройстве мы вызываем функцию ядра, названную check_disk_change, чтобы проверить не произошла ли смена носителя. Можно возразить, что этот вызов должно сделать ядро, но стандартным шаблоном для драйвера является его обработка во время работы open.
Последний шагом является увеличение счётчика пользователей и возвращение.
Задача метода release, наоборот, уменьшить счётчик пользователей и, если указано, запустить таймер смены носителя:
static int sbull_release(struct inode *inode, struct file *filp)
{
struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
spin_lock(&dev->lock);
dev->users--;
if (!dev->users) {
dev->timer.expires = jiffies + INVALIDATE_DELAY;
add_timer(&dev->timer);
}
spin_unlock(&dev->lock);
return 0;
}
В драйвере, который обрабатывает реальные аппаратные устройства, методы open и release должны бы были установить состояние драйвера и оборудования соответственно. Эта работа может включать ускорение или замедление вращения диска, блокировку дверцы съёмного устройства, выделение буферов DMA и так далее.
Вы можете быть удивлены, кто фактически открывает блочное устройство. Есть несколько операций, которые являются причиной открытия блочного устройства непосредственно из пространства пользователя; к ним относятся разбиение диска, создание файловой системы на разделе или работа контроля файловой системы. Кроме того, блочный драйвер получает вызов open при монтировании раздела. В этом случае нет никакого процесса пространства пользователя, содержащего дескриптор открытого на устройстве файла; открытый файл, вместо того, удерживается самим ядром. Блочный драйвер не может определить разницу между операцией mount (которая открывает устройство из пространства ядра) и вызов из такой утилиты, как mkfs (которая открывает его из пространства пользователя).
Структура block_device_operations включает в себя два метода для поддержки съёмных носителей. Если вы пишете драйвер для неудаляемого устройства, вы можете безболезненно пропустить эти методы. Их реализация относительно проста.
Чтобы увидеть, был ли заменён носитель, вызывается метод media_changed (из check_disk_change); если это произошло, он должен вернуть ненулевое значение. Реализация в sbull проста; она запрашивает флаг, который был установлен, если истёк таймер смены носителя:
int sbull_media_changed(struct gendisk *gd)
{
struct sbull_dev *dev = gd->private_data;
return dev->media_change;
}
После замены носителя вызывается метод revalidate; его работой является сделать всё, что требуется для подготовки драйвера для операций с новым носителем, если это необходимо. После вызова revalidate ядро пытается перечитать таблицу разделов и начать заново работу с устройством. Реализация в sbull просто сбрасывает флаг media_change и обнуляет память устройства, чтобы имитировать подключение пустого диска.
int sbull_revalidate(struct gendisk *gd)
{
struct sbull_dev *dev = gd->private_data;
if (dev->media_change) {
dev->media_change = 0;
memset (dev->data, 0, dev->size);
}
return 0;
}
Блочные устройства могут предоставить для выполнения функций управления устройством метод ioctl. Высокоуровневый код блочной подсистемы перехватывает ряд команд ioctl всегда прежде чем драйвер их получит, однако (смотрите для полного набора drivers/block/ioctl.c в исходных кодах ядра). По сути, современный блочный драйвер может совсем не иметь реализации для очень многих команд ioctl.
Метод ioctl в sbull обрабатывает только одну команду - запрос о конфигурации устройства:
int sbull_ioctl (struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
long size;
struct hd_geometry geo;
struct sbull_dev *dev = filp->private_data;
switch(cmd) {
case HDIO_GETGEO:
/*
* Получить конфигурацию: поскольку мы являемся виртуальным устройством, мы должны сделать
* что-то правдоподобное. Итак, мы заявляем о 16 секторах, четырёх головках,
* и рассчитываем соответствующее число цилиндров. Мы устанавливаем
* начало данных на четвёртом секторе.
*/
size = dev->size*(hardsect_size/KERNEL_SECTOR_SIZE);
geo.cylinders = (size & ~0x3f) >> 6;
geo.heads = 4;
geo.sectors = 16;
geo.start = 4;
if (copy_to_user((void __user *) arg, &geo, sizeof(geo)))
return -EFAULT;
return 0;
}
return -ENOTTY; /* команда неизвестна */
}
Предоставление информации о конфигурации может показаться любопытной задачей, так как наше устройство чисто виртуальное и не имеет ничего общего с дорожками и цилиндрами. Даже самое настоящее блочное оборудование за многие годы было снабжено гораздо более сложными структурами. Ядро не связано с конфигурацией блочного устройства; оно рассматривает его просто как линейный массив секторов. Однако, есть определённые утилиты пространства пользователя, которые всё ещё ожидают возможности для запроса конфигурации диска. В частности, утилита fdisk, которая редактирует таблицы разделов, зависит от информации о цилиндрах и не функционирует должным образом, если такая информация недоступна.
Мы хотели бы, чтобы устройство sbull было допускающим разбиение даже устаревшими простодушными инструментами. Таким образом, мы предоставили метод ioctl, который поставляется с правдоподобной выдуманной конфигурацией, которая могла бы соответствовать ёмкости нашего устройства. Большинство дисковый драйверов делают что-то подобное. Обратите внимание, что, как обычно, если необходимо, счётчик секторов преобразуется, чтобы соответствовать соглашению о 512 байтах, используемому в ядре.
Комментариев нет:
Отправить комментарий
Примечание. Отправлять комментарии могут только участники этого блога.