Сожалеем, но текст оригинала доступен только зарегистрированным пользователям. |
16.3 Обработка запросов
hardorsoft
25.10.10 в 22:36
|
|
В основе любого блочного драйвера лежит функция request. В этой функции происходит выполнение основных задач. Рассмотрев эту функцию, мы увидим обработку запросов в драйвере.
hardorsoft
25.10.10 в 22:36
|
|
Производительность дискового драйвера может быть критична для производительности системы в целом. Поэтому подсистема блочных устройств должна быть реализована с наилучшей производительностью. Ваш драйвер может управлять и другими устройствами, если это позволит осуществлять более быстрый ввод-вывод. В других случаях, незачем усложнять API драйвера в подсистеме блочного ввода-вывода. Можно написать простую функцию request (что мы вскоре увидим), но если ваш драйвер должен хорошо работать со сложной аппаратурой, то простой функции не получиться.
hardorsoft
25.10.10 в 22:40
|
|
16.3.1 Введение в метод request
hardorsoft
25.10.10 в 22:40
|
|
Прототип метода request:
hardorsoft
25.10.10 в 22:41
|
|
void request(request_queue_t *queue);
hardorsoft
25.10.10 в 22:41
|
|
Эта функция вызывается, когда ядро выделяет драйверу время на выполнение записи, чтения или другой операции над устройством. Функция не обязана завершить выполнение всех запросов из очереди, прежде чем закончит работу. Ведь, многие реальные устройства могут не сразу закончить выполнение запроса. Однако начать исполнение запросов, и обеспечить, чтобы все они обработались драйвером необходимо.
hardorsoft
25.10.10 в 22:41
|
|
Каждое устройство имеет очередь запросов. Это сделано потому, что передача данных в устройство и из него может занять длительное время. А ядро должно быть гибким и исполнять операции в наиболее удобное для этого время (например, вместе группируются запросы относящиеся к соседним секторам). Как вы помните, функция request ассоциируется с очередью запросов при ее создании:
hardorsoft
25.10.10 в 22:41
|
|
dev->queue = blk_init_queue(sbull_request, &dev->lock);
hardorsoft
25.10.10 в 22:41
|
|
Для создания очереди, так же предоставляется spinlock, который захватывается ядром, при вызове функция request. Поэтому функция выполняется атомарно. Необходимо следовать всем правилам для атомарного кода, изложенным в главе 5.
hardorsoft
25.10.10 в 22:42
|
|
Так же блокировка предотвращает появление другого запроса для вашего устройства, во время работы функции request. В некоторых случаях вам может потребоваться снять блокировку, во время работы функции request. Если вы так сделаете, то должны быть уверены, что не произойдет обращения к очереди запросов или другим структурам которые должны быть защищены блокировкой. Так же, перед возвратом из функции, вы должны будете вновь установить блокировку.
hardorsoft
25.10.10 в 22:42
|
|
И последнее, вызов функции request обычно происходит асинхронно к действиям в пользовательском пространстве. Вы не должны полагать, что ядро работает в контексте процесса запросившего текущую операцию. Вам не известно, где находится буфер ввода-вывода текущего запроса, в ядре или в пространстве пользователя. Поэтому любая операция, явно выполняющая обращение в пространство пользователя является ошибкой, и вызовет серьезные проблемы. Вы увидели все, что ваш драйвер должен знать о структуре запроса, передаваемого вам через очередь.
hardorsoft
25.10.10 в 22:42
|
|
16.3.2 Простой метод request
hardorsoft
25.10.10 в 22:42
|
|
В примере драйвера sbull, представлены несколько различные методы для обработки запросов. По умолчанию, sbull использует метод sbull_request, который является простым примером request. Вот он:
hardorsoft
25.10.10 в 22:43
|
|
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
|
|
В функции используется структура struct request. Структура передает нам на исполнение запросы блочного ввода-вывода. Позже, мы рассмотрим ее подробнее.
hardorsoft
25.10.10 в 22:43
|
|
Ядро предоставляет функцию elv_next_request для получения первого, не выполненного запроса в очереди. Если в очереди нет запросов для исполнения, функция возвращает NULL. Имейте в виду, что elv_next_request не удаляет запрос из очереди. Если вы вызовете ее дважды без промежуточных операций, то оба раза она вернет указатель на одну и туже struct request. Это простой режим, в котором запросы изымаются из очереди, только когда их обработка завершена.
hardorsoft
25.10.10 в 22:44
|
|
В очереди блочного устройства могут содержаться запросы не производящие передачу блоков на диск или из него. Такие запросы могут содержать информацию производителя, низкоуровневые операции по диагностики, или инструкции, влияющие на режим работы устройства, такие как режим записи пакетов для записываемых носителей. Многие блочные драйверы не обрабатывают такие запросы, и возвращают признак неудачного исполнения. Именно так и поступает sbull. Вызов функции block_fs_request позволяет нам обнаружить запрос файловой системы, в нем будет только пересылка блоков данных. Если запрос не от файловой системы, мы пропускаем его, вызвав end_request:
hardorsoft
25.10.10 в 22:44
|
|
void end_request(struct request *req, int succeeded);
hardorsoft
25.10.10 в 22:44
|
|
Когда мы получаем запрос не от файловой системы, мы заносим в succeeded значение «0», это показывает, что запрос не может быть обработан. В противном случае вызывается sbull_transfer для передачи данных. Для этого структура struct request предоставляет поля:
hardorsoft
25.10.10 в 22:44
|
|
|
|
|
Индекс начального сектора нашего устройства. Помните, что этот номер сектора, как и все подобные номера сектора, передающиеся между ядром и драйвером, подразумевают 512-байтный сектор. Если ваш драйвер использует другой размер сектора, необходимо сделать соответствующее масштабирование. Например, если аппаратно используется 2048-байтный сектор, начальный сектор нужно разделить на 4, перед передачей запроса в аппаратуру.
hardorsoft
25.10.10 в 22:45
|
|
unsigned long nr_sectors; Количество передающихся (512-байтных) секторов.
hardorsoft
25.10.10 в 22:45
|
|
char *buffer; Указатель на буфер, из которого или в который будут передаваться данные. Этот указатель на виртуальный адрес ядра, и может быть непосредственно разыменован (dereferenced) при необходимости.
hardorsoft
25.10.10 в 22:45
|
|
rq_data_dir(struct request *req); Этот макрос извлекает направление передачи из запроса. Ноль означает чтение из устройства, не нулевое значение – запись в устройство.
hardorsoft
25.10.10 в 22:45
|
|
Зная эту информацию, в драйвере sbull можно реализовать передачу данных простым копированием посредством memcpy. Помимо копирования sbull_transfer производит масштабирование размера сектора, и не допускает попыток копирования превышающих границу емкости устройства.
hardorsoft
25.10.10 в 22:45
|
|
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
|
|
С этим кодом в sbull реализуется законченный драйвер простого RAM-диска. Однако это все еще не настоящий драйвер, он не может применяться для многих типов устройств по двум причинам.
hardorsoft
25.10.10 в 22:46
|
|
Во-первых, sbull выполняет запросы синхронно, по одному. Высокопроизводительное дисковое устройство может обслуживать несколько запросов одновременно. Контроллер диска может выбирать наиболее оптимальную последовательность обработки запросов. Обрабатывая только первый запрос в очереди, мы никогда не обработаем одновременно несколько запросов. Для использования возможности работать более чем с одним запросом, необходимо изучить очередь запросов и структуру request. В этом помогут следующие подразделы.
hardorsoft
25.10.10 в 22:46
|
|
Однако, там рассматривается не только это. Наилучшая производительность дисковых устройств достигается, при передаче множества секторов, расположенных рядом на диске. Наибольшее время в дисковых операциях всегда занимает позиционирование головок для чтения и записи. Время непосредственного чтения или записи – почти незначительно. Разработчики, проектирующие и реализующие файловые системы и подсистемы виртуальной памяти, онимают это, и улучшают их, размещая связанные данные смежно на диске, что делает возможным их передачу в одном запросе. Подсистема блочного ввода-вывода так же помогает в решении этой задачи. Очередь запросов содержит информацию, предназначенную для нахождения смежных запросов и их объединения.
hardorsoft
25.10.10 в 22:47
|
|
Однако драйвер sbull просто игнорирует эту информацию. Только один буфер передается одновременно. Это означает, что размер одной передачи почти никогда не превысит размера одной страницы. Драйвер блочного устройства можно сделать намного лучше, но для этого требуется глубокое понимание структур request и bio, из которых состоят запросы.
hardorsoft
25.10.10 в 22:47
|
|
В следующих подразделах будет рассказано о том, как уровень блочных устройств формирует запросы, и о структурах данных запросов.
hardorsoft
25.10.10 в 22:47
|
|
16.3.3 Очереди запросов
hardorsoft
25.10.10 в 22:47
|
|
Для простоты можно сказать, что очередь запросов состоит из блоков для ввода-вывода. Если разобраться глубже, то очередь запросов оказывается неожиданно сложной структурой данных. К счастью, большинство этих сложностей драйверу нужно.
hardorsoft
25.10.10 в 22:47
|
|
Очередь хранит не выполненные запросы на блочный ввод-вывод. Они так же играют важную роль при создании запросов. Очередь запросов хранить информацию, описывающую тип доступных запросов: их максимальный размер, как много сегментов может быть в запросе, размер аппаратного сектора, требования выравнивания. Если очередь запросов правильно сконфигурирована, то ваше устройство никогда не получит запрос, который не может быть обработан.
hardorsoft
25.10.10 в 22:48
|
|
Очередь запросов так же реализует сменный интерфейс, который позволяет использовать множество планировщиков (или лифтов) ввода- вывода. Работа планировщика заключается в передаче драйверу запросов таким образом, чтобы обеспечить максимум производительности. Многие планировщики накапливают пакет запросов, сортируют их в возрастающем (или убывающем) порядке индексов блоков, и предают драйверу запросы в новой последовательности. Головки диска, когда получают сортированный список запросов, работают от одного конца диска до другого, что очень похоже на движение наполненного лифта в одном направлении, пока все запросы (люди ожидающие выхода) не будут удовлетворены. Ядро 2.6 содержит планировщик с ограничением по времени (т.е. он пытается гарантировать, что каждый запрос будет удовлетворен за заранее установленное максимальное время) и предупреждающий планировщик (который не на долго приостанавливает устройство после выполнения запроса в ожидании следующего). Например, при записи, по-умолчанию используется предупреждающий планировщик, который дает лучшую производительность при системом взаимодействии.
hardorsoft
25.10.10 в 22:48
|
|
Так же планировщик выполняет объединение смежных запросов. Когда обрабатывается новый запрос ввода-вывода, планировщик ищет в очереди смежные сектора. Если найден один, и если результирующий запрос получается не слишком длинным, то два запроса – объединяются.
hardorsoft
25.10.10 в 22:49
|
|
В очередях запросов используется тип struct request_queue или request_queue_t. Этот тип и функции для работы с ним обьявлены в файле <linux></linux>blkdev.h>. Если вам интересна реализация очереди запросов, то многое можно подчерпнуть из файлов drivers/block/ll_rw_block.c и elevator.c
hardorsoft
25.10.10 в 22:49
|
|
16.3.3.1 Создание и удаление очереди
hardorsoft
25.10.10 в 22:49
|
|
Как мы показали в нашем примере, очередь запросов является динамической структурой данных, которая должна создаваться системой блочного ввода-вывода. Для создания и инициализации очереди запросов используется функция:
hardorsoft
25.10.10 в 22:49
|
|
request_queue_t *blk_init_queue(request_fn_proc *request, spinlock_t *lock);
hardorsoft
25.10.10 в 22:50
|
|
Аргументами является функция request и spinlock для контроля доступа к очереди. Функция выполняет выделение памяти, поэтому может претерпеть неудачу. Вы должны всегда проверять возвращаемое значение перед использованием очереди.
hardorsoft
25.10.10 в 22:50
|
|
При инициализации очереди запросов вы должны установить поле queuedata (которое является указателем void*) в любое необходимое вам значение. Это поле эквивалентно полю private_data, которое рассматривалось в других структурах.
hardorsoft
25.10.10 в 22:50
|
|
Для возвращения очереди запросов системе (в основном, при выгрузке модуля) вызывается:
hardorsoft
25.10.10 в 22:50
|
|
void blk_cleanup_queue(request_queue_t *);
hardorsoft
25.10.10 в 22:50
|
|
После этого вызова, ваш драйвер не получает запросов из этой очереди и не обращается к ней.
hardorsoft
25.10.10 в 22:51
|
|
16.3.3.2 Функции работы с очередью
hardorsoft
25.10.10 в 22:51
|
|
Для обработки запросов в очереди, имеется не большое количество функций. Вы должны захватывать блокировку очереди, перед вызовом каждой из этих функций.
hardorsoft
25.10.10 в 22:51
|
|
Функция возвращает следующий запрос из очереди для обработки:
hardorsoft
25.10.10 в 22:51
|
|
struct request *elv_next_request(request_queue_t *queue);
hardorsoft
25.10.10 в 22:52
|
|