Свой собственный модуль::Журнал СА 10.2003
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Подписка
Архив номеров
Где купить
Наука и технологии
Авторам
Рекламодателям
Контакты
   

  Опросы
  Статьи

Дата-центры  

Дата-центры: есть ли опасность утечки данных?

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

 Читать далее...

Событие  

В банке рассола ждет сисадмина с полей фрактал-кукумбер

Читайте впечатления о слете ДСА 2024, рассказанные волонтером и участником слета

 Читать далее...

Организация бесперебойной работы  

Бесперебойная работа ИТ-инфраструктуры в режиме 24/7 Как обеспечить ее в нынешних условиях?

Год назад ИТ-компания «Крок» провела исследование «Ключевые тренды сервисного рынка 2023». Результаты

 Читать далее...

Книжная полка  

Читайте и познавайте мир технологий!

Издательство «БХВ» продолжает радовать выпуском интересных и полезных, к тому же прекрасно

 Читать далее...

СУБД PostgreSQL  

СУБД Postgres Pro

Сертификация по новым требованиям ФСТЭК и роль администратора без доступа к данным

 Читать далее...

Критическая инфраструктура  

КИИ для оператора связи. Готовы ли компании к повышению уровня кибербезопасности?

Похоже, что провайдеры и операторы связи начали забывать о требованиях законодательства

 Читать далее...

Архитектура ПО  

Архитектурные метрики. Качество архитектуры и способность системы к эволюционированию

Обычно соответствие программного продукта требованиям мы проверяем через скоуп вполне себе понятных

 Читать далее...

Как хорошо вы это знаете  

Что вам известно о разработках компании ARinteg?

Компания ARinteg (ООО «АРинтег») – системный интегратор на российском рынке ИБ –

 Читать далее...

Графические редакторы  

Рисование абстрактных гор в стиле Paper Cut

Векторный графический редактор Inkscape – яркий представитель той прослойки open source, с

 Читать далее...

День сисадмина  

Учите матчасть! Или как стать системным администратором

Лето – время не только отпусков, но и хорошая возможность определиться с профессией

 Читать далее...

День сисадмина  

Живой айтишник – это всегда движение. Остановка смерти подобна

Наши авторы рассказывают о своем опыте и дают советы начинающим системным администраторам.

 Читать далее...

Виртуализация  

Рынок решений для виртуализации

По данным «Обзора российского рынка инфраструктурного ПО и перспектив его развития», сделанного

 Читать далее...

Книжная полка  

Как стать креативным и востребованным

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

 Читать далее...

Книжная полка  

От создания сайтов до разработки и реализации API

В издательстве «БХВ» недавно вышли книги, которые будут интересны системным администраторам, создателям

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 6237
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 6944
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

12.03.2018г.
Просмотров: 4230
Комментарии: 0
Глубокое обучение с точки зрения практика

 Читать далее...

12.03.2018г.
Просмотров: 3015
Комментарии: 0
Изучаем pandas

 Читать далее...

12.03.2018г.
Просмотров: 3811
Комментарии: 0
Программирование на языке Rust (Цветное издание)

 Читать далее...

19.12.2017г.
Просмотров: 3827
Комментарии: 0
Глубокое обучение

 Читать далее...

19.12.2017г.
Просмотров: 6323
Комментарии: 0
Анализ социальных медиа на Python

 Читать далее...

19.12.2017г.
Просмотров: 3174
Комментарии: 0
Основы блокчейна

 Читать далее...

19.12.2017г.
Просмотров: 3466
Комментарии: 0
Java 9. Полный обзор нововведений

 Читать далее...

16.02.2017г.
Просмотров: 7283
Комментарии: 0
Опоздавших не бывает, или книга о стеке

 Читать далее...

17.05.2016г.
Просмотров: 10649
Комментарии: 0
Теория вычислений для программистов

 Читать далее...

30.03.2015г.
Просмотров: 12370
Комментарии: 0
От математики к обобщенному программированию

 Читать далее...

18.02.2014г.
Просмотров: 14005
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

 Читать далее...

13.02.2014г.
Просмотров: 9130
Комментарии: 0
Читайте, размышляйте, действуйте

 Читать далее...

12.02.2014г.
Просмотров: 7084
Комментарии: 0
Рисуем наши мысли

 Читать далее...

10.02.2014г.
Просмотров: 5393
Комментарии: 3
Страна в цифрах

 Читать далее...

18.12.2013г.
Просмотров: 4620
Комментарии: 0
Большие данные меняют нашу жизнь

 Читать далее...

18.12.2013г.
Просмотров: 3431
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

 Читать далее...

04.12.2013г.
Просмотров: 3164
Комментарии: 0
Паутина в облаках

 Читать далее...

03.12.2013г.
Просмотров: 3405
Комментарии: 0
Рецензия на книгу «MongoDB в действии»

 Читать далее...

02.12.2013г.
Просмотров: 3029
Комментарии: 0
Не думай о минутах свысока

 Читать далее...

Друзья сайта  

 Свой собственный модуль

Архив номеров / 2003 / Выпуск №10 (11) / Свой собственный модуль

Рубрика: Администрирование /  Продукты и решения

ДЕНИС КОЛИСНИЧЕНКО

Свой собственный модуль

В этой статье мы рассмотрим основные операции с модулями ядра Linux, а также создание собственного модуля, позволяющего расширить возможности ядра операционной системы.

Прежде чем сказать, что же такое модуль, поговорим о драйверах устройств. В Windows мы часто сталкиваемся с установкой, удалением и обновлением драйверов. Драйвером устройства называется определенная программа, позволяющая операционной системе (и пользовательским программам) работать с данным устройством. Операционная система Windows (имеется в виду ME/2000/XP) содержит драйверы для работы с наиболее распространенными устройствами, но если вы хотите установить какое-нибудь экзотическое устройство (драйвера которого нет в базе драйверов операционной системы), вам нужен драйвер этого устройства – без него система не сможет работать с устройством. В Windows установка драйверов выполняется достаточно просто – запустил программу установки драйвера, подождал, пока драйверы будут установлены, и после перезапуска компьютера уже можно работать с новым устройством. Конечно, при условии, что вы выбрали подходящий драйвер.

В Linux «драйверы» выполнены в виде модулей ядра.

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

Такое решение нельзя назвать рациональным. Мы не можем предусмотреть, какие устройства будут установлены у конечного пользователя, даже если включить в состав ядра драйверы всех возможных устройств. Предположим, что у нашего пользователя установлена звуковая плата Yamaha, а наше ядро «знает» еще с десяток звуковых плат. Получится, что один код будет работать всегда, а остальные десять драйверов будут просто использовать оперативную память. Кстати, об оперативной памяти: вы представляете размер ядра, если оно будет содержать драйверы всех устройств?

Исходя из всего этого, разработчики ядра Linux «изобрели» механизм динамически загружаемых модулей. Модули хранятся на диске в виде объектных файлов (*.o). При необходимости ядро загружает необходимый ему модуль. Откуда ядро знает, какой модуль нужно загружать, а какой – нет? Список модулей, а также передаваемые им параметры хранятся в файле /etc/modules.conf (или /etc/conf.modules – в зависимости от дистрибутива и версии ядра). Вот пример этого файла:

Листинг 1. Файл /etc/modules.conf (Linux Red Hat 7.3)

alias sound-slot-0 i810_audio

post-install sound-slot-0 /bin/aumix-minimal -f /etc/.aumixrc -L >/dev/null 2>&1

pre-remove sound-slot-0 /bin/aumix-minimal -f /etc/.aumixrc -S >/dev/null 2>&1

Подробнее о формате файла /etc/modules.conf вы можете прочитать в справочной системе, введя команду man modules.conf.

При загрузке система читает этот файл и загружает указанные в нем модули. В нашем случае загружается только модуль i810_audio.

Примечание: загрузка модулей из файла modules.conf обеспечивается программой modprobe, которая вызывается из сценария инициализации системы /etc/rc.d/rc.sysinit.

Во время работы системы мы сами можем загрузить нужный нам модуль. Для этого нужно использовать программу insmod, входящую в состав пакета modutils. Использовать программу очень просто:

insmod <имя_файла_модуля>

Чтобы использовать программу insmod, вы должны обладать привилегиями суперпользователя – пользователя root.

Просмотреть список загруженных модулей можно с помощью команды lsmod. Вот вывод этой программы:

Module            Size         Used by         Not tainted

autofs            12164           0               (autoclean) (unused)

nls_koi8-r            4576           2               (autoclean)

nls_cp866            4576           2               (autoclean)

vfat            12092           2               (autoclean)

fat                  37400           0               (autoclean) [vfat]

usb-uhci            24484           0               (unused)

usbcore            73152           1               [usb-uhci]

Некоторые модули загружаются не из файла /etc/modules.conf. Например, модули файловых систем загружаются по мере необходимости – при монтировании определенной файловой системы загружается нужный модуль, если, конечно, он есть. Модули nls_koi8-r и nls_cp866 загружаются также при монтировании файловой системы, если указаны опции монтирования iocharset=koi8-r,codepage=866.

Выгрузить модуль предельно просто:

rmmod имя_модуля

Программа modinfo позволяет просмотреть информацию о модуле:

modinfo usbcore

 

filename:      /lib/modules/2.4.18-3/kernel/drivers/usb/usbcore.o

description:      <none>

author:            <none>

license:            "GPL"

Примечание: программы insmod, rmmod, lsmod и modinfo входят в состав modutils. Для использования любой этой программы необходимы права пользователя root.

Теперь мы уже подошли к созданию своего собственного модуля. Что он будет делать, зависит от вас – это может быть драйвер устройства или просто небольшой модуль, дополняющий ядро нужной вам функцией.

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

Листинг 2. Каркас модуля ядра Linux (module.c)

#define MODULE

#define __KERNEL__

#include

int init_module()

{

return 0;

}

void cleanup_module()

{

return 0;

}

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

Примечание: первые две строчки листинга 2 – это не просто магические строчки, а директивы препроцессора, которые при обработке файла препроцессором cpp будут заменены нужным кодом.

Третья строка подключает заголовочный файл module.h, в котором находятся все необходимые определения для создания модуля определения.

Функция init_module() вызывается при загрузке модуля ядром. Если загрузка модуля прошла успешно, функция возвращает 0, в противном случае функция должна возвратить любое другое значение – код ошибки.

Функция cleanup_module() вызывается при удалении модуля. Как и в прошлом случае, она возвращает 0, если удаление модуля прошло успешно.

Названия функций init_module() и cleanup_module() необязательны – вы можете назвать их по-другому, но для этого вам нужно использовать функции module_init() и module_exit(), которые определены в заголовочном файле init.h.

Переделаем наш шаблон так, чтобы не использовать стандартные имена функций init_module() и cleanup_module():

Листинг 3. Шаблон модуля с переименованием стандартных функций (module2.c)

#define MODULE

#define __KERNEL__

#include // для модуля

#include    // module_init() и module_exit()

int start() {  return 0; }

void stop() {  return 0; }

module_init(start);

module_exit(stop);

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

Помните, для чего используется программа modinfo? Да, для получения информации о модуле. Давайте добавим эту информацию в наш модуль.

Листинг 4. Информация о модуле (module.c)

#define MODULE

#define __KERNEL__

#include

MODULE_AUTHOR("Denis Kolisnichenko dhsilabs@mail.ru");

int init_module()  { return 0; }

void cleanup_module() { return 0; }

При необходимости модуль может выводить на консоль сообщения, например, о невозможности инициализации устройства. Выводимые модулем сообщения будут запротоколированы службой протоколирования ядра – демоном klogd. Выводить сообщения нужно не с помощью функции printf(), а функцией printk(). В этом случае сообщения не только окажутся на системной консоли, но и будут запротоколированы в файле /var/log/messages.

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

Листинг 5. Использование функции printk()

#define MODULE

#define __KERNEL__

#include

#include // printk

MODULE_AUTHOR("Denis Kolisnichenko dhsilabs@mail.ru");

int init_module()

{

printk(“My module: Starting... ”);

return 0;

}

void cleanup_module()

{

printk(“My module: Stopping... ”);

return 0;

}

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

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

  • cpp – препроцессор cpp;
  • binutils – набор различных утилит (as, gprof, ld и др.);
  • glibc-kerheaders – заголовочные файлы ядра;
  • glibc-devel – вспомогательные файлы для разработки приложений с использованием стандартной библиотеки C;
  • gcc – компилятор gcc;
  • kernel-source – исходные тексты ядра Linux.

Устанавливать пакеты нужно в указанной последовательности. Если компилятор gcc у вас установлен, то вам нужно установить только последний пакет – исходные тексты ядра.

Также нужно убедиться, что наше ядро поддерживает динамически загружаемые модули. Для этого перейдите в каталог /usr/src/linux-2.4 (или /usr/src/linux) и введите команду make menuconfig. Вы получили сообщение, что библиотека Ncurses не найдена? Установите пакеты ncurses (или ncurses4) и ncurses-devel и введите команду make menuconfig снова.

Убедитесь, что в разделе Loadable module support включена опция Enable loadable module support (см. рис. 1).

Рисунок  1. Конфигурирование ядра

Рисунок  1. Конфигурирование ядра

Если опция Enable loadable module support выключена, ее нужно включить, сохранить файл конфигурации ядра и перекомпилировать ядро. О компиляции ядра вы можете прочитать в моей книге «Linux-сервер своими руками» («Наука и техника», 2002 г.) или в статье «Компилирование ядра»: http://dkws.narod.ru/linux/kernel/kern.html.

Компилятору gcc нужно передать много параметров, поэтому мы напишем так называемый Makefile, облегчающий нам жизнь при компилировании/перекомпилировании модуля.

Листинг 6. Makefile нашего модуля (Makefile)

CC=gcc

PATH=/usr/include /usr/src/linux-2.4/include

MODFLAGS:= -O3 -Wall –DLINUX –D__KERNEL__ -I$(PATH)

module.o: module.c

$(CC) $(MODFLAGS) -c module.c

В качестве компилятора мы будем использовать компилятор gcc ($CC), ему будут переданы параметры $MODFLAGS и –c module.c. module.c – это имя файла нашего модуля. Опции компилятора означают следующее:

  • O3: будет использован третий уровень оптимизации (что это такое, вы узнаете в справочной системе gcc: man gcc);
  • Wall: включаем все предупреждения;
  • DLINUX: генерируем код для Linux;
  • I$(PATH): определяем путь поиска заголовочных файлов. По умолчанию компилятор ищет файлы заголовков в каталоге /usr/include, но там может и не быть нужных файлов, например, для дистрибутива ALT Linux (ядро 2.4.21) файлы заголовков находятся в каталоге /usr/include/linux-2.4.21rel-std-up/.

Makefile поместите в один каталог с файлом module.c и выполните команду make. После ее выполнения вы получите файл module.o, который будет находиться в одном каталоге с файлом module.c.

Если вы по каким-либо причинам не хотите использовать утилиту make, для компилирования модуля введите команду:

gcc –O3 -DMODULE -D__KERNEL__ -I/usr/include -c module.c

После того, как наш модуль откомпилирован, его можно установить:

insmod module.o

Вы увидите сообщение My module: Starting... Это же сообщение будет записано в файл протокола /var/log/messages.

Мы только что написали модуль ядра, который можно установить, удалить, который выводит сообщения, но он ничего полезного не делает. Усложним нашу задачу: напишем драйвер для некоторого устройства /dev/device. Данного устройства в нашей системе нет, поэтому нам его нужно создать, но этим мы займемся чуть позже.

Перед тем как приступить к написанию драйвера устройства, нужно поговорить о самих устройствах. Устройства бывают трех типов:

  • Символьные: чтение и запись устройства выполняются посимвольно. Примером такого устройства может послужить последовательный порт.
  • Блочные: чтение и запись устройства выполняются блоками, как правило, по 512 или 1024 байта. Самый яркий пример блочного устройства – жесткий диск.
  • Сетевые: файлов этих устройств вы не найдете в каталоге /dev, поскольку использование файловой системы, то есть работа с этими устройствами как с файлами, неэффективно. Пример – сетевая карта (ethX).

Чтобы сделать устройство доступным для системы и пользовательских программ, нужно его зарегистрировать. В заголовочном файле fs.h определены функции для регистрации символьных и блочных устройств. Все они начинаются с префикса register. Наше устройство будет символьным, поэтому для его регистрации мы будем использовать функцию register_chrdev. Вот ее прототип:

extern int register_chrdev(unsigned int, const char *, struct file_operations *);

Разберемся, что означает каждый параметр. Первый параметр – это старший номер (major number) устройства, определяющий его тип. Если старший номер равен 0, то функция возвратит свободный старший номер для устройства нашего вида (символьное устройство).

Чтобы понять, для чего нужен старший номер, вспомним каталог /dev, содержащий файлы устройств. Возьмем файл /dev/tty1 – это терминал с номером 1. Старший номер определяет тип устройства – терминал (tty), а младший – его номер в системе (1). Человеку проще работать не с номерами, а с символьными именами устройств, поэтому каждому старшему номеру соответствует символьное обозначение.

При регистрации устройства нужно указать его тип – старший номер устройства. Но откуда мы знаем, какой номер занят, а какой – нет? Проще всего указать 0 в качестве первого параметра функции register_chrdev(). В этом случае функция возвратит свободный старший номер символьного устройства для нашей системы. Если же явно указать старший номер, может возникнуть конфликт номеров, и наше устройство не будет зарегистрировано.

Второй параметр определяет имя устройства («device»). Последний параметр очень важен. Это структура указателей на функции для работы с нашим устройством. Наш драйвер (модуль) содержит таблицу доступных функций, а операционная система вызывает нужную функцию, когда пользовательской программе нужно выполнить операцию с файлом устройства (открытие/закрытие, чтение/запись). Таблица функций символьного устройства хранится в структуре file_operations, которая передается при регистрации устройства.

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

Листинг 7. Драйвер устройства /dev/device (без структуры file_operations)

#define MODULE

#define __KERNEL__

#include<linux/module.h>

#include<linux/init.h>

#include<linux/kernel.h>

#include<linux/fs.h>           // регистрация устройств

#include<linux/ioport.h>       // работа с портами ввода/вывода

#include<linux/sched.h> // резервирование прерывания

// Имя нашего устройства

#define DEV_NAME "device"

// Порты ввода-вывода нашего устройства

#define PORT_START 0x2000

#define PORT_QTY 10

// Память нашего устройства

#define MEM_START 0x20000000

#define MEM_QTY 0x20

// Номер прерывания для нашего устройства

#define IRQ_NUM 9

MODULE_AUTHOR("Denis Kolisnichenko dhsilabs@mail.ru");

MODULE_DESCRIPTION("Linux kernel module");

// Старший номер файла устройства

static int Major;

// Структура file_operations – пока пустая, но вскоре

// мы ее напишем

struct file_operations FO;

// Обработчик прерывания

void irq_handler(int irq, void *dev_id, struct pt_regs *regs)

{

return;

}

int init_module()

{

// Регистрируем устройство

printk("My module: starting…\n");

Major = register_chrdev(0, DEV_NAME, &FO);

if (Major < 0) {

// Устройство не зарегистрировано

printk("My module: registration failed\n");

return Major;

}

printk("My module: device registered, major number = %d\n",Major);

// Резервирование портов ввода-вывода

printk("My module: allocating io ports\n");

if (check_region(PORT_START, PORT_QTY))

{

printk("My module: allocation io ports failed\n");

return -EBUSY;

}

request_region(PORT_START, PORT_QTY, DEV_NAME);

printk ("My module: io ports allocated\n");

// Резервирование памяти

if (check_mem_region(MEM_START, MEM_QTY))

{

printk("My module: memory allocation failed\n");

release_region(PORT_START, PORT_QTY);

return -EBUSY;

}

request_mem_region(MEM_START, MEM_QTY, DEV_NAME);

printk ("My module: memory allocated\n");

// Резервирование прерывания

if (request_irq(IRQ_NUM, irq_handler, 0, DEV_NAME, NULL))

{

printk("My module: IRQ allocation failed\n");

release_mem_region(MEM_START, MEM_QTY);

release_region(PORT_START, PORT_QTY);

return -EBUSY;

}

printk ("My module: IRQ allocated\n");

return 0;

}

void cleanup_module()

{

// Освобождаем порты ввода-вывода

release_region(PORT_START, PORT_QTY);

printk("My module: release io ports\n");

// Освобождаем память

release_mem_region(MEM_START, MEM_QTY);

printk("My module: release memory\n");

// Освобождаем прерывание

free_irq(IRQ_NUM,NULL);

printk("My module: release irq\n");

// Отменяем регистрацию устройства

if (unregister_chrdev(Major, DEV_NAME) < 0){

   printk("My module: cannot to unregister device\n");

}

printk("My module: device unregistered\n");

return;

}

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

My module: device registered, major number = 255

Конечно, кроме этого сообщения будут другие сообщения, но нас они не интересуют. Почему именно это сообщение так важно для нас? В первой части сообщения говорится, что наше устройство успешно зарегистрировано, а во второй – сообщается старший номер устройства, который мы будем использовать для создания устройств /dev/device0 и /dev/device1.

Вы не забыли, что нам еще нужно создать два устройства типа device, чтобы программы могли бы работать с ними? Перейдите в каталог /dev и от имени пользователя root выполните команды:

mknod device c 255 0

mknod device c 255 1

Здесь 255 – это старший номер устройства (у вас он будет другим), 0 и 1 – младшие номера устройств. После выполнения данных команд будут созданы два файла устройств – /dev/device0 и /dev/device1.

После регистрации устройства функцией register_chrdev() мы пытаемся захватить диапазон портов. Для этого предназначена функция request_region(), но перед ее вызовом мы должны убедиться, что нужный нам диапазон не используется (функция check_region()). Затем, если нужно, мы резервируем память для нашего устройства. Для резервирования памяти используется функция request_mem_region(), а для проверки возможности захвата памяти предназначена функция check_mem_region(). После успешной регистрации памяти можно попытаться захватить IRQ – функция request_irq().

Предположим, что на каком-то этапе регистрации драйвера произошла ошибка. Если мы не смогли зарегистрировать порты ввода/вывода, вряд ли имеет смысл продолжать работу. Если же ошибка произошла при резервировании памяти, то перед завершением работы модуля нам нужно освободить порты ввода/вывода, которые мы зарегистрировали на предыдущем этапе. Аналогично поступаем при ошибке захвата IRQ – освобождаем порты и память. Функции release_mem_region(),release_region и free_irq() используются для освобождения памяти, портов и IRQ соответственно.

Обратите внимание: мы написали драйвер так, что он захватывает порты и память от имени одного устройства – DEV_NAME. В реальности все гораздо сложнее: нужно захватывать ресурсы для каждого устройства данного типа. К тому же нужно предусмотреть поиск устройств драйвером: в нашем случае мы знаем, что устройств только два, но у конечного пользователя таких устройств может быть больше или меньше, поэтому наш драйвер будет не универсален, если он будет поддерживать только два устройства. О поиске устройств мы поговорим чуть позже.

Наш драйвер пока еще не может называться драйвером в прямом смысле этого слова: устройство-то он регистрирует, но не позволяет выполнить ни одной операции с устройством – ведь структура file_operations пуста.

Кроме структуры file_operations, нам еще понадобится структура для хранения информации о состоянии устройства, а так как устройств у нас два, то также нужен массив структур для хранения состояния каждого устройства. Индексами массива будут младшие номера устройств.

// Структура для хранения состояния устройства

struct device_state

{

// 1 – устройство открыто, 0 - закрыто

int dev_open;

// Количество прочитанных байт из устройства

ssize_t byte_read;

// Количество записанных байт

ssize_t byte_write;

};

// Массив для хранения информации о состоянии устройств

static struct device_state state[2];

В принципе можно обойтись и без кода поиска устройств – без него модуль будет проще, да и работать он будет быстрее. А мы знаем, чем проще программа, тем она надежнее. Обойти поиск устройств можно следующим образом. Мы не знаем, сколько устройств типа device будет у конечного пользователя – у него может быть только одно устройство – /dev/device0, а может быть целых 20 устройств – /dev/device0…/dev/device19. Поэтому вместо описанного выше массива state, нужно использовать динамический список, который будет содержать информацию о каждом устройстве типа device. При загрузке модуля он будет содержать всего один элемент – для устройства /dev/device0. Даже если этого устройства не будет в системе, будем считать, что оно просто закрыто, а при попытке обращения к нему будем сообщать, что оно занято. По мере поступления запросов программ на открытие других устройств /dev/deviceX будем добавлять новые элементы в наш список. Чтобы не усложнять код нашего модуля, в этой статье мы не будем использовать динамические структуры – для вас как начинающего разработчика модулей ядра Linux, главное – разобраться с написанием самого модуля, а добавить динамические списки вы можете в любой момент сами.

Если же вам все-таки хочется узнать конкретное количество устройств /dev/deviceX, установленных у пользователя, можно просто просмотреть содержимое каталога /dev и посчитать количество файлов device*.

Все готово для того, чтобы написать функцию открытия устройства.

Листинг 8. Функция открытия устройства

static int device_open(struct inode *inode, struct file *fp)

{

struct device_state *dev_state;

printk("My module: try  to open device with minor number %d ", MINOR(inode->i_rdev));

dev_state = &state[MINOR(inode->i_rdev)]; 

if(dev_state->dev_open)

{

printk("Devise is busy ");

return -EBUSY;

}

dev_state->dev_open = 1;

dev_state->byte_read = 0;

dev_state->byte_write = 0;

MOD_INC_USE_COUNT;

return 0;

}

Младший номер устройства мы получаем с помощью вызова MINOR(inode->i_rdev). Если устройство уже открыто, мы выводим сообщение: Devise is busy. В противном случае устанавливаем флаг открытия устройства, обнуляем byte_read и byte_write, а также увеличиваем счетчик использования данного модуля (MOD_INC_USE_COUNT).

Функция закрытия устройства: сбрасываем флаг dev_open и уменьшаем счетчик использования устройства.

Листинг 9. Функция закрытия устройства

static int device_close(struct inode *inode, struct file *fp)

{

struct device_state *dev_state;

printk("My module: try to close device with minor number %d ", MINOR(inode->i_rdev));

dev_state = &state[MINOR(inode->i_rdev)];

if(!dev_state->dev_open)

{

printk("Device is not open ");

return 0;

}

dev_state->dev_open=0;

MOD_DEC_USE_COUNT;

return 0;

}

Теперь нам нужно указать ядру, какие функции нужно использовать для открытия и закрытия устройства:

struct file_operations FO =

{

open: device_open,

release: device_close

};

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

Листинг 10. Модуль устройства device (module.c)

#define MODULE

#define __KERNEL__

#include

#include

#include

#include           // регистрация устройств

#include       // работа с портами ввода/вывода

#include        // резервирование прерывания

// Имя нашего устройства

#define DEV_NAME "device"

// Порты ввода-вывода нашего устройства

#define PORT_START 0x2000

#define PORT_QTY 10

// Память нашего устройства

#define MEM_START 0x20000000

#define MEM_QTY 0x20

// Номер прерывания для нашего устройства

#define IRQ_NUM 9

MODULE_AUTHOR("Denis Kolisnichenko dhsilabs@mail.ru");

 

// Старший номер файла устройства

static int Major;

// Структура file_operations – пока пустая, но вскоре

// мы ее напишем

struct file_operations FO

{

open: device_open,

release: device_close

};

// Структура для хранения состояния устройства

struct device_state

{

// 1 – устройство открыто, 0 - закрыто

int dev_open;

// Количество прочитанных байтов из устройства

ssize_t byte_read;

// Количество записанных байтов

ssize_t byte_write;

};

// Массив для хранения информации о состоянии устройств

static struct device_state state[2];

// Обработчик прерывания

void irq_handler(int irq, void *dev_id, struct pt_regs *regs)

{

return;

}

int init_module()

{

// Регистрируем устройство

printk("My module: starting… ");

Major = register_chrdev(0, DEV_NAME, &FO);

if (Major < 0) {

// Устройство не зарегистрировано

printk("My module: registration failed ");

return Major;

}

printk("My module: device registered, major number = %d ",Major);

// Резервирование портов ввода-вывода

printk("My module: allocating io ports ");

if (check_region(PORT_START, PORT_QTY))

{

printk("My module: allocation io ports failed ");

return -EBUSY;

}

request_region(PORT_START, PORT_QTY, DEV_NAME);

printk ("My module: io ports allocated ");

// Резервирование памяти

if (check_mem_region(MEM_START, MEM_QTY))

{

printk("My module: memory allocation failed ");

release_region(PORT_START, PORT_QTY);

return -EBUSY;

}

request_mem_region(MEM_START, MEM_QTY, DEV_NAME);

printk ("My module: memory allocated ");

// Резервирование прерывания

if (request_irq(IRQ_NUM, irq_handler, 0, DEV_NAME, NULL))

{

printk("My module: IRQ allocation failed ");

release_mem_region(MEM_START, MEM_QTY);

release_region(PORT_START, PORT_QTY);

return -EBUSY;

}

printk ("My module: IRQ allocated ");

return 0;

}

void cleanup_module()

{

// Освобождаем порты ввода-вывода

release_region(PORT_START, PORT_QTY);

printk("My module: release io ports ");

// Освобождаем память

release_mem_region(MEM_START, MEM_QTY);

printk("My module: release memory ");

// Освобождаем прерывание

free_irq(IRQ_NUM,NULL);

printk("My module: release irq ");

// Отменяем регистрацию устройства

if (unregister_chrdev(Major, DEV_NAME) < 0){

   printk("My module: cannot to unregister device ");

}

printk("My module: device unregistered ");

return;

}

static int device_open(struct inode *inode, struct file *fp)

{

struct device_state *dev_state;

printk("My module: try  to open device with minor number %d ", MINOR(inode->i_rdev));

dev_state = &state[MINOR(inode->i_rdev)];

if(dev_state->dev_open)

{

printk("Devise is busy ");

return -EBUSY;

}

dev_state->dev_open = 1;

dev_state->byte_read = 0;

dev_state->byte_write = 0;

MOD_INC_USE_COUNT;

return 0;

}

static int device_close(struct inode *inode, struct file *fp)

{

struct device_state *dev_state;

printk("My module: try to close device with minor number %d ", MINOR(inode->i_rdev));

 

dev_state = &state[MINOR(inode->i_rdev)];

if(!dev_state->dev_open)

{

printk("Device is not open ");

return 0;

}

dev_state->dev_open=0;

MOD_DEC_USE_COUNT;

return 0;

}

Теперь драйвер для нашего абстрактного устройства device готов. Вы можете написать небольшую программку, которая бы пыталась выполнить операции с нашим устройством: открыть его и закрыть – других операций мы не определили. Для определения других действий используется та же структура file_operations. В файле /usr/src/linux-2.4/include/linux/fs.h она объявлена так:

Листинг 11. Фрагмент файла /usr/src/linux-2.4/include/linux/fs.h

struct file_operations {

    struct module *owner;

    loff_t (*llseek) (struct file *, loff_t, int);

    ssize_t (*read) (struct file *, char *, size_t, loff_t *);

    ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

    int (*readdir) (struct file *, void *, filldir_t);

    unsigned int (*poll) (struct file *, struct poll_table_struct *);

    int (*ioctl) (struct inode *, struct file *, їunsigned int, unsigned long);

    int (*mmap) (struct file *, struct vm_area_struct *);

    int (*open) (struct inode *, struct file *);

    int (*flush) (struct file *);

    int (*release) (struct inode *, struct file *);

    int (*fsync) (struct file *, struct dentry *, int datasync);

    int (*fasync) (int, struct file *, int);

    int (*lock) (struct file *, int, struct file_lock *);

    ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);

    ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);

    ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

    unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

};

Как использовать структуру file_operations, думаю, ясно. Например, нам нужно описать обработчики записи и чтения устройства – функции device_write() и device_read():

struct file_operations FO =

{

open: device_open,

release: device_close

read: device_read,

write: device_write

};

Обработчики чтения и записи пишутся «по образу и подобию» обработчиков открытия и закрытия устройства, то есть сначала нам нужно определить младший номер с помощью вызова MINOR(), а затем произвести операцию с устройством.

Все ваши вопросы и комментарии рад буду выслушать по адресу dhsilabs@mail.ru.


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-45
E-mail: sa@samag.ru