Содержание


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

Comments

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

Сравнение различных подходов

Оставим в стороне рассмотрение softirq, так как этот механизм подробно обсуждался в предыдущих статьях, а сложность его использования оправдана при необходимости масштабирования высокоскоростных процессов на большое число обслуживающих процессоров в SMP. Для нас больший интерес представляют два других подхода —тасклеты и очереди отложенных действий, предлагающие две различные схемы реализации отложенных действий для современной ОС Linux, перенося часть работы из верхней половины в нижнюю половину драйвера. В тасклетах реализуется простой и ясный механизм с низкой латентностью, а очереди обладают более гибким и развитым API, позволяющим обслуживать несколько отложенных действий в порядке очередности. В каждой схеме откладывание (планирование) последующей работы выполняется из контекста прерывания, но тасклеты запускаются автоматически и не останавливаются до тех пор, пока обработка не будет завершена, тогда как очереди отложенных действий разрешают функциям-обработчикам переходить в блокированное состояние. В этом и состоит их принципиальное отличие: рабочая функция тасклета не может блокироваться.

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

В листинге 1 представлен пример использования тасклета. Исходный код примеров можно также найти в архиве IRQ.tgz в разделе "Материалы для скачивания".

Листинг 1. Использование тасклетов
#include <linux/module.h>
#include <linux/jiffies.h>
#include <linux/interrupt.h>
#include <linux/timex.h>

MODULE_LICENSE("GPL");

cycles_t cycles1, cycles2;
static u32 j1, j2;

char tasklet_data[] = "tasklet_function was called";

/* "нижняя половина" функции-обработчика */
void tasklet_function( unsigned long data ) {
   j2 = jiffies;
   cycles2 = get_cycles();
   printk( "%010lld [%05d] : %s\n", (long long unsigned)cycles2, j2, (char*)data );
   return;
}

DECLARE_TASKLET( my_tasklet, tasklet_function, (unsigned long)&tasklet_data );

int init_module( void ) {
   j1 = jiffies;
   cycles1 = get_cycles();
   printk( "%010lld [%05d] : tasklet_scheduled\n", (long long unsigned)cycles1, j1 );
   /* запланируем исполнение "нижней половины" */
   tasklet_schedule( &my_tasklet );
   return 0;
}

void cleanup_module( void ) {
  /* остановка тасклета перед завершением программы. */
  tasklet_kill( &my_tasklet );
  return;
}

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

$ uname -a 
Linux notebook.localdomain 2.6.32.9-70.fc12.i686.PAE #1 ...
$ sudo insmod mod_tasklet.ko
$ dmesg | tail -n100 | grep " : "
51300758164810 [30536898] : tasklet_scheduled
51300758185080 [30536898] : tasklet_function was called
$ sudo rmmod mod_tasklet
$ sudo nice -n19 ./clock
00002EE46EFE8248
00002EE46F54F4E8
00002EE46F552148
1663753694

По временным меткам видно, что выполнение функции тасклета произошло после планирования тасклета на выполнение, но латентность оказалась очень низкой, так как системный счётчик jiffies не успел изменить своё значение, т.е. всё произошло в пределах одного системного такта. Отсрочка выполнения составила порядка 20000 процессорных тактов для процессора частотой 1.66 ГГц, а это составляет порядка 12 микросекунд.

В следующем примере мы выполним аналогичные действия, но уже с помощью очередей отложенных действий.

Листинг 2. Использование очередей отложенных действий
#include <linux/module.h>
#include <linux/jiffies.h>
#include <linux/interrupt.h>
#include <linux/timex.h>

MODULE_LICENSE("GPL");

static struct workqueue_struct *my_wq;

typedef struct {
  struct work_struct my_work;
  int    id;
  u32    j;
  cycles_t cycles;
} my_work_t;

/* "нижняя половина" функции-обработчика */
static void my_wq_function( struct work_struct *work ) {
   u32 j = jiffies;
   cycles_t cycles = get_cycles();
   my_work_t *wrk = (my_work_t*)work;
   printk( "#%d : %010lld [%05d] => %010lld [%05d]\n",
           wrk->id,
           (long long unsigned)wrk->cycles, wrk->j,
           (long long unsigned)cycles, j
         );
  kfree( (void *)wrk );
  return;
}

int init_module( void ) {
   my_work_t *work1, *work2;
   int ret;
   my_wq = create_workqueue( "my_queue" );
   if( my_wq ) {
      /* запланировать выполнение определённых действий (work1) в очереди */
      work1 = (my_work_t*)kmalloc( sizeof(my_work_t), GFP_KERNEL );
      if( work1 ) {
         INIT_WORK( (struct work_struct *)work1, my_wq_function );
         work1->id = 1;
         work1->j = jiffies;
         work1->cycles = get_cycles();
         ret = queue_work( my_wq, (struct work_struct *)work1 );
      }
      /* запланировать выполнение дополнительных действий (work2) в очереди */
      work2 = (my_work_t*)kmalloc( sizeof(my_work_t), GFP_KERNEL );
      if( work2 ) {
         INIT_WORK( (struct work_struct *)work2, my_wq_function );
         work2->id = 2;
         work2->j = jiffies;
         work2->cycles = get_cycles();
         ret = queue_work( my_wq, (struct work_struct *)work2 );
      }
   }
   return 0;
}

void cleanup_module( void ) {
  flush_workqueue( my_wq );
  destroy_workqueue( my_wq );
  return;
}

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

$ sudo insmod mod_workqueue.ko
$ lsmod | head -n3
Module                  Size  Used by
mod_workqueue           1079  0
vfat                    6740  1
$ ps -ef | grep my_
root     17058     2  0 22:43 ?        00:00:00 [my_queue/0]
root     17059     2  0 22:43 ?        00:00:00 [my_queue/1]
olej     17061 11385  0 22:43 pts/10   00:00:00 grep my_

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

$ dmesg | grep "=>" 
#1 : 54741885665810 [32606771] => 54741890115000 [32606774] 
#2 : 54741885675880 [32606771] => 54741890128690 [32606774] 
$ sudo rmmod mod_workqueue

На этот раз мы поместили в очередь отложенных действий два экземпляра работы, и каждый из них был отстрочен от точки планирования на 3 системных такта, т.е. в данном случае латентность реакции была существенно больше, чем в случае с тасклетом, что соответствует утверждениям, имеющимся в профильной литературе.

Дополнительные вопросы

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

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

Ответ на второй вопрос мне (пока) не известен, а вот ответ на первый вопрос можно узнать с помощью примера, приведённого в листинге 3.

Листинг 3. Разделение линии прерывания
#include <linux/module.h>
#include <linux/interrupt.h>

MODULE_LICENSE( "GPL v2" );
#define SHARED_IRQ 1
#define MAX_SHARED 9
#define NAME_SUFFIX "serial_"
#define NAME_LEN   10
static int irq = SHARED_IRQ, num = 2;
module_param( irq, int, 0 );
module_param( num, int, 0 );

static irqreturn_t handler( int irq, void *id ) {
   cycles_t cycles = get_cycles();
   printk( KERN_INFO "%010lld : irq=%d - handler #%d\n", cycles, irq, (int)id );
   return IRQ_NONE;
}

static char dev[ MAX_SHARED ][ NAME_LEN ];

int init_module( void ) {
   int i;
   if( num > MAX_SHARED ) num = MAX_SHARED;
   for( i = 0; i < num; i++ ) {
      sprintf( dev[ i ], "serial_%02d", i + 1 );
      if( request_irq( irq, handler, IRQF_SHARED, dev[ i ], (void*)
      ( i + 1 ) ) ) return -1;
   }
   return 0;
}

void cleanup_module( void ) {
   int i;
   for( i = 0; i < num; i++ ) {
      synchronize_irq( irq );
      free_irq( irq, (void*)( i + 1 ) );
   }
}

Здесь на одну (любую) линию IRQ (параметр модуля irq) последовательно один за другим устанавливается num (параметр num, по умолчанию равный 2) обработчиков прерывания, которые фиксируют время получения ими прерывания на обработку. Выполним запуск данного модуля в тестовой системе (дистрибутив Ubuntu 10.04.3, развёрнутый на виртуальной машине в Virtual Box, ядро 2.6.32):

$ uname -r 
2.6.32-33-generic 
$ cat /proc/interrupts | grep hci 
           CPU0 
  5:      39793    XT-PIC-XT        ahci, Intel 82801AA-ICH 
 10:      92471    XT-PIC-XT        ehci_hcd:usb1, eth0 
 11:       3845    XT-PIC-XT        ohci_hcd:usb2

В данном случае нас интересует линия IRQ 11 (USB-мышь):

$ sudo insmod mod_ser.ko irq=11
$ lsmod | head -n3
Module                  Size  Used by
mod_ser                 1130  0
binfmt_misc             6587  1
$ cat /proc/interrupts | grep hci
  5:      16120    XT-PIC-XT        ahci, Intel 82801AA-ICH
 10:      59800    XT-PIC-XT        ehci_hcd:usb1, eth0
 11:       3820    XT-PIC-XT        ohci_hcd:usb2, serial_01, serial_02

В следующем выводе представлен фрагмент системного журнала при перемещении мыши:

$ dmesg | grep 'irq=11' | head -n6
[ 9499.031303] 15199977878339 : irq=11 - handler #1
[ 9499.031341] 15199977948850 : irq=11 - handler #2
[ 9499.047312] 15200003431449 : irq=11 - handler #1
[ 9499.047351] 15200003502182 : irq=11 - handler #2
[ 9499.072494] 15200043610967 : irq=11 - handler #1
[ 9499.072532] 15200043682638 : irq=11 - handler #2

Здесь по меткам счётчика процессорных тактов (rdtsc) можно предположить, что ранее зарегистрированный обработчик срабатывает первым, то есть новые обработчики прерывания устанавливаются в хвост очереди разделяемых прерываний. Для подтверждения и сравнения запустим этот пример но на другой тестовой системе (дистрибутив Fedora 14 PFR, развёрнутый на той же виртуальной машине в Virtual Box, ядро 2.6.32) и получим крайне интересные результаты:

$ uname -r
2.6.35.13-92.fc14.i686
$ cat /proc/interrupts | grep hci
  19:       8683   IO-APIC-fasteoi   ehci_hcd:usb1, eth0
  21:       9079   IO-APIC-fasteoi   ahci, Intel 82801AA-ICH
  22:       1634   IO-APIC-fasteoi   ohci_hcd:usb2

Как видно, на одном и том же компьютере виртуальные машины с разные Linux-дистрибутивами видят один и тот же аппаратный контроллер прерываний совершенно по разному. Продолжим наше исследование, используя ту же линию IRQ для USB-мыши (правда её номер в этот раз будет 22):

$ sudo insmod mod_ser.ko irq=22 num=5
$ cat /proc/interrupts | grep 22:
  22:       1653   IO-APIC-fasteoi   ohci_hcd:usb2, serial_01, serial_02 ...
$ sudo rmmod mod_ser
 $ dmesg | grep 'irq=22' | tail -n10
[10618.475392] 16971191379365 : irq=22 - handler #1
[10618.475392] 16971192198665 : irq=22 - handler #2
[10618.475392] 16971192685213 : irq=22 - handler #3
[10618.475392] 16971193423264 : irq=22 - handler #4
[10618.475392] 16971193880266 : irq=22 - handler #5
[10669.904309] 17053241497863 : irq=22 - handler #1
[10669.905331] 17053242842991 : irq=22 - handler #2
[10669.905331] 17053243293397 : irq=22 - handler #3
[10669.905331] 17053243600477 : irq=22 - handler #4
[10669.907843] 17053245722371 : irq=22 - handler #5

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

Заключение

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


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


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=932896
ArticleTitle=Обслуживание периферии в коде модулей ядра: Часть 61. Обработка прерываний, примеры и обсуждение
publish-date=06062013