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

Часть 21. Модуль как драйвер. Управление устройством с помощью ioctl()

Comments

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

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

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

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

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

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

Операции ioctl() обычно используются для выполнения над устройством некоторых специфических (управляющих) действий, которые не обеспечиваются регулярными POSIX вызовами (read(), write(), lseek(), ...). Часто это могут быть специальные действия, зависящие от конкретных аппаратных особенностей реализации устройства.

Долгое время операции ioctl() были основной техникой выполнения нестандартных действий над устройством (и не только в операционной системе Linux, но и в других, например, в той же MS Windows). Но операции ioctl() являются опасными: при их выполнении отклоняется контроль типизации параметров, и в качестве параметра может быть передана любая структура. Если в коде реализующей части вызова ioctl() (модуле ядра) не реализован тщательнейший контроль данных, переданных в вызов, то некоторые параметры могут вызывать тяжелые ошибки в коде ядра, вплоть до его разрушения. Серьёзной альтернативой управляющих операций ioctl() является чтение/запись информации через псевдоимена файловой системы /proc и /sys (об этом речь впереди). Показательным примером такой замены управляющих операций ioctl() может быть:

$ echo 0 > /proc/sys/net/ipv4/ip_forward

Такой командой в ядре Linux запрещается форвардинг (транзитная пересылка) сетевых пакетов между разными сетевыми интерфейсами. Соответственно, запись значения 1 разрешает эти операции в ядре.

Тем не менее, несмотря на некоторую настороженность, использование операций ioctl() весьма распространено и сегодня: как в предыдущих реализациях, так и в силу приверженности традициям разработчиков некоторых новых драйверов устройств.

Регистрация устройства

В этот раз в процессе реализации регистрации устройства для более полного обзора мы воспользуемся ещё одним, так называемым «старым» методом регистрации символьного устройства путём вызова метода register_chrdev(), о чём уже упоминалось ранее. Эта техника не потеряла актуальности и используется и сегодня, так что это будет демонстрация третьего и последнего альтернативного способа создания устройства. Полностью исходный код приведен в файле ioctl_dev.c.

Листинг 1. Драйвер устройства, использующего управление по ioctl().
#include "ioctl.h" 
#include "../dev.h" 

// работа с символьным устройством в старом стиле
static int dev_open( struct inode *n, struct file *f ) { 
   // ... при этом MINOR номер устройства должна обслуживать функция open() 
   // unsigned int minor = iminor( n ); 
   return 0; 
} 

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

static int dev_ioctl( struct inode *n, struct file *f, 
                      unsigned int cmd, unsigned long arg ) { 
   if( ( _IOC_TYPE( cmd ) != IOC_MAGIC ) ) 
      return -ENOTTY; 
   switch( cmd ) { 
      case IOCTL_GET_STRING: 
         if( copy_to_user( (void*)arg, hello_str, _IOC_SIZE( cmd ) ) ) 
            return -EFAULT; 
         break; 
      default: 
         return -ENOTTY; 
   } 
   return 0; 
} 

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

#define HELLO_MAJOR 200 
#define HELLO_MODNAME "my_ioctl_dev" 

static int __init dev_init( void ) { 
   int ret = register_chrdev( HELLO_MAJOR, HELLO_MODNAME, &hello_fops );
   if( ret < 0 ) { 
      printk( KERN_ERR "=== невозможно зарегистрировать символьное устройство\n" ); 
      goto err; 
   } 
err: 
   return ret; 
} 

static void __exit dev_exit( void ) { 
  unregister_chrdev( HELLO_MAJOR, HELLO_MODNAME ); 
}

Для согласования используемых типов и констант между модулем и работающими с ним пользовательскими приложениями введен совместно используемый заголовочный файл определений ioctl.h. Это обычная практика, поскольку функциональность операции ioctl() никак не стандартизована и индивидуальна для каждого проекта.

Листинг 2. Общие определения.
typedef struct _RETURN_STRING { 
   char buf[ 160 ]; 
} RETURN_STRING; 

#define IOC_MAGIC    'h' 
#define IOCTL_GET_STRING _IOR( IOC_MAGIC, 1, RETURN_STRING ) 
#define DEVPATH "/dev/ioctl"

Для проверки того, как функционирует управление модулем, необходимо создать тестовое приложение, использующее вызовы функции ioctl(). Полностью код приложения приведен в файле ioctl.c.

Листинг 3. Приложение, вызывающее функцию ioctl() для устройства.
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include "ioctl.h"

#define ERR(...) fprintf( stderr, "\7" __VA_ARGS__ ), exit( EXIT_FAILURE )

int main( int argc, char *argv[] ) {
   int dfd;                  // дескриптор устройства 
   if( ( dfd = open( DEVPATH, O_RDWR ) ) < 0 ) 
      ERR( "Open device error: %m\n" );
   RETURN_STRING buf;
   if( ioctl( dfd, IOCTL_GET_STRING, &buf ) ) 
      ERR( "IOCTL_GET_STRING error: %m\n" );
   fprintf( stdout, (char*)&buf );
   close( dfd );
   return EXIT_SUCCESS;
};

Испытаем созданное устройство и изучим полученные результаты.

$ sudo insmod ioctl_dev.ko 
$ cat /proc/devices | grep ioctl 
200 my_ioctl_dev 
$ sudo mknod -m0666 /dev/ioctl c 200 0 
$ ls -l /dev/ioctl 
crw-rw-rw- 1 root root 200, 0 Янв 22 23:27 /dev/ioctl 
$ cat /dev/ioctl 
Hello, world! 
$ ./ioctl 
Hello, world! 
$ sudo rmmod ioctl_dev 
$ ./ioctl 
Open device error: No such device or address 
$ cat /dev/ioctl 
cat: /dev/ioctl: Нет такого устройства или адреса

Обратите внимание на одну особенность, которая уже упоминалась раньше: при регистрации устройства вызовом register_chrdev() драйвер регистрирует только major-номер, но получает под свой контроль весь диапазон (0-255) minor-номеров:

$ sudo mknod -m0666 /dev/ioctl c 200 0 
$ sudo mknod -m0666 /dev/ioctl200 c 200 200 
$ ls -l /dev/ioctl* 
crw-rw-rw- 1 root root 200,   0 Янв 22 23:52 /dev/ioctl 
crw-rw-rw- 1 root root 200, 200 Янв 22 23:51 /dev/ioctl200 
$ cat /dev/ioctl 
Hello, world! 
$ cat /dev/ioctl200 
Hello, world!

В этом случае модуль сам должен различать устройства по minor-номеру: в обработчике операции open() по своему первому полученному параметру struct inode*.

Заключение

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


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


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=832470
ArticleTitle=Разработка модулей ядра Linux: Часть 21. Модуль как драйвер. Управление устройством с помощью ioctl()
publish-date=08302012