Библиотека сайта 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)? Этого категорично утверждать нельзя! Очень часто устройство физического доступа (аппаратная реализация) по своей природе требует только монопольного его использования, и тогда схема множественного параллельного доступа становится неуместной. Опять же, схема множественного доступа (в такой или иной реализации) должна предусматривать динамическое управление памятью, что принято считать более опасным в системах критической надёжности и живучести (но и само это мнение тоже может вызывать сомнения). В любом случае, способ открытия устройства может реализовываться по самым различным алгоритмам, должен соответствовать логике решаемой задачи, накладывает требования на реализацию всех прочих операций на устройстве, и, в итоге, заслуживает самой пристальной проработки при начале нового проекта.
| Предыдущий раздел: | Оглавление | Следующий раздел: |
| Управляющие операции устройства | Счётчик ссылок использования модуля |
