Содержание


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

Часть 25. Драйвер устройства. Окончание

Comments

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

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

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

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

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

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

Счётчик ссылок использования модуля

О счётчике ссылок использования модуля, и о том, что это один из важнейших элементов контроля безопасности загрузки модулей и целостности ядра системы неоднократно говорилось в предыдущих статьях. Ещё раз обратимся к вопросу счётчика ссылок использования модуля в контексте операций драйвера и на примере только что спроектированного модуля проясним отдельные аспекты этого важнейшего процесса. Для этого изготовим ещё одну (файл simple.c, который можно найти в архиве mopen.tgz в разделе "Материалы для скачивания") тестовую программу (пользовательский процесс).

Листинг 1. Тест для одиночного открытия устройства.
#include <fcntl.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include "mopen.h" 

int main( int argc, char *argv[] ) { 
   char dev[ 80 ] = "/dev/"; 
   strcat( dev, DEVNAM ); 
   int df; 
   if( ( df = open( dev, O_RDWR ) ) < 0 ) 
      printf( "open device error: %m" ), exit( EXIT_FAILURE ); 
   char msg[ 160 ]; 
   fprintf( stdout, "> " ); 
   fflush( stdout ); 
   gets( msg ); // gets() - опасная функция! 
   int res, len = strlen( msg ); 
   if( ( res = write( df, msg, len ) ) != len ) 
      printf( "write device error: %m" ); 
   char *p = msg; 
   do { 
      if( ( res = read( df, p, sizeof( msg ) ) ) > 0 ) { 
         *( p + res ) = '\0'; 
         printf( "read %d bytes: %s", res, p ); 
         p += res; 
      } 
      else if( res < 0 ) 
         printf( "read device error: %m" ); 
   } while ( res > 0 ); 
   fprintf( stdout, "%s", msg ); 
   close( df ); 
   return EXIT_SUCCESS; 
};

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

$ make 
...
/tmp/ccfJzj86.o: In function `main': 
simple.c:(.text+0x9c): 
    warning: the `gets' function is dangerous and should not be used.

Такое предупреждение при сборке не должно смущать, так как хотя все наверняка слышали об опасности функции gets() с точки зрения возможного переполнения буфера ввода, но для нашего теста это вполне допустимо. Однако при вводе мы всё-таки будем соблюдать разумную осторожность.

$ sudo insmod ./mmopen.ko mode=2 debug=1

Запустим 3 отдельных копии тестового процесса:

$ ./simple 
> 12345 
read 6 bytes: 12345 
12345 
$ ./simple 
> 987 
read 4 bytes: 987 
987 
$ ./simple 
> ^C

Представленный пример выполняется на 3-х независимых терминалах, и это достаточно сложно объяснить в линейном протоколе выполнения, но будем считать, что мы оставили 3 тестовых процесса заблокированными в ожидании ввода строки (символ приглашения '>'). В этом месте необходимо выполнить следующую команду.

$ lsmod | grep mmopen 
mmopen                  2455  3

Интересно, что команда lsmod показывает число ссылок на модуль, но "не знает" имён ссылающихся модулей и не показывает их, как это обычно делается в выводе lsmod. Теперь взглянем на это с точки зрения тестируемого модуля, открыв системный журнал.

$ dmesg | tail -n3 
open - node: f1899ce0, file: f2e5ff00, refcount: 1 
open - node: f1899ce0, file: f2f35880, refcount: 2 
open - node: f1899ce0, file: f2de2500, refcount: 3

Хорошо видно, как счётчик ссылок использования пробежал диапазон от 0 до 3. После этого введём строки (разной длины) на 2-х копиях тестового процесса, а последний (3-й) завершим по Ctrl+C (SIGINT), чтобы узнать, как счётчик использования отреагирует на завершение (аварийное) клиента по сигналу. После всех этих манипуляций в системном журнале появится:

$ dmesg | tail -n15 
write - file: f2e5ff00, write to f2ff9200 bytes 5; refcount: 3 
put bytes : 5 
read - file: f2e5ff00, read from f2ff9200 bytes 160; refcount: 3 
return bytes :  6 
read - file: f2e5ff00, read from f2ff9200 bytes 160; refcount: 3 
return : EOF 
close - node: f1899ce0, file: f2e5ff00, refcount: 3 
write - file: f2f35880, write to f1847800 bytes 3; refcount: 2 
put bytes : 3 
read - file: f2f35880, read from f1847800 bytes 160; refcount: 2 
return bytes :  4 
read - file: f2f35880, read from f1847800 bytes 160; refcount: 2 
return : EOF 
close - node: f1899ce0, file: f2f35880, refcount: 2 
close - node: f1899ce0, file: f2de2500, refcount: 1 
$ lsmod | grep mmopen 
mmopen                  2455  0

Обратите внимание, что на всём протяжении выполнения функции, реализующей операцию release() устройства, счётчик использования ещё не декрементирован, так как сессия файлового открытия ещё не завершена (представленный вывод выполняется именно из кода операции release()). Система производит декремент счётчика после выполнения операции release()!

Ещё хотелось бы подчеркнуть, что, как следует из протокола системного журнала, после выполнения операции open() другие операции из той же таблицы файловых операций (read(), write()) никоим образом не влияют на значение счётчика ссылок. Всё это говорит о том, что на сегодняшний день отслеживание ссылок использования при выполнении open() и close() корректно выполняется самим ядром (этот аспект довольно трудно понять в случаях, когда операции open() и close() переопределяются, не оставляя места для реализаций этих же операций, используемых по умолчанию).

Всё это было сказано потому, что:

  • выполнение операции open() при возможности того, что непосредственно после этого модуль будет выгружен, будет довольно рискованно: последующий read() выполнил бы обращение к уже освобождённой области памяти и мог бы иметь катастрофические последствия для всей системы;
  • до сих пор во многих публикациях рекомендуется увеличивать число ссылок для модуля непосредственно в коде при операции open() вызовом try_module_get( THIS_MODULE ) и, соответственно, при операции close() уменьшать счётчик ссылок вызовом module_put( THIS_MODULE ). Как показано выше, на сегодняшний день такая ручная манипуляция избыточна. Тем не менее, в отдельных ситуациях эта возможность по прежнему востребована, и её стоит иметь в своём "арсенале".
  • Более того, до сих пор в литературе бытует рекомендация пользоваться для этих целей макросами MOD_INC_USE_COUNT() и MOD_DEC_USE_COUNT(). Не ищите этих макросов, их нет в современном ядре, они остались в ядрах 2.4! Для работы со счётчиком ссылок пользуйтесь макросами, приведёнными в предыдущем пункте.

При этом не упускайте из виду, что если в результате каких-либо действий счётчик ссылок будет инкрементирован и не возвращён позже в исходное состояние, то такой модуль можно будет выгрузить только путем остановки и перезагрузки всей системы.

Блочные устройства

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

int register_blkdev( unsigned major, const char* ); 
void unregister_blkdev( unsigned major, const char* );

Главное отличие от символьных устройств, рассмотренных выше, состоит в использовании в качестве таблицы функций, реализующих операции, вместо struct file_operations используется struct block_device_operations (её можно найти в файле <linux/blkdev.h>).

struct block_device_operations { 
        int (*open) ( struct block_device *, fmode_t ); 
        int (*release) ( struct gendisk *, fmode_t ); 
        int (*locked_ioctl) ( struct block_device *, fmode_t, 
                              unsigned, unsigned long ); 
        int (*ioctl) ( struct block_device *, fmode_t, unsigned, unsigned long ); 
        int (*compat_ioctl) ( struct block_device *, fmode_t, 
                              unsigned, unsigned long ); 
        int (*direct_access) ( struct block_device *, sector_t, 
                               void **, unsigned long * ); 
        int (*media_changed) ( struct gendisk * ); 
        unsigned long long (*set_capacity) ( struct gendisk *, unsigned long long ); 
        int (*revalidate_disk) ( struct gendisk * ); 
        int (*getgeo)( struct block_device *, struct hd_geometry * ); 
        struct module *owner; 
};

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

Заключение

В данной статье были рассмотрены финальные штрихи к написанию основного канала обмена данными (/dev) драйвера устройства. На этом мы можем завершить рассмотрение основного канала обмена данными и перейти к рассмотрению операций по каналам диагностики и управления (/proc и /sys).


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


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=834551
ArticleTitle=Разработка модулей ядра Linux: Часть 25. Драйвер устройства. Окончание
publish-date=09112012