Содержание


Обслуживание периферии в коде модулей ядра: Часть 59. Создание "нижней половины" обработчика прерываний

Comments

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

Отложенная обработка прерываний

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

Термин «нижняя половина» обработчика прерываний используется для общего обозначения нескольких различных способов реализации, которые можно использовать для осуществления отложенной обработки прерываний. Когда-то в ядре Linux был метод организации отложенной обработки, который так и именовался: обработчик нижней половины, но в данный момент его уже не существует, а термин сохранился, и теперь он используется для обозначения всех способов организации отложенной обработки, рассматриваемых далее.

Отложенные прерывания (softirq)

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

Отложенные прерывания представлены с помощью структур softirq_action, определенных в файле <linux/interrupt.h> в следующем виде (ядро 2.6.37):

// структура, представляющая одно отложенное прерывание
struct softirq_action {
     void (*action)( struct softirq_action* );
};

В ядре 2.6.18 (и в большинстве публикаций, затрагивающих отложенные прерывания) используется другое, более раннее определение:

struct softirq_action {
   void (*action)( struct softirq_action* );
   void *data;
};

Для уточнения картины с softirq_action заголовочных файлов, скорее всего, будет недостаточно (хотя это довольно редкий случай), поэтому потребуется окунуться в рассмотрение исходных кодов реализации ядра. Если у вас установлены исходные тексты ядра, то нужную информацию можно найти в файле <kernel/softirq.c>, в противном случае можно обратиться к следующим ресурсам: http://lxr.free-electrons.com/ или http://lxr.linux.no/.

enum {          /* задействованные номера */
   HI_SOFTIRQ=0,
   TIMER_SOFTIRQ,
   NET_TX_SOFTIRQ,
   NET_RX_SOFTIRQ,
   BLOCK_SOFTIRQ,
   BLOCK_IOPOLL_SOFTIRQ,
   TASKLET_SOFTIRQ,
   SCHED_SOFTIRQ,
   HRTIMER_SOFTIRQ,
   RCU_SOFTIRQ, /* предпочтительное RCU значение должно указываться последним */
   NR_SOFTIRQS  /* число задействованных номеров */
}; 
static struct softirq_action softirq_vec[NR_SOFTIRQS] 
char *softirq_to_name[NR_SOFTIRQS] = {
   "HI", "TIMER", "NET_TX", "NET_RX", "BLOCK", "BLOCK_IOPOLL",
   "TASKLET", "SCHED", "HRTIMER", "RCU"
};

В ядре 2.6.18 аналогичные описания (которые часто встречается в литературных источниках) были заметно проще и статичнее:

enum {
   HI_SOFTIRQ=0,
   TIMER_SOFTIRQ,
   NET_TX_SOFTIRQ,
   NET_RX_SOFTIRQ,
   BLOCK_SOFTIRQ,
   TASKLET_SOFTIRQ
};
static struct softirq_action softirq_vec[32]

В данном случае имеется возможность создать до 32-х уровней обработчиков softirq, количество которых фиксировано. В версии ядра 2.6.18 можно было определить 32 уровня разработчиков, но задействованы были только первые 6. Эти определения из предыдущей версии ядра помогают лучше понять актуальную реализацию.

Динамическая диагностика использования softirq в работающей системе может производиться так:

$ cat /proc/softirqs 
                CPU0       CPU1       CPU2       CPU3
      HI:          0          0          0          0
   TIMER:     764858     806727     615475     617660
  NET_TX:          0          0       2520          0
  NET_RX:        777        700     471255        521
   BLOCK:      48435     112893         12      24613
BLOCK_IOPOLL:      0          0          0          0
 TASKLET:        108         81          5          1
   SCHED:     405389     430031     327195     345751
 HRTIMER:        985       1356       1211       1518
     RCU:     884440     850717     837750     672217

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

Но в любом случае, независимо от версии, невозможно добавить новый уровень обработчика (назовём его XXX_SOFT_IRQ) без перекомпиляции ядра. Максимальное число используемых обработчиков (уровней) softirq не может быть изменено динамически . Отложенные прерывания с меньшим номером выполняются раньше отложенных прерываний с большим номером (приоритеты). Обработчик одного отложенного прерывания никогда не может вытеснить другой обработчик softirq. Единственное событие, которое может вытеснить выполняющийся обработчик softirq— это возникшее в это время аппаратное прерывание. Однако на другом процессоре одновременно с обработчиком отложенного прерывания может выполняться другой (и даже этот же) обработчик отложенного прерывания. Отложенное прерывание выполняется в контексте прерывания, а значит для него недопустимы блокирующие операции. Это основные характеристики softirq, необходимые для понимания и реализации отложенных прерываний и объясняющие их предназначение.

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

  1. Определить новый индекс (уровень) отложенного прерывания, вписав в файл <linux/interrupt.h> свою константу вида XXX_SOFT_IRQ в имеющийся список на одну позицию выше TASKLET_SOFTIRQ, чтобы воспользоваться возможностями тасклетов.
  2. Во время инициализации модуля зарегистрировать (объявить) обработчик отложенного прерывания с помощью вызова open_softirq(), который принимает три параметра: индекс вашего отложенного прерывания, функцию обработчика и значение поля data:
    /* "нижняя половина" обработчика прерываний */ 
    void xxx_analyze( void *data ) {
      /* проанализировать и выполнить действия */
    }
    void __init roller_init() {
       /* ... */
       request_irq( irq, xxx_interrupt, 0, "xxx", NULL );
       open_softirq( XXX_SOFT_IRQ, xxx_analyze, NULL ); 
    }
  3. Функция-обработчик отложенного прерывания (в точности как и рассматриваемого далее тасклета) должна соответствовать правильному прототипу:
    void xxx_analyze( unsigned long data );
  4. Зарегистрированное отложенное прерывание, для того, чтобы оно было поставлено в очередь на выполнение, должно быть отмечено (возбуждено) вызовом raise_softirq(). Это называется генерацией отложенного прерывания. Обычно обработчик аппаратного прерывания (ISR, верхней половины) перед возвратом возбуждает свои обработчики отложенных прерываний:
    /* "верхняя половина" обработчика прерываний */
    static irqreturn_t xxx_interrupt( int irq, void *dev_id ) {
       /* ... */
       /* отметить отложенное прерывание softirq */
       raise_softirq( XXX_SOFT_IRQ );
       return IRQ_HANDLED;
    }
  5. Затем, в подходящий для системы момент времени начнётся выполнение отложенного прерывания. Обработчик отложенного прерывания выполняется при разрешённых прерываниях процессора (особенность нижней половины). Во время выполнения обработчика отложенного прерывания новые отложенные прерывания на данном процессоре запрещаются. Однако на другом процессоре обработчики отложенных прерываний могут выполняться. На самом деле, если вдруг генерируется отложенное прерывание в тот момент, когда ещё выполняется предыдущий его обработчик, то такой же обработчик может быть запущен на другом процессоре одновременно с первым обработчиком. Это означает, что любые совместно используемые данные, которые используются в обработчике отложенного прерывания, и даже глобальные данные, которые используются только внутри самого обработчика, должны соответствующим образом ограждаться примитивами синхронизации.

Заключение

С одной стороны, представленная схема достаточно проста, и её вполне достаточно для реализации кода. С другой стороны, представленный алгоритм кажется громоздким, особенно в сравнении с альтернативными методами, которые будут рассматриваться далее. Эта сложность во многом объясняется тем, что главная причина использования отложенных прерываний — их превосходная масштабируемость на несколько процессоров. Это становится особенно актуально сегодня, когда рядовой настольный компьютер может обладать 8 ядрами, и значимость подобного масштабирования будет только расти.

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


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


Похожие темы


Комментарии

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

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