Безопасное распределение ресурсов в драйверах устройств ввода-вывода через отображаемую память.

Автор: Dr B Thangaraju
Перевод: Андрей Киселев

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

Введение

В быстро развивающемся мире Информационных Технологий, постоянно появляются новые устройства и их разнообразие просто поражает. Доступ к периферии (жесткие диски, CD-ROM, терминалы, принтеры, сетевые адаптеры и пр.) осуществляется через подсистему ввода-вывода. Модули ядра, управляющие устройствами, называются "драйверы устройств". Подсистема ввода-вывода отвечает за перемещение данных между устройством и памятью. Все устройства ввода-вывода подразделяются на блочные и символьные, в зависимости от типа доступа. К символьным можно отнести клавиатуру, мышь, консоль и модем. Обмен с ними выглядит как поток байтов (символов). Блочные устройства (жесткие диски, CD-ROM и др.) передают и принимают данные целыми блоками.

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

Как правило, работа драйвера заключается в передаче данных между компьютером и дополнительными устройствами. Ими могут быть устройства дополнительной памяти (например, Flash-модули прим. перев.), устройства связи, терминалы и т.п.. Возможны три варианта обработки ввода-вывода: PIO (PIO от англ. Programmed Input/Output -- режим программного ввода/вывода, когда вводом/выводом занимается процессор, что отнимает какую-то часть процессорного времени прим. перев.), ввод-вывод по прерываниям и DMA (DMA -- от англ. Direct Memory Access -- способ обмена данными между внешним устройством и памятью без участия процессора, что может заметно снизить на него нагрузку и повысить общую производительность системы прим. перев.). Передача данных между системой и устройствами может осуществляться двумя способами: через порты ввода-вывода и через отображаемую память. В этой статье обсуждаются основные положения организации ввода-вывода через отображаемую память и макроопределения, используемые при написании драйверов, для распределения областей памяти (идущие с примерами хорошо отлаженного кода). Поскольку драйвер является частью ядра, любая попытка вторгнуться в уже занятые адреса памяти приведет к краху системы. Таким образом, драйвер должен сначала проверить свободен ли заданный диапазон адресов и если нет, то вернуть управление системе с кодом ошибки. В противном случае этот диапазон резервируется за данным устройством.

Основы устройств ввода-вывода через отображаемую память

Драйверы накрепко связаны с аппаратурой обслуживаемого устройства. Они принимают на себя всю ответственность по взаимодействию CPU (CPU от англ. Central Processor Unit - Устройство Центрального Процессора, для архитектуры PC равносильно понятию "микропроцессор" прим. перев.) и устройства. Существует два основных способа передачи данных между устройством и ядром -- PIO и DMA. PIO задействует процессор, передача информации выполняется байт за байтом по мере готовности в процессе обработки прерываний, либо по опросу. Устройствам DMA передаются адрес источника (source address), адрес назначения (destination address) и размер блока данных. Устройство само перемещает данные в/из память(и). А после окончания передачи информации -- генерирует прерывание, чтобы уведомить ядро об окончании операции. Как правило, режим PIO используется для низкоскоростных устройств, таких как модемы, принтеры. И наоборот, для дисковых накопителей, графических терминалов используется режим DMA.

Для PIO-устройств есть два способа передачи данных. Выбор конкретного способа зависит от аппаратной архитектуры. Для архитектуры Intel x86 -- это порты ввода/вывода, для архитектуры Motorola 680x0 -- это ввод/вывод через отображаемую память. Кроме того, большинство устройств с шиной ISA (Industry Standard Architecture) поддерживают ввод/вывод через порты, в то время как устройства с шиной PCI (Peripheral Component Interconnect) поддерживают обмен через отображаемую память. Драйверу должны передаваться различные параметры, такие как адреса портов ввода/вывода или диапазон адресов в памяти. Иногда требуется передача драйверу дополнительных параметров, которые помогут ему обнаружить соответствующее устройство или включить/выключить некоторые специфические функции. В своей предыдущей статье на Linux Focus (http://linuxfocus.org/English/November2002/article264.meta.shtml), я рассказывал об основах управления устройствами и проблемах безопасного выделения портов ввода/вывода драйверам устройств в Linux. Так что снова к этой теме я возвращаться не буду. С точки зрения разработчика распределение отображаемой памяти для устройства во многом похоже на распределение портов ввода/вывода, поскольку внутренние механизмы распределения похожи друг на друга.

Макроопределения, используемые при выделении отображаемой памяти ввода/вывода

Проверка на занятость диапазона адресов выполняется следующим макросом:

int check_mem_region (unsigned long start, unsigned long length);

Где первый аргумент start -- начальный адрес, второй аргумент length -- размер блока памяти. Возвращаемое значение равно нулю, если диапазон адресов свободен, в противном случае возвращается значение меньше нуля.

Для "захвата" требуемого диапазона адресов отображаемой памяти используется макрос:

void request_mem_region (unsigned long start, unsigned long length, char *device_name);

Где char *device_name -- это название (имя) устройства, за которым резервируется требуемый диапазон памяти.

Перед тем как драйвер будет выгружен из памяти, он должен освободить память следующим макросом:

void release_mem_region (unsigned long start, unsigned long length);




Пример выделения области памяти ввода/вывода

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/ioport.h>

static int Major, result;
struct file_operations fops;

unsigned long start = 0, length = 0;

MODULE_PARM (start, "l");
MODULE_PARM (length, "l");

int Wipro_init (void) {
Major = register_chrdev (0, "Wipro_device", &fops);
if (Major < 0)
{
printk (" Major number allocation is failed \n");
return (Major);
}
printk (" The Major number of the device is %d \n", Major);

result = check_mem_region (start, length);
if (result < 0)
{
printk ("Allocation for I/O memory range is failed: Try other range\n");
return (result);
}

request_mem_region (start, length, "Wipro_device");
return 0;
}

void Wipro_cleanup (void) {
release_mem_region (start, length);
printk (" The I/O memory region is released successfully \n");

unregister_chrdev (Major, "Wipro_device");
printk (" The Major number is released successfully \n");
}

module_init (Wipro_init);
module_exit (Wipro_cleanup);

Первые четыре строки подключают заголовочные файлы, которые содержат объявления макроопределений и функций в ядре. Затем следуют объявления переменных. Макросы MODULE_PARM выполняют начальную установку переменных во время загрузки модуля. Этим макрокомандам передаются два параметра, первое -- имя переменной, второе -- тип переменной, в данном случае "l" означает long int. Сохраните исходный код примера в файле "io_mem.c".

В функциях Wipro_init и Wipro_cleanup производится инициализация и финализация (завершение работы) модуля драйвера. Функция Wipro_init сначала регистрирует устройство Wipro_device в системе, размещая старший номер устройства динамически. Затем она пробует зарезервировать заданную область памяти. Если заданный диапазон адресов уже занят, функция возвратит код ошибки, в противном случае она зарезервирует за устройством этот диапазон памяти. Функция Wipro_cleanup освобождает выделенную ранее память и возвращает старший номер устройства в систему (производит дерегистрацию устройства).

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

$cat /proc/devices
Character devices:
1 mem
2 pty
...
180 usb

$cat /proc/iomem
00000000-0009fbff : System RAM
...
e0000000-e3ffffff : Silicon Integrated Systems [SiS] 620 Host
ffff0000-ffffffff : reserved

Загрузите модуль командой

$insmod ./io_mem.o start=0xeeee0000 length=0xeeee

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

$cat /proc/devices
Character devices:
1 mem
2 pty
...
180 usb
254 Wipro_device

$cat /proc/iomem
00000000-0009fbff : System RAM
...
e0000000-e3ffffff : Silicon Integrated Systems [SiS] 620 Host
eeee0000-eeeeeeed : Wipro_device
ffff0000-ffffffff : reserved




Заключение

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

Благодарности

Я хотел бы выразить свою признательность Mr.V.Jayasurya и Dr. Sanjay Gupta, Talent Transformation, Wipro Technologies, India.

Ссылки

1. Linux Device Drivers (2nd Edition), by Alessandro Rubini and Jonathan Corbet.
Книга издательства O'Reilly :http://linux.oreilly.com/
[Online версию книги "Linux Device Drivers, 2nd Edition" вы найдёте по адресу http://www.xml.com/ldd/chapter/book/index.html. Прим.ред.]
Copyright (C) 2002, Dr B Thangaraju. Copying license http://www.linuxgazette.com/copying.html
Published in Issue 83 of Linux Gazette, October 2002

Вернуться на главную страницу