Linux Device Drivers 3 / Драйверы устройств Линукс (третье издание): 16.3. Обработка запросов

Оглавление
← →
Английский оригинал Перевод на русский

Сожалеем, но текст оригинала доступен только зарегистрированным пользователям.

16.3 Обработка запросов

hardorsoft 25.10.10 в 22:36

0

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

hardorsoft 25.10.10 в 22:36

0

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

hardorsoft 25.10.10 в 22:40

0

16.3.1 Введение в метод request

hardorsoft 25.10.10 в 22:40

0

Прототип метода request:

hardorsoft 25.10.10 в 22:41

0

void request(request_queue_t *queue);

hardorsoft 25.10.10 в 22:41

0

Эта функция вызывается, когда ядро выделяет драйверу время на
выполнение записи, чтения или другой операции над устройством.
Функция не обязана завершить выполнение всех запросов из очереди,
прежде чем закончит работу. Ведь, многие реальные устройства могут не
сразу закончить выполнение запроса. Однако начать исполнение
запросов, и обеспечить, чтобы все они обработались драйвером
необходимо.

hardorsoft 25.10.10 в 22:41

0

Каждое устройство имеет очередь запросов. Это сделано потому, что
передача данных в устройство и из него может занять длительное время.
А ядро должно быть гибким и исполнять операции в наиболее удобное для
этого время (например, вместе группируются запросы относящиеся к
соседним секторам). Как вы помните, функция request ассоциируется с
очередью запросов при ее создании:

hardorsoft 25.10.10 в 22:41

0

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

hardorsoft 25.10.10 в 22:41

0

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

hardorsoft 25.10.10 в 22:42

0

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

hardorsoft 25.10.10 в 22:42

0

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

hardorsoft 25.10.10 в 22:42

0

16.3.2 Простой метод request

hardorsoft 25.10.10 в 22:42

0

В примере драйвера sbull, представлены несколько различные
методы для обработки запросов. По умолчанию, sbull использует метод
sbull_request, который является простым примером request. Вот он:

hardorsoft 25.10.10 в 22:43

0

static void sbull_request(request_queue_t *q)
{
struct request *req;
while ((req = elv_next_request(q)) != NULL) {
struct sbull_dev *dev = req->rq_disk->private_data;
if (! blk_fs_request(req)) {
printk (KERN_NOTICE "Skip non-fs request\n");
end_request(req, 0);
continue;
}
sbull_transfer(dev, req->sector, req->current_nr_sectors,
req->buffer, rq_data_dir(req));
end_request(req, 1);
}
}

hardorsoft 25.10.10 в 22:43

0

В функции используется структура struct request. Структура
передает нам на исполнение запросы блочного ввода-вывода. Позже, мы
рассмотрим ее подробнее.

hardorsoft 25.10.10 в 22:43

0

Ядро предоставляет функцию elv_next_request для получения
первого, не выполненного запроса в очереди. Если в очереди нет запросов
для исполнения, функция возвращает NULL. Имейте в виду, что
elv_next_request не удаляет запрос из очереди. Если вы вызовете ее
дважды без промежуточных операций, то оба раза она вернет указатель
на одну и туже struct request. Это простой режим, в котором запросы
изымаются из очереди, только когда их обработка завершена.

hardorsoft 25.10.10 в 22:44

0

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

hardorsoft 25.10.10 в 22:44

0

void end_request(struct request *req, int succeeded);

hardorsoft 25.10.10 в 22:44

0

Когда мы получаем запрос не от файловой системы, мы заносим в
succeeded значение «0», это показывает, что запрос не может быть
обработан. В противном случае вызывается sbull_transfer для передачи
данных. Для этого структура struct request предоставляет поля:

hardorsoft 25.10.10 в 22:44

0

sector_t sector;

hardorsoft 25.10.10 в 22:44

0

Индекс начального сектора нашего устройства. Помните, что этот
номер сектора, как и все подобные номера сектора, передающиеся
между ядром и драйвером, подразумевают 512-байтный сектор.
Если ваш драйвер использует другой размер сектора, необходимо
сделать соответствующее масштабирование. Например, если
аппаратно используется 2048-байтный сектор, начальный сектор
нужно разделить на 4, перед передачей запроса в аппаратуру.

hardorsoft 25.10.10 в 22:45

0

unsigned long nr_sectors;
Количество передающихся (512-байтных) секторов.

hardorsoft 25.10.10 в 22:45

0

char *buffer;
Указатель на буфер, из которого или в который будут передаваться
данные. Этот указатель на виртуальный адрес ядра, и может быть
непосредственно разыменован (dereferenced) при необходимости.

hardorsoft 25.10.10 в 22:45

0

rq_data_dir(struct request *req);
Этот макрос извлекает направление передачи из запроса. Ноль
означает чтение из устройства, не нулевое значение – запись в
устройство.

hardorsoft 25.10.10 в 22:45

0

Зная эту информацию, в драйвере sbull можно реализовать
передачу данных простым копированием посредством memcpy. Помимо
копирования sbull_transfer производит масштабирование размера
сектора, и не допускает попыток копирования превышающих границу
емкости устройства.

hardorsoft 25.10.10 в 22:45

0

static void sbull_transfer(struct sbull_dev *dev, unsigned long
sector,
unsigned long nsect, char *buffer, int write)
{
unsigned long offset = sector*KERNEL_SECTOR_SIZE;
unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE;
if ((offset + nbytes) > dev->size) {
printk (KERN_NOTICE "Beyond-end write (%ld %ld)\n", offset,
nbytes);
return;
}
if (write)
memcpy(dev->data + offset, buffer, nbytes);
else
memcpy(buffer, dev->data + offset, nbytes);
}

hardorsoft 25.10.10 в 22:46

0

С этим кодом в sbull реализуется законченный драйвер простого
RAM-диска. Однако это все еще не настоящий драйвер, он не может
применяться для многих типов устройств по двум причинам.

hardorsoft 25.10.10 в 22:46

0

Во-первых, sbull выполняет запросы синхронно, по одному.
Высокопроизводительное дисковое устройство может обслуживать
несколько запросов одновременно. Контроллер диска может выбирать
наиболее оптимальную последовательность обработки запросов.
Обрабатывая только первый запрос в очереди, мы никогда не обработаем
одновременно несколько запросов. Для использования возможности
работать более чем с одним запросом, необходимо изучить очередь
запросов и структуру request. В этом помогут следующие подразделы.

hardorsoft 25.10.10 в 22:46

0

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

hardorsoft 25.10.10 в 22:47

0

Однако драйвер sbull просто игнорирует эту информацию. Только
один буфер передается одновременно. Это означает, что размер одной
передачи почти никогда не превысит размера одной страницы. Драйвер
блочного устройства можно сделать намного лучше, но для этого
требуется глубокое понимание структур request и bio, из которых
состоят запросы.

hardorsoft 25.10.10 в 22:47

0

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

hardorsoft 25.10.10 в 22:47

0

16.3.3 Очереди запросов

hardorsoft 25.10.10 в 22:47

0

Для простоты можно сказать, что очередь запросов состоит из
блоков для ввода-вывода. Если разобраться глубже, то очередь запросов
оказывается неожиданно сложной структурой данных. К счастью,
большинство этих сложностей драйверу нужно.

hardorsoft 25.10.10 в 22:47

0

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

hardorsoft 25.10.10 в 22:48

0

Очередь запросов так же реализует сменный интерфейс, который
позволяет использовать множество планировщиков (или лифтов) ввода-
вывода. Работа планировщика заключается в передаче драйверу запросов
таким образом, чтобы обеспечить максимум производительности. Многие
планировщики накапливают пакет запросов, сортируют их в
возрастающем (или убывающем) порядке индексов блоков, и предают
драйверу запросы в новой последовательности. Головки диска, когда
получают сортированный список запросов, работают от одного конца
диска до другого, что очень похоже на движение наполненного лифта в
одном направлении, пока все запросы (люди ожидающие выхода) не будут
удовлетворены. Ядро 2.6 содержит планировщик с ограничением по
времени (т.е. он пытается гарантировать, что каждый запрос будет
удовлетворен за заранее установленное максимальное время) и
предупреждающий планировщик (который не на долго приостанавливает
устройство после выполнения запроса в ожидании следующего).
Например, при записи, по-умолчанию используется предупреждающий
планировщик, который дает лучшую производительность при системом
взаимодействии.

hardorsoft 25.10.10 в 22:48

0

Так же планировщик выполняет объединение смежных запросов.
Когда обрабатывается новый запрос ввода-вывода, планировщик ищет в
очереди смежные сектора. Если найден один, и если результирующий
запрос получается не слишком длинным, то два запроса – объединяются.

hardorsoft 25.10.10 в 22:49

0

В очередях запросов используется тип struct request_queue или
request_queue_t. Этот тип и функции для работы с ним обьявлены в
файле <linux></linux>blkdev.h>. Если вам интересна реализация очереди
запросов, то многое можно подчерпнуть из файлов
drivers/block/ll_rw_block.c и elevator.c

hardorsoft 25.10.10 в 22:49

0

16.3.3.1 Создание и удаление очереди

hardorsoft 25.10.10 в 22:49

0

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

hardorsoft 25.10.10 в 22:49

0

request_queue_t *blk_init_queue(request_fn_proc *request, spinlock_t *lock);

hardorsoft 25.10.10 в 22:50

0

Аргументами является функция request и spinlock для контроля
доступа к очереди. Функция выполняет выделение памяти, поэтому может
претерпеть неудачу. Вы должны всегда проверять возвращаемое значение
перед использованием очереди.

hardorsoft 25.10.10 в 22:50

0

При инициализации очереди запросов вы должны установить поле
queuedata (которое является указателем void*) в любое необходимое вам
значение. Это поле эквивалентно полю private_data, которое
рассматривалось в других структурах.

hardorsoft 25.10.10 в 22:50

0

Для возвращения очереди запросов системе (в основном, при
выгрузке модуля) вызывается:

hardorsoft 25.10.10 в 22:50

0

void blk_cleanup_queue(request_queue_t *);

hardorsoft 25.10.10 в 22:50

0

После этого вызова, ваш драйвер не получает запросов из этой
очереди и не обращается к ней.

hardorsoft 25.10.10 в 22:51

0

16.3.3.2 Функции работы с очередью

hardorsoft 25.10.10 в 22:51

0

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

hardorsoft 25.10.10 в 22:51

0

Функция возвращает следующий запрос из очереди для обработки:

hardorsoft 25.10.10 в 22:51

0

struct request *elv_next_request(request_queue_t *queue);

hardorsoft 25.10.10 в 22:52

0
Минутку...

Следующая страница →