Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

На главную -> MyLDP -> Электронные книги по ОС Linux
Цилюрик О.И. Модули ядра Linux
Назад Внешние интерфейсы модуля Вперед

Примеры реализации

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

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( "5.2" );

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

	static ssize_t hello_read( struct file * file, char * buf, 
	                           size_t count, loff_t *ppos ) { 
	   int len = strlen( hello_str ); 
	   if( count < len ) return -EINVAL; 
	   if( *ppos != 0 ) return 0; 
	   if( copy_to_user( buf, hello_str, len ) ) return -EINVAL; 
	   *ppos = len; 
	   return len; 
	} 

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

	static void __exit hello_exit( void ); 
	module_exit( hello_exit ); 

Тогда первый вариант драйвера (архив cdev.tgz), использующий структуру struct cdev, будет иметь вид (рассмотренный общий файл dev.h включён как преамбула этого кода, так будет и в дальнейших примерах):

hello_dev.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 hello_open( struct inode *n, struct file *f ) { 
	   if( device_open ) return -EBUSY; 
	   device_open++; 
	   return EOK; 
	} 
	
	static int hello_release( struct inode *n, struct file *f ) { 
	   device_open--; 
	   return EOK; 
	} 
	
	static const struct file_operations hello_fops = { 
	   .owner = THIS_MODULE, 
	   .open = hello_open, 
	   .release = hello_release, 
	   .read  = hello_read, 
	}; 
	
	#define DEVICE_FIRST  0 
	#define DEVICE_COUNT  1 
	#define MODNAME "hello_dev"
	static struct cdev hcdev; 
	
	static int __init hello_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, &hello_fops ); 
	   hcdev.owner = THIS_MODULE; 
	   hcdev.ops = &hello_fops;   // обязательно! - cdev_init() недостаточно? 
	   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 hello_exit( void ) { 
	   cdev_del( &hcdev ); 
	   unregister_chrdev_region( MKDEV( major, DEVICE_FIRST ), DEVICE_COUNT ); 
	   printk( KERN_INFO "=============== module removed ==================\n" ); 
	} 

Здесь показан только один (для краткости) уход на метку ошибки выполнения (err:) на шаге инсталляции модуля, в коде реальных модулей вы увидите целые цепочки подобных конструкций.

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

Итак, проверяем работоспособность написанного модуля (немного поэкспериментируем):

$ sudo insmod ./hello_dev.ko major=250

insmod: error inserting './hello_dev.ko': -1 Device or resource busy

$ dmesg | tail -n1

Can not register char device region!

$ ls -l /dev | grep ' 250'

	crw-rw----  1 root root      250,  0 Июл  2 09:59 hidraw0 
	crw-rw----  1 root root      250,  1 Июл  2 09:59 hidraw1 
	crw-rw----  1 root root      250,  2 Июл  2 09:59 hidraw2 

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

Выберем более удачный старший номер:

$ sudo insmod ./hello_dev.ko major=200

$ cat /proc/devices | grep hel

200 hello_dev

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

$ sudo insmod ./hello_dev.ko

$ cat /proc/devices | grep hel

248 hello_dev

Но самого такого устройства (с major равным 248) у нас пока не существует в /dev (мы не сможем пока воспользоваться модулем, даже если он загружен). Это та вторая группа вопросов, которая упоминалась раньше: как создаётся устройство с заданными номерами? Пока мы создадим такое символьное устройство вручную, связывая его со старшим номером, обслуживаемым модулем, и проверим работу модуля:

$ sudo mknod -m0666 /dev/z0 c 248 0

$ cat /dev/z0

Hello, world!

$ sudo rmmod hello_dev

$ cat /dev/z0

cat: /dev/z0: Нет такого устройства или адреса

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

dyndev.c :

	#include <linux/device.h>
	...
	static dev_t dev; 
	static struct cdev hcdev; 
	static struct class *devclass; 

	static int __init hello_init( void ) { 
	...
	   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 "dyndev"
	   device_create( devclass, NULL, dev, "%s", DEVNAME ); 
	...
	} 

	static void __exit hello_exit( void ) { 
	   device_destroy( devclass, dev ); 
	   class_destroy( devclass ); 
	   cdev_del( &hcdev ); 
	...
	} 

Теперь нам не будет необходимости вручную создавать имя устройства в /dev отслеживать соответствие его номеров — имя возникает после загрузки модуля, и так же ликвидируется после выгрузки модуля:

$ sudo insmod dyndev.ko

$ lsmod | head -n2

Module Size Used by

dyndev 1366 0

$ ls -l /dev/dyn*

crw-rw---- 1 root root 248, 0 Июн 23 23:28 /dev/dyndev

$ cat /dev/dyndev

Hello, world!

$ cat /proc/modules | grep dyn

dyndev 1366 0 - Live 0xf7ed1000

$ ls -l /sys/class/dyn*

итого 0

lrwxrwxrwx 1 root root 0 Июн 23 23:31 dyndev -> ../../devices/virtual/dyn_class/dyndev

$ sudo rmmod dyndev

$ ls -l /dev/dyn*

ls: невозможно получить доступ к /dev/dyn*: Нет такого файла или каталога

Но контроль за номерами устройства, даже создаваемого динамически, сохраняется за модулем, независимо, либо он получает major от пользователя при старте, либо запрашивает его у системы. Такое динамическое управление номерами устройства очень упрощает рутинные операции увязывания, и наилучшим образом подходит для создания неких виртуальных устройств-интерфейсов, моделирующих какие-то логические сущности. Примерам такого создания являются общеизвестный интерфейс zaptel/DAHDI к оборудованию, используемый в VoIP проектах SoftSwitch, или подобный интерфейс, развиваемый производителем Sangoma для спектра своего оборудования поддержки линий связи E1/T1/J1 (и E3/T3/J3) — и в том и в другом случае, в /dev создаётся набор (возможно, до нескольких сот) фиктивных устройств-имён, взаимно-однозначно соответствующих столь же фиктивным уплотнённым по времени каналам линй E1/T1/J1. Тем не менее, далее можно читать-писать такие устройства совершенно реальными read() или write(), в точности так, как мы это делаем с реальным физическим устройством «в железе». Бегло структура интерфейса zaptel/DAHDI рассмотрена в качестве наглядной иллюстрации отдельным приложением.

Следующий вариант регистрации драйвера в системе — с динамическим созданием логического «устройства» вне привязки его к фиксированным номерам: сразу же создаётся требуемое имя в /dev, с использованием для него произвольных динамических номеров, «понравившихся» системе. Эту технику регистрации драйвера устройста часто называют в литературе как misc (miscelleneous) drivers, и она является частной конкретизацией обсуждавшегося выше механизма, со скрытым использованием использованием struct cdev. Это самая простая в кодировании техника создания устройств (из-за её краткости мы будем именно её использовать во множестве дальнейших примеров). Каждое такое устройство создаётся с major значением 10, но может выбирать свой уникальный minor. Именно поэтому, если драйвер собирается обслуживать целую сетку однотипных устройств, различающихся по minor, этот способ не есть хорошим кандидатом на использование.

В этом варианте драйвер регистрируется и создаёт символическое имя устройства в /dev одним единственным вызовом misc_register() (архив misc.tgz):

hello_dev.c :

	#include <linux/fs.h> 
	#include <linux/miscdevice.h>
	#include "../dev.h" 
	
	static const struct file_operations hello_fops = { 
	   .owner  = THIS_MODULE, 
	   .read   = hello_read, 
	}; 

	static struct miscdevice hello_dev = { 
	   MISC_DYNAMIC_MINOR, "hello", &hello_fops 
	}; 

	static int __init hello_init( void ) { 
	   int ret = misc_register( &hello_dev ); 
	   if( ret ) printk( KERN_ERR "unable to register misc device\n" ); 
	   return ret; 
	} 

	static void __exit hello_exit( void ) { 
	   misc_deregister( &hello_dev ); 
	} 

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

$ sudo insmod ./hello_dev.ko

$ lsmod | grep hello

hello_dev 909 0

$ ls -l /dev/hel*

crw-rw---- 1 root root 10, 56 Мар 4 00:32 /dev/hello

$ sudo cat /dev/hello

Hello, world!

$ sudo rmmod hello_dev

$ ls -l /dev/hel*

ls: невозможно получить доступ к /dev/hel*: Нет такого файла или каталога


Предыдущий раздел: Оглавление Следующий раздел:
Драйверы: интерфейс устройства   Управляющие операции устройства