Разработка модулей ядра Linux: Часть 20. Модуль как драйвер. Динамические устройства

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

Олег Цилюрик, преподаватель тренингового отделения, Global Logic

Фото автораОлег Иванович Цилюрик, много лет был разработчиком программного обеспечения в крупных центрах разработки: ВНИИ РТ, НПО "Дельта", КБ ПМ. Последние годы работал над проектами в области промышленной автоматики, IP телефонии и коммуникаций. Автор нескольких книг. Преподаватель тренингового отделения международной софтверной компании Global Logic.



28.08.2012

Введение

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


Тестирование

Отвлечёмся от кодирования модуля, чтобы посмотреть, как можно проверить его работоспособность. Прежде любых других тестов мы должны использовать естественные тестеры стандарта POSIX — консольные утилиты GNU для обмена данными с именованными файлами. В этой возможности проявляется сила стандартов POSIX: их механизмы должны работать во всех окружениях, даже тех, которые не предусматривались ранее, как в случае с новым создаваемым драйвером. А поскольку эти утилиты неоднократно проверены на соответствие стандартам, они являются наилучшими тестерами для проектируемого драйвера. В качестве таких тестов могут использоваться любые утилиты GNU для работы с файлами. Чаще всего это будут команды cat и echo в простейшей форме, как уже показывалось ранее, но это могут быть и другие консольные команды (например, dd), использующие файловые операции, например (mopen - это драйвер, допускающий чтение и запись, который мы вскоре построим):

$ cat mmopen.c > /dev/mopen
$ cat /dev/mopen | wc -c 
79

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


Динамические устройства

Установим драйвер, созданный в предыдущей части, не указывая ему принудительно major-номер.

$ sudo insmod fixdev.ko 
$ dmesg | grep === 
=========== module installed 249:0 ============== 
$ cat /proc/devices | grep my_ 
249 my_cdev_dev 
$ sudo mknod -m0666 /dev/z0 c 249 0 
$ ls -l /dev | grep 249 
crw-rw-rw-  1 root root      249,   0 Янв 22 13:29 z0 
$ cat /dev/z0 
Hello, world! 
$ sudo rm /dev/z0 
$ cat /dev/z0 
cat: /dev/z0: Нет такого файла или каталога

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

  • этот major-номер ещё не был занят в системе;
  • для этого major-номера не был занят запрашиваемый диапазон minor-номеров.

Но самого имени для такого устройства (с major-номером равным 249) в системе не существовало, и мы были вынуждены создать его сами с помощью команды mknod.

Вариацией на тему использования того же API будет вариант модуля (который находится в архиве cdev.tgz в предыдущей статье), динамически создающий нужные ему имена устройств в каталоге /dev. Для этого модуль использует сервис файловой подсистемы Linux sysfs (/sys, которая будет детально рассмотрена позже).

Листинг 1. Модуль, создающий имена устройств (файл dyndev.c, показаны только отличия от предыдущего варианта).
#include <linux/device.h>
...
static struct cdev hcdev; 
static struct class *devclass; 
      
static int __init dev_init( void ) { 
   int ret, i; 
   dev_t dev; 
   ...
   ret = cdev_add( &hcdev, dev, DEVICE_COUNT ); 
   if( ret < 0 ) { 
      unregister_chrdev_region( MKDEV( major, DEVICE_FIRST ), DEVICE_COUNT ); 
      printk( KERN_ERR "=== Can not add char device\n" ); 
      goto err; 
   } 
   devclass = class_create( THIS_MODULE, "dyn_class" ); 
#define DEVNAME "dyn" 
   for( i = 0; i < DEVICE_COUNT; i++ ) { 
      char name[ 10 ]; 
      dev = MKDEV( major, DEVICE_FIRST + i ); 
      sprintf( name, "%s_%d", DEVNAME, i ); 
      device_create( devclass, NULL, dev, "%s", name ); 
   } 
   printk( KERN_INFO "======== module installed %d:[%d-%d] ========\n", 
           MAJOR( dev ), DEVICE_FIRST, MINOR( dev ) ); 
err: 
   ...
} 

static void __exit dev_exit( void ) { 
   dev_t dev; 
   int i; 
   for( i = 0; i < DEVICE_COUNT; i++ ) { 
      dev = MKDEV( major, DEVICE_FIRST + i ); 
      device_destroy( devclass, dev ); 
   } 
   class_destroy( devclass ); 
   cdev_del( &hcdev ); 
   ...
}

Здесь создаётся класс устройств ("dyn_class") в терминологии sysfs, а затем внутри него создается нужное число устройств, исходя из диапазона minor. Теперь не потребуется вручную создавать имя устройства в /dev и отслеживать соответствие его номеров, так как соответствующее имя возникнет после загрузки модуля и ликвидируется после его выгрузки.

$ ls -l /dev/dyn* 
ls: невозможно получить доступ к /dev/dyn*: Нет такого файла или каталога 
$ sudo insmod dyndev.ko 
$ dmesg | tail -n30 | grep == 
======== module installed 249:[0-2] =========== 
$ ls -l /dev/dyn* 
crw-rw---- 1 root root 249, 0 Янв 22 18:09 /dev/dyn_0 
crw-rw---- 1 root root 249, 1 Янв 22 18:09 /dev/dyn_1 
crw-rw---- 1 root root 249, 2 Янв 22 18:09 /dev/dyn_2 
$ cat /dev/dyn_2 
Hello, world! 
$ ls /sys/class/d* 
...
/sys/class/dyn_class: 
dyn_0  dyn_1  dyn_2 
$ tree /sys/class/dyn_class/dyn_0 
/sys/class/dyn_class/dyn_0 
├── dev 
├── power 
│   └── wakeup 
├── subsystem -> ../../../../class/dyn_class 
└── uevent 
$ cat /proc/modules | grep dyn 
dyndev 1480 0 - Live 0xf88de000 
$ cat /proc/devices | grep dyn 
249 my_dyndev_mod 
$ sudo rmmod dyndev 
$ ls -l /dev/dyn* 
ls: невозможно получить доступ к /dev/dyn*: Нет такого файла или каталога

Этот же модуль может использоваться для создания диапазона устройств с принудительным указанием major-номера (если использование этого номера возможно с точки зрения системы), но с динамическим созданием имён таких устройств в /dev:

$ sudo insmod dyndev.ko major=260 
$ ls -l /dev | grep 260 
crw-rw----  1 root root      260,   0 Янв 22 18:57 dyn_0 
crw-rw----  1 root root      260,   1 Янв 22 18:57 dyn_1 
crw-rw----  1 root root      260,   2 Янв 22 18:57 dyn_2 
$ cat /dev/dyn_1 
Hello, world! 
$ sudo rmmod dyndev 
$ ls -l /dev | grep 260 
$

Динамическое создание устройств сильно упрощает работу над драйвером. Но всегда ли подходит такой способ распределения номеров устройств? Всё зависит от решаемой задачи. Если номера реального физического устройства в системе меняются от одного компьютера к другому, то это вряд ли понравится разработчикам этого устройства. С другой стороны, для некоторых задач удобнее создавать псевдоустройства - некоторые моделирующие сущности для каналов ввода и вывода. Для таких случаев совершенно уместным будет полностью динамическое распределение параметров таких устройств. Один из лучших примеров, поясняющих сказанное, - это интерфейс zaptel/DAHDI к оборудованию передачи VoIP, используемый во многих проектах коммутаторов IP-телефонии: Asterisk, FreeSWITCH, и т.д. В архитектуре этой драйверной подсистемы для синхронных цифровых линий связи E1/T1/J1 (и E3/T3/J3) создаётся множество (возможно, до нескольких сотен) фиктивных устройств-имён в /dev, взаимно-однозначно соответствующих виртуальным каналам временного уплотнения реальных линий связи (тайм-слотам). Вся дальнейшая работа с созданными динамическими именами устройств обеспечивается традиционными операциями read() или write(), в точности так, как это делается с реальным физическим оборудованием.

Практика динамически перераспределяемых псевдоустройств приобрела настолько широкое распространение, что для упрощения реализации таких устройств был предложен специальный интерфейс (<linux/miscdevice.h>). Эту техники регистрации драйвера устройства в литературе часто называют misc drivers (miscellaneous, интерфейс смешанных устройств). Это самая простая в использовании техника регистрации устройства. Каждое такое устройство создаётся с единым major-номером 10, но может выбирать свой уникальный minor-номер (задаётся принудительно или устанавливается системой). В этом варианте одним единственным вызовом misc_register() выполняется регистрация драйвера и создание символического имени устройства в /dev (архив misc.tgz с кодом примера доступен в разделе «Материалы для скачивания»).

Листинг 2. Минимальный код для регистрации драйвера.
#include <linux/fs.h> 
#include <linux/miscdevice.h> 
#include "../dev.h" 

static int minor = 0; 
module_param( minor, int, S_IRUGO ); 

static const struct file_operations misc_fops = { 
   .owner  = THIS_MODULE, 
   .read   = dev_read, 
}; 

static struct miscdevice misc_dev = { 
   MISC_DYNAMIC_MINOR,    // автоматически выбираемое 
   "own_misc_dev", 
   &misc_fops 
}; 

static int __init dev_init( void ) { 
   int ret; 
   if( minor != 0 ) misc_dev.minor = minor; 
   ret = misc_register( &misc_dev ); 
   if( ret ) printk( KERN_ERR "=== Unable to register misc device\n" ); 
   return ret; 
} 

static void __exit dev_exit( void ) { 
   misc_deregister( &misc_dev ); 
}

Вызов misc_register() регистрирует единичное устройство, с одним значением minor, определённым в struct miscdevice. Поэтому, если драйвер должен обслуживать группу однотипных устройств, различающихся по minor-номерам, то это не самое подходящее решение. Хотя, конечно, драйвер может поочерёдно зарегистрировать несколько структур struct miscdevice. Пример запуска подобного драйвера приведен ниже.

$ sudo insmod misc_dev.ko 
$ lsmod | head -n2 
Module                  Size  Used by 
misc_dev                1167  0 
$ cat /proc/modules | grep misc 
misc_dev 1167 0 - Live 0xf99e8000 
$ cat /proc/devices  | grep misc 
 10 misc 
$ ls -l  /dev/own* 
crw-rw---- 1 root root 10, 54 Янв 22 22:08 /dev/own_misc_dev 
$ cat /dev/own_misc_dev 
Hello, world!

Операционная система и прикладные проекты регистрируют достаточно много разносортных устройств с major-номером 10.

$ ls -l /dev | grep 10, 
crw-------  1 root video      10, 175 Янв 22 11:49 agpgart 
crw-rw----  1 root root       10,  57 Янв 22 11:50 autofs 
...
crw-rw----  1 root root       10,  54 Янв 22 22:08 own_misc_dev 
...
crw-------  1 root root       10,  56 Янв 22 11:50 vboxdrv
crw-rw----  1 root root       10, 130 Янв 22 11:49 watchdog

Все такие устройства регистрируются в sysfs в едином классе misc.

$ ls /sys/class/misc/own_misc_dev 
dev  power  subsystem  uevent 
$ tree /sys/devices/virtual/misc/own_misc_dev/ 
/sys/devices/virtual/misc/own_misc_dev/ 
├── dev 
├── power 
│   └── wakeup 
├── subsystem -> ../../../../class/misc 
└── uevent

А вот пример использования этого модуля с принудительным указанием minor-номера.

$ sudo insmod misc_dev.ko minor=55 
insmod: error inserting 'misc_dev.ko': -1 Device or resource busy 
$ sudo insmod misc_dev.ko minor=200 
$ ls -l /dev/own* 
crw-rw---- 1 root root 10, 200 Янв 22 22:15 /dev/own_misc_dev 
$ cat /dev/own* 
Hello, world!

Заключение

В представленных примерах использовалась единственная операция read(). Операция write(), абсолютно симметричная read(), реализуется также, и поэтому не включена в обсуждение, хотя и будет показана позже. Но кроме этих операций ввода /вывода в основном потоке данных, в Linux широко используется операция ioctl(), применяемая для управления устройством и осуществляющая обмен с устройством по всем основным потокам данных. Эта операция и будет рассмотрена в следующей статье.


Загрузка

ОписаниеИмяРазмер
Образец кодаmisc.tgz2KB

Ресурсы

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


  • Bluemix

    Узнайте больше информации о платформе IBM Bluemix, создавайте приложения, используя готовые решения!

  • developerWorks Premium

    Эксклюзивные инструменты для построения вашего приложения. Узнать больше.

  • Библиотека документов

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=832206
ArticleTitle=Разработка модулей ядра Linux: Часть 20. Модуль как драйвер. Динамические устройства
publish-date=08282012