Содержание


Отладка и тестирование модулей ядра: Часть 76. Отладка в ядре. Практические советы

Comments

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

Интерфейсы пространства пользователя к модулю

Для контроля значений ключевых переменных и их изменения внутри модуля эти переменные можно отобразить в псевдофайловые системы /proc, а ещё лучше /sys. Подобный приём часто применяется для счётчика прерываний, обработанных в драйвере, как это показано в примере ниже (этот пример также доказывает, что таким способом можно контролировать переменные даже внутри обработчиков аппаратных прерываний). Полный код примера можно найти в архиве simple-debug.tgz в разделе "Материалы для скачивания".

Листинг 1. Отображение переменных модуля в систему /sys (файл mdsys.c)
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/version.h>

#define SHARED_IRQ 16              // IRQ-линия прерывания для eth0
static int irq = SHARED_IRQ;
module_param( irq, int, S_IRUGO ); // может измениться на вашей системе

static unsigned int irq_counter = 0;
static irqreturn_t mdsys_interrupt( int irq, void *dev_id ) {
   irq_counter++;
   return IRQ_NONE;
}

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32)
static ssize_t show( struct class *class, struct class_attribute *attr, char *buf ) {
#else
static ssize_t show( struct class *class, char *buf ) {
#endif
   sprintf( buf, "%d\n", irq_counter );
   return strlen( buf );
}

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32)
static ssize_t store( struct class *class, struct class_attribute *attr,
                      const char *buf, size_t c )
#else
static ssize_t store( struct class *class, const char *buf, size_t count ) {
#endif
   int i, res = 0;
   const char dig[] = "0123456789";
   for( i = 0; i < count; i++ ) {
      char *p = strchr( dig, (int)buf[ i ] );
      if( NULL == p ) break;
      res = res * 10 + ( p - dig );
   }
   irq_counter = res;
   return count;
}

CLASS_ATTR( mds, 0666, &show, &store ); // => struct class_attribute class_attr_mds
static struct class *mds_class;
static int my_dev_id;

int __init init( void ) {
   int res = 0;
   mds_class = class_create( THIS_MODULE, "mds-class" );
   if( IS_ERR( mds_class ) ) printk( KERN_ERR "bad class create\n" );
   res = class_create_file( mds_class, &class_attr_mds );
   if( res != 0 ) printk( KERN_ERR "bad class create file\n" );
   if( request_irq( irq, mdsys_interrupt, IRQF_SHARED, "my_interrupt", &my_dev_id ) )
      res = -1;
   return res;
}

void cleanup( void ) {
   synchronize_irq( irq );
   free_irq( irq, &my_dev_id );
   class_remove_file( mds_class, &class_attr_mds );
   class_destroy( mds_class );
   return;
}

module_init( init );
module_exit( cleanup );
MODULE_AUTHOR( "Oleg Tsiliuric <olej@front.ru>" );
MODULE_DESCRIPTION( "module in debug" );
MODULE_LICENSE( "GPL v2" );

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

Для проверки загрузим созданный модуль для контроля линии IRQ сетевого адаптера (хотя можно было выбрать и любую другую используемую IRQ-линию, например, системного таймера):

$ cat /proc/interrupts | grep eth
  16:      34985          0   IO-APIC-fasteoi   i915, eth0
$ sudo insmod mdsys.ko irq=16$ cat /sys/class/mds-class/mds
280
$ cat /sys/class/mds-class/mds
301
$ cat /sys/class/mds-class/mds
353

В данном случае мы контролируем нарастающее значение счётчика сработавших прерываний. Изменим (в динамике) начальное значение этого счётчика, от которого происходит инкремент и отметим это изменение в выводе:

$ echo 10 > /sys/class/mds-class/mds$ cat /sys/class/mds-class/mds
29
$ sudo rmmod mdsys

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

Комплементарный отладочный модуль

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

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

Для детального рассмотрения трансформируем в эту схему пример, представленный в листинге 1. Причём сделаем это без всяких изменений и улучшений, чтобы мы могли сравнить исходный код примеров по принципу: что было и что стало. Полный код всех файлов, перечисленных ниже, можно найти в архиве simple-debug.tgz разделе "Материалы для скачивания".

Листинг 2. Файл общих определений (файл mdsys2.h)
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/version.h>

extern unsigned int irq_counter;
int __init init( void );
void __exit cleanup( void );
module_init( init );
module_exit( cleanup );
MODULE_AUTHOR( "Oleg Tsiliuric <olej@front.ru>" );
MODULE_DESCRIPTION( "module in debug" );
MODULE_LICENSE( "GPL v2" );
Листинг 3. Собственно проектируемый (отлаживаемый) модуль (файл mdsys2.с)
#include "mdsys2.h"
#define SHARED_IRQ 16              // IRQ-линия прерывания для eth0
static int irq = SHARED_IRQ;
module_param( irq, int, S_IRUGO ); // может измениться на вашей системе

unsigned int irq_counter = 0;
EXPORT_SYMBOL( irq_counter );
static irqreturn_t mdsys_interrupt( int irq, void *dev_id ) {
   irq_counter++;
   return IRQ_NONE;
}

static int my_dev_id;
int __init init( void ) {
   if( request_irq( irq, mdsys_interrupt, IRQF_SHARED, "my_interrupt", &my_dev_id ) )
      return -1;
   else
      return 0;
}

void cleanup( void ) {
   synchronize_irq( irq );
   free_irq( irq, &my_dev_id );
   return;
}
Листинг 4. Модуль, создающий для него отладочный интерфейс (mdsysс.h)
#include "mdsys2.h"
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32)
static ssize_t show( struct class *class, struct class_attribute *attr, char *buf ) {
#else
static ssize_t show( struct class *class, char *buf ) {
#endif
   sprintf( buf, "%d\n", irq_counter );
   return strlen( buf );
}

#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,32)
static ssize_t store( struct class *class, struct class_attribute *attr, 
                      const char *buf, size_t c )
#else
static ssize_t store( struct class *class, const char *buf, size_t count ) {
#endif
   int i, res = 0;
   const char dig[] = "0123456789";
   for( i = 0; i < count; i++ ) {
      char *p = strchr( dig, (int)buf[ i ] );
      if( NULL == p ) break;
      res = res * 10 + ( p - dig );
   }
   irq_counter = res;
   return count;
}

CLASS_ATTR( mds, 0666, &show, &store ); // => struct class_attribute class_attr_mds
static struct class *mds_class;

int __init init( void ) {
   int res = 0;
   mds_class = class_create( THIS_MODULE, "mds-class" );
   if( IS_ERR( mds_class ) ) printk( KERN_ERR "bad class create\n" );
   res = class_create_file( mds_class, &class_attr_mds );
   if( res != 0 ) printk( KERN_ERR "bad class create file\n" );
   return res;
}

void cleanup( void ) {
   class_remove_file( mds_class, &class_attr_mds );
   class_destroy( mds_class );
   return;
}

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

$ sudo insmod mdsys2.ko
$ sudo insmod mdsysc.ko
$ lsmod | head -n3
Module                  Size  Used by
mdsysc                   934  0
mdsys2                   844  1 mdsysc
$ cat /sys/class/mds-class/mds
784
$ cat /sys/class/mds-class/mds
825
$ echo 0 > /sys/class/mds-class/mds$ cat /sys/class/mds-class/mds
21

Теперь удалим отладочный модуль:

$ sudo rmmod mdsysc

Отлаживаемый модуль продолжает работать, но отладочные интерфейсы к нему исчезли:

$ lsmod | head -n3
Module                  Size  Used by
mdsys2                   844  0
lp                      6794  0
$ cat /sys/class/mds-class/mds
cat: /sys/class/mds-class/mds: Нет такого файла или каталога
$ sudo rmmod mdsys2

Небольшие полезные советы

Пишите в файлы протоколов

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

Чаще перезагружайте систему!

Отладка модулей ядра отличается от отладки пользовательского пространства тем, что очередное аварийное завершение теста модуля может оставлять "следы" в ядре, создавая тем малозаметные (или поздно обнаруживаемые) аномалии в поведении системы. Особенно часто это наблюдается, например, при отработке интерфейсов драйвера в файловую систему /proc.

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

Для того, чтобы избежать десятков часов бездарно потерянного времени, при работе над модулями, перезагружайте ОС Linux время от времени, даже если вам кажется, что она работает совершенно нормально. После перезагрузки результаты повторения только-что выполненного теста могут радикально поменяться!

Используйте естественные POSIX тестеры

Здесь имеется в виду, что при отработке модуля всегда, прежде, чем начинать более жёсткое тестирование драйвера, проверьте его реакцию по чтению и запись на естественные POSIX тестеры: cat для чтения и echo для записи, например. В этом качестве могут быть полезны и другие стандартные утилиты Linux, например, cp. Возможно, для обеспечения совместимости функционирования с POSIX-командами, вам потребуется добавить к драйверу дополнительную функциональность (например, обработка ситуации EOF), которая может отсутствовать в формальной спецификации на продукт. Но достижение POSIX совместимости стоит затраченного дополнительного труда!

Тестируйте чтение сериями

Выполняя проверку операций read(), не ограничивайтесь одиночной операцией тестирования. Вместо этого проверьте реакцию модуля на серию последовательных операций чтения. Благодаря этому вы убедитесь, что драйвер не только правильно отрабатывает операцию, но и нормально восстанавливается после операции и готов к выполнению следующей. Другими словами, вместо одиночной операции cat (в простейшем случае) делайте несколько последовательных, сверяя их идентичность:

$ cat /dev/xxx
REZULT
$ cat /dev/xxx
REZULT
$ cat /dev/xxx
REZULT

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

Заключение

В статье описываются некоторые приёмы, оказывающиеся полезными при отладке и испытаниях создаваемых модулей ядра Linux. В следующей статье мы рассмотрим вопросы сборки и установки ядра.


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


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=950567
ArticleTitle=Отладка и тестирование модулей ядра: Часть 76. Отладка в ядре. Практические советы
publish-date=10292013