Разработка модулей ядра Linux

Часть 19. Модуль как драйвер. Пример реализации

Comments

Серия контента:

Этот контент является частью # из серии # статей: Разработка модулей ядра Linux

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Разработка модулей ядра Linux

Следите за выходом новых статей этой серии.

В данной статье мы на практике рассмотрим вопросы, обсуждавшиеся в предыдущей статье, создав первый вариант модуля символьного устройства, предоставляющий пользователю только возможность чтения из устройства (для максимального упрощения картины — операция записи реализуется симметрично). Кроме того, поскольку мы собираемся реализовать целую группу примеров альтернативных драйверов интерфейса /dev, то сразу вынесем их общую часть (главным образом это реализация функции чтения) в отдельный включаемый файл, как показано в листинге 1.

Листинг 1. Общий включаемый файл dev.h — реализация операции чтения.
#include <linux/fs.h> 
#include <linux/init.h> 
#include <linux/module.h> 
#include <asm/uaccess.h> 

MODULE_LICENSE( "GPL" ); 
MODULE_AUTHOR( "Oleg Tsiliuric <olej@front.ru>" ); 
MODULE_VERSION( "6.3" ); 

static char *hello_str = "Hello, world!\n";         // buffer! 

static ssize_t dev_read( struct file * file, char * buf, 
                         size_t count, loff_t *ppos ) { 
   int len = strlen( hello_str ); 
   printk( KERN_INFO "=== read : %d\n", count ); 
   if( count < len ) return -EINVAL; 
   if( *ppos != 0 ) { 
      printk( KERN_INFO "=== read return : 0\n" );  // EOF 
      return 0; 
   } 
   if( copy_to_user( buf, hello_str, len ) ) return -EINVAL; 
   *ppos = len; 
   printk( KERN_INFO "=== read return : %d\n", len ); 
   return len; 
} 

static int __init dev_init( void ); 
module_init( dev_init ); 

static void __exit dev_exit( void ); 
module_exit( dev_exit );

В листинге 2 приведен первый созданный нами драйвер, исходный код которого находится в архиве cdev.tgz, расположенном в разделе "Материалы для скачивания".

Листинг 2. Файл fixdev.c — драйвер устройства.
#include <linux/cdev.h> 
#include "../dev.h" 

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

#define EOK 0 
static int device_open = 0; 

static int dev_open( struct inode *n, struct file *f ) { 
   if( device_open ) return -EBUSY; 
   device_open++; 
   return EOK; 
} 

static int dev_release( struct inode *n, struct file *f ) { 
   device_open--; 
   return EOK; 
} 

static const struct file_operations dev_fops = { 
   .owner = THIS_MODULE, 
   .open = dev_open, 
   .release = dev_release, 
   .read  = dev_read, 
}; 

#define DEVICE_FIRST  0 
#define DEVICE_COUNT  3 
#define MODNAME "my_cdev_dev" 

static struct cdev hcdev; 

static int __init dev_init( void ) { 
   int ret; 
   dev_t dev; 
   if( major != 0 ) { 
      dev = MKDEV( major, DEVICE_FIRST ); 
      ret = register_chrdev_region( dev, DEVICE_COUNT, MODNAME ); 
   } 
   else { 
      ret = alloc_chrdev_region( &dev, DEVICE_FIRST, DEVICE_COUNT, MODNAME ); 
      major = MAJOR( dev );  // не забыть зафиксировать! 
   } 
   if( ret < 0 ) { 
      printk( KERN_ERR "=== Can not register char device region\n" ); 
      goto err; 
   } 
   cdev_init( &hcdev, &dev_fops ); 
   hcdev.owner = THIS_MODULE; 
   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; 
   } 
   printk( KERN_INFO "=========== module installed %d:%d =========\n", 
           MAJOR( dev ), MINOR( dev ) ); 
err: 
   return ret; 
} 

static void __exit dev_exit( void ) { 
   cdev_del( &hcdev ); 
   unregister_chrdev_region( MKDEV( major, DEVICE_FIRST ), DEVICE_COUNT ); 
   printk( KERN_INFO "=============== module removed =============\n" ); 
}

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

Дальше путём экспериментирования можно проверить работоспособность написанного модуля. Заодно эти эксперименты прояснят очень многое относительно функционирования драйверов устройств в Linux.

$ sudo insmod fixdev.ko major=250 
insmod: error inserting 'fixdev.ko': -1 Device or resource busy 
$ dmesg | grep === 
=== Can not register char device region

Этот major-номер в системе уже занят:

$ ls -l /dev | grep 250 
crw-rw----  1 root root      250,   0 Янв 22 11:49 hidraw0 
crw-rw----  1 root root      250,   1 Янв 22 11:49 hidraw1

В этот раз нам не повезло: наугад выбранный major-номер для нашего устройства оказывается уже занятым другим устройством в системе. В конечном итоге мы находим первый свободный major-номер в системе (в вашей системе он может быть совершенно другой):

$ sudo insmod fixdev.ko major=255 
$ ls -l /dev | grep 255 
$ dmesg | grep === 
=========== module installed 255:0 ============== 
$ lsmod | grep fix 
fixdev                  1384  0 
$ cat /proc/devices | grep my_ 
255 my_cdev_dev

Драйвер успешно установлен! Но для его использования этого недостаточно, и здесь становится заметной важная особенность реализации подсистемы устройств: драйвер оперирует с устройством как с парой номеров major/minor, а все команды GNU и функции POSIX API оперируют с устройством как с именем в каталоге /dev. Для работы с устройством мы должны установить взаимно однозначное соответствие между major/minor номерами и именем устройства. Сначала мы создадим такое именованное устройство вручную, указав произвольное имя и связав его с major/minor номерами, обслуживаемыми модулем:

$ sudo mknod -m0666 /dev/abc c 255 0 
$ cat /dev/abc 
Hello, world! 
$ sudo rm /dev/abc

Создание имени устройства в текущем каталоге (а не в каталоге /dev как полагается) еще убедительнее иллюстрирует, что первично, а что вторично с позиции подсистемы устройств и драйвера.

$ sudo mknod -m0666 ./z0 c 255 0 
$ ls -l | grep ^c 
crw-rw-rw- 1 root root 255, 0 Янв 22 16:43 z0 
$ cat ./z0 
Hello, world! 
$ sudo rm ./z0

Экспериментируя с модулем, важно периодически его выгружать перед очередным тестом, причём этот процесс также может послужить источником информации.

$ cat /dev/abc 
Hello, world! 
$ sudo rmmod fixdev 
$ lsmod | grep fix 
$ cat /dev/abc 
cat: /dev/abc: Нет такого устройства или адреса 
$ ls -l /dev/abc 
crw-rw-rw- 1 root root 255, 0 Янв 22 14:13 /dev/abc

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

Особое внимание следует обратить на то, каким образом функция, обрабатывающая запросы read(), сообщает вызывающей программе об исчерпании потока доступных данных (признак EOF), так как это важнейшая функция операции чтения. Посмотрим, как утилита cat запрашивала данные, и что она получала в результате:

$ cat /dev/abc 
Hello, world! 
$ dmesg | tail -n20 | grep === 
=== read : 32768 
=== read return : 14 
=== read : 32768 
=== read return : 0

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

В конце можно проверить, что созданный драйвер поддерживает именно указанный ему диапазон minor-номеров, не больше, но и не меньше (0-2 в показанном примере):

$ sudo insmod fixdev.ko major=255 
$ sudo mknod -m0666 /dev/abc2 c 255 2 
$ sudo mknod -m0666 /dev/abc3 c 255 3 
$ ls -l /dev | grep 255 
crw-rw-rw-  1 root root      255,   2 Янв 22 14:37 abc2 
crw-rw-rw-  1 root root      255,   3 Янв 22 14:37 abc3 
$ cat /dev/abc2 
Hello, world! 
$ cat /dev/abc3 
cat: /dev/abc3: Нет такого устройства или адреса

Заключение

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

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