Библиотека сайта rus-linux.net
Цилюрик О.И. Модули ядра Linux | ||
Назад | Внешние интерфейсы модуля | Вперед |
Множественное открытие устройства
В рассмотренных выше вариантах мы совершенно дистанциировались от вопроса: как должен работать драйвер устройства, если устройство попытаются использовать (открыть) одновременно несколько пользовательских процессов. Этот вопрос оставляется полностью на усмотрение разработчику драйвера. Здесь может быть несколько вариантов:
- Драйвер вообще никак не контролирует возможности параллельного использования (то, что было во всех рассматриваемых примерах).
- Драйвер допускает только единственное открытие устройства; попытки параллельного открытия будут завершаться ошибкой до тех пор, пока использующий его процесс не закроет устройство.
- Драйвер допускает много параллельных сессий использования устройства. При этом драйвер должен реализовать индивидуальный экземпляр данных для каждой копии открытого устройства.
Детальнее это проще рассмотреть на примере (архив mopen.tgz). У нас будет модуль, реализующий все три названных варианта (параметр mode запуска модуля):
mmopen.c :
#include <linux/module.h> #include <linux/fs.h> #include <asm/uaccess.h> #include <linux/miscdevice.h> #include "mopen.h" MODULE_LICENSE( "GPL" ); MODULE_AUTHOR( "Oleg Tsiliuric <olej@front.ru>" ); MODULE_VERSION( "5.4" ); static int mode = 0; // открытие: 0 - без контроля, 1 - единичное, 2 - множественное module_param( mode, int, S_IRUGO ); static int debug = 0; module_param( debug, int, S_IRUGO ); #define LOG(...) if( debug !=0 ) printk( KERN_INFO __VA_ARGS__ ) static int dev_open = 0; static int mopen_open( struct inode *n, struct file *f ) { LOG( "open - node: %p, file: %p, refcount: %d", n, f, module_refcount( THIS_MODULE ) ); if( dev_open ) return -EBUSY; if( 1 == mode ) dev_open++; if( 2 == mode ) { f->private_data = kmalloc( LEN_MSG + 1, GFP_KERNEL ); if( NULL == f->private_data ) return -ENOMEM; strcpy( f->private_data, "dynamic: not initialized!" ); // динамический буфер } return 0; } static int mopen_release( struct inode *n, struct file *f ) { LOG( "close - node: %p, file: %p, refcount: %d", n, f, module_refcount( THIS_MODULE ) ); if( 1 == mode ) dev_open--; if( 2 == mode ) kfree( f->private_data ); return 0; } static char* get_buffer( struct file *f ) { static char static_buf[ LEN_MSG + 1 ] = "static: not initialized!"; // статический буфер : switch( mode ) { case 0: case 1: default: return static_buf; case 2: return (char*)f->private_data; } } // чтение из /dev/mopen : static ssize_t mopen_read( struct file *f, char *buf, size_t count, loff_t *pos ) { static int odd = 0; char *buf_msg = get_buffer( f ); LOG( "read - file: %p, read from %p bytes %d; refcount: %d", f, buf_msg, count, module_refcount( THIS_MODULE ) ); if( 0 == odd ) { int res = copy_to_user( (void*)buf, buf_msg, strlen( buf_msg ) ); odd = 1; put_user( '\n', buf + strlen( buf_msg ) ); res = strlen( buf_msg ) + 1; LOG( "return bytes : %d", res ); return res; } odd = 0; LOG( "return : EOF" ); return 0; } // запись в /dev/mopen : static ssize_t mopen_write( struct file *f, const char *buf, size_t count, loff_t *pos ) { int res, len = count < LEN_MSG ? count : LEN_MSG; char *buf_msg = get_buffer( f ); LOG( "write - file: %p, write to %p bytes %d; refcount: %d", f, buf_msg, count, module_refcount( THIS_MODULE ) ); res = copy_from_user( buf_msg, (void*)buf, len ); if( '\n' == buf_msg[ len -1 ] ) buf_msg[ len -1 ] = '\0'; else buf_msg[ len ] = '\0'; LOG( "put bytes : %d", len ); return len; } static const struct file_operations mopen_fops = { .owner = THIS_MODULE, .open = mopen_open, .release = mopen_release, .read = mopen_read, .write = mopen_write, }; static struct miscdevice mopen_dev = { MISC_DYNAMIC_MINOR, DEVNAM, &mopen_fops }; static int __init mopen_init( void ) { int ret = misc_register( &mopen_dev ); if( ret ) printk( KERN_ERR "unable to register %s misc device", DEVNAM ); return ret; } static void __exit mopen_exit( void ) { misc_deregister( &mopen_dev ); } module_init( mopen_init ); module_exit( mopen_exit );
Для тестирования полученного модуля мы будем использовать стандартные команды чтения и записи устройства: cat и echo, но этого нам будет недостаточно, и мы используем сделанное по этому случаю тестовое приложение, которое выполняет одновременно открытие двух дескрипторов нашего устройства, и делает на них поочерёдные операции записи-чтения (но в порядке выполнения операций чтения обратном записи):
pmopen.c :
#include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "mopen.h" char dev[ 80 ] = "/dev/"; int prepare( char *test ) { int df; if( ( df = open( dev, O_RDWR ) ) < 0 ) printf( "open device error: %m\n" ); int res, len = strlen( test ); if( ( res = write( df, test, len ) ) != len ) printf( "write device error: %m\n" ); else printf( "prepared %d bytes: %s\n", res, test ); return df; } void test( int df ) { char buf[ LEN_MSG + 1 ]; int res; printf( "------------------------------------\n" ); do { if( ( res = read( df, buf, LEN_MSG ) ) > 0 ) { buf[ res ] = '\0'; printf( "read %d bytes: %s\n", res, buf ); } else if( res < 0 ) printf( "read device error: %m\n" ); else printf( "read end of stream\n" ); } while ( res > 0 ); printf( "------------------------------------\n" ); } int main( int argc, char *argv[] ) { strcat( dev, DEVNAM ); int df1, df2; // разные дескрипторы одного устройства df1 = prepare( "1111111" ); df2 = prepare( "22222" ); test( df1 ); test( df2 ); close( df1 ); close( df2); return EXIT_SUCCESS; };
И модуль и приложение для слаженности своей работы используют небольшой общий заголовочный файл:
mopen.h :
#define DEVNAM "mopen" // имя устройства #define LEN_MSG 256 // длины буферов устройства
Пример, может, и несколько великоват, но он стоит того, чтобы поэкспериментировать с ним в работе для тонкого разграничения деталей возможных реализаций концепции устройства! Итак, первый вариант, когда драйвер никоим образом не контролирует открытия устройства (параметр mode здесь можно не указывать — это значение по умолчанию, я делаю это только для наглядности):
$ sudo insmod ./mmopen.ko mode=0
$ cat /dev/mopen
static: not initialized!
Записываем на устройство произвольную символьную строку:
$ echo 777777777 > /dev/mopen
$ cat /dev/mopen
777777777
$ ./pmopen
prepared 7 bytes: 1111111 prepared 5 bytes: 22222 ------------------------------------ read 6 bytes: 22222 read end of stream ------------------------------------ ------------------------------------ read 6 bytes: 22222 read end of stream ------------------------------------
$ sudo rmmod mmopen
Здесь мы наблюдаем нормальную работу драйвера устройства при тестировании его утилитами POSIX (echo/cat) — это уже важный элемент контроля корректности, и с этих проверок всегда следует начинать. Но в контексте множественного доступа происходит полная ерунда: две операции записи пишут в один статический буфер устройства, а два последующих чтения, естественно, оба читают идентичные значения, записанные более поздней операцией записи. Очевидно, это совсем не то, что мы хотели бы получить от устройства!
Следующий вариант: устройство допускает только единичные операции доступа, и до тех пор, пока использующий процесс его не освободит, все последующие попытки использования устройства будут безуспешные:
$ sudo insmod ./mmopen.ko mode=1
$ cat /dev/mopen
static: not initialized!
$ echo 777777777 > /dev/mopen
$ cat /dev/mopen
777777777
$ ./pmopen
prepared 7 bytes: 1111111 open device error: Device or resource busy write device error: Bad file descriptor ------------------------------------ read 8 bytes: 1111111 read end of stream ------------------------------------ ------------------------------------ read device error: Bad file descriptor ------------------------------------
$ sudo rmmod mmopen
Хорошо видно, что при второй попытке открытия устройства возникла ошибка «устройство занято».
Следующий вариант: устройство допускающее параллельный доступ, и работающее в каждой копии со своим экземпляром данных, повторяем всё те же манипуляции:
$ sudo insmod ./mmopen.ko mode=2
$ cat /dev/mopen
dynamic: not initialized!
$ echo 777777777 > /dev/mopen
$ cat /dev/mopen
dynamic: not initialized!
Стоп! ... Очень странный результат. Понять то, что происходит, нам поможет отладочный режим загрузки модуля (для этого и добавлен параметр запуска debug) и содержимое системного журнала (показанный вывод в точности соответствует показанной выше последовательности команд):
$ sudo insmod ./mmopen.ko mode=2 debug=1
$ echo 9876543210 > /dev/mopen
$ cat /dev/mopen
dynamic: not initialized!
$ dmesg | tail -n10
open - node: f2e855c0, file: f2feaa80, refcount: 1 write - file: f2feaa80, write to f2c5f000 bytes 11; refcount: 1 put bytes : 11 close - node: f2e855c0, file: f2feaa80, refcount: 1 open - node: f2e855c0, file: f2de2d80, refcount: 1 read - file: f2de2d80, read from f2ff9600 bytes 32768; refcount: 1 return bytes : 26 read - file: f2de2d80, read from f2ff9600 bytes 32768; refcount: 1 return : EOF close - node: f2e855c0, file: f2de2d80, refcount: 1
Тестовые операции echo и cat, каждая, открывают свой экземпляр устройства, выполняют требуемую операцию и закрывают устройство. Следующая выполняемая команда работает с совершенно другим экземпляром устройства и, соответственно, с другой копией данных! Это косвенно подтверждает и число ссылок на модуль после завершения операций (но этом мы поговорим детально чуть ниже):
$ lsmod | grep mmopen
mmopen 2459 0
Хотя именно то, для чего мы готовили драйвер — срабатывает отменно:
$ ./pmopen
prepared 7 bytes: 1111111 prepared 5 bytes: 22222 ------------------------------------ read 8 bytes: 1111111 read end of stream ------------------------------------ ------------------------------------ read 6 bytes: 22222 read end of stream ------------------------------------
$ sudo rmmod mmopen
$ dmesg | tail -n60
open - node: f2e85950, file: f2f35300, refcount: 1 write - file: f2f35300, write to f2ff9600 bytes 7; refcount: 1 put bytes : 7 open - node: f2e85950, file: f2f35900, refcount: 2 write - file: f2f35900, write to f2ff9200 bytes 5; refcount: 2 put bytes : 5 read - file: f2f35300, read from f2ff9600 bytes 256; refcount: 2 return bytes : 8 read - file: f2f35300, read from f2ff9600 bytes 256; refcount: 2 return : EOF read - file: f2f35900, read from f2ff9200 bytes 256; refcount: 2 return bytes : 6 read - file: f2f35900, read from f2ff9200 bytes 256; refcount: 2 return : EOF close - node: f2e85950, file: f2f35300, refcount: 2 close - node: f2e85950, file: f2f35900, refcount: 1
Как итог этого рассмотрения, вопрос: всегда ли последний вариант (mode=2) лучше других (mode=0 или mode=1)? Этого категорично утверждать нельзя! Очень часто устройство физического доступа (аппаратная реализация) по своей природе требует только монопольного его использования, и тогда схема множественного параллельного доступа становится неуместной. Опять же, схема множественного доступа (в такой или иной реализации) должна предусматривать динамическое управление памятью, что принято считать более опасным в системах критической надёжности и живучести (но и само это мнение тоже может вызывать сомнения). В любом случае, способ открытия устройства может реализовываться по самым различным алгоритмам, должен соответствовать логике решаемой задачи, накладывает требования на реализацию всех прочих операций на устройстве, и, в итоге, заслуживает самой пристальной проработки при начале нового проекта.
Предыдущий раздел: | Оглавление | Следующий раздел: |
Управляющие операции устройства | Счётчик ссылок использования модуля |