Уменьшение числа прерываний

Предыдущая  Содержание  Следующая V*D*V

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

 

Качестве одного из способов повышения эффективности Linux на высокопроизводительных системах, разработчики сетевой подсистемы создали альтернативный интерфейс (названный NAPI) (* NAPI означает "новый API"; исследователи сетей хороши при создании интерфейсов, а не их именовании.) на основе опроса. "Опрос" может быть грязным словом среди разработчиков драйверов, которые часто рассматривают техники опроса, как неэлегантные и неэффективные. Однако, опрос является неэффективным, только если интерфейс опрашивается, когда нет работы. Когда система имеет высокоскоростной интерфейс, обрабатывающий большой трафик, для обработки всегда есть много пакетов. В таких ситуациях нет необходимости прерывать процессор; достаточно часто забирать пакеты из интерфейса.

 

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

 

Однако, не все устройства могут работать в режиме NAPI. NAPI-совместимый интерфейс должен быть способен сохранять несколько пакетов (либо на самой карте, либо в памяти кольца DMA). Интерфейс должен быть способен отключить прерывания для принятых пакетов, продолжая генерировать прерывания для успешной передачи и других событий. Есть и другие тонкие вопросы, которые могут сделать написание NAPI-совместимого драйвера труднее; смотрите для подробностей Documentation/networking/NAPI_HOWTO.txt в дереве исходного кода ядра.

 

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

 

Драйвер snull, когда он загружен с параметром use_napi, установленным в ненулевое значение, работает в режиме NAPI. Во время инициализации мы должны установить несколько дополнительных полей struct net_device:

 

if (use_napi) {

    dev->poll   = snull_poll;

    dev->weight = 2;

}

 

Поле poll должно быть указывать на функцию опроса вашего драйвера; в ближайшее время мы посмотри на snull_poll. Поле weight описывает относительную важность этого интерфейса: насколько большой трафик должен быть принят с помощью интерфейса, когда ресурсов недостаточно. Нет строгих правил, как должен быть установлен параметр weight; по соглашению, 10 MBps Ethernet интерфейсы устанавливают weight в 16, в то время как более быстрые интерфейсы используют 64. Вы не должны устанавливать weight в значение большее, чем количество пакетов, которое может хранить ваш интерфейс. В snull мы установили weight в двойку в качестве способа продемонстрировать замедленный приём пакета.

 

Следующим шагом в создании NAPI-совместимого драйвера является изменение обработчика прерывания. Когда ваш интерфейс (который должен стартовать с разрешёнными прерываниями при приёме) сигнализирует о том, что пакет прибыл, обработчик прерывания не должен обрабатывать пакет. Наоборот, он должен отключить дальнейшие прерывания при получении и сообщить ядру, что пришло время начать опрашивать этот интерфейс. В обработчике "прерывания" snull код, который отвечает на прерывания приёма пакетов, был изменён на следующий:

 

if (statusword & SNULL_RX_INTR) {

    snull_rx_ints(dev, 0); /* Запрещаем дальнейшие прерывания */

    netif_rx_schedule(dev);

}

 

Когда интерфейс сообщает нам, что пакет доступен, обработчик прерываний оставляет его в интерфейсе; всё, что должно произойти на данном этапе - это вызов netif_rx_schedule, который распоряжается, чтобы наш метод poll был вызван в какой-то момент в будущем.

 

Метод poll имеет следующий прототип:

 

int (*poll)(struct net_device *dev, int *budget);

 

Реализация в snull метода poll выглядит следующим образом:

 

static int snull_poll(struct net_device *dev, int *budget)

{

    int npackets = 0, quota = min(dev->quota, *budget);

    struct sk_buff *skb;

    struct snull_priv *priv = netdev_priv(dev);

    struct snull_packet *pkt;

 

    while (npackets < quota && priv->rx_queue) {

        pkt = snull_dequeue_buf(dev);

        skb = dev_alloc_skb(pkt->datalen + 2);

        if (! skb) {

            if (printk_ratelimit( ))

                 printk(KERN_NOTICE "snull: packet dropped\n");

            priv->stats.rx_dropped++;

            snull_release_buffer(pkt);

            continue;

        }

        memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);

        skb->dev = dev;

        skb->protocol = eth_type_trans(skb, dev);

        skb->ip_summed = CHECKSUM_UNNECESSARY; /* не проверяем его */

        netif_receive_skb(skb);

 

        /* Ведение статистики */

        npackets++;

        priv->stats.rx_packets++;

        priv->stats.rx_bytes += pkt->datalen;

        snull_release_buffer(pkt);

    }

    /* Если мы обработали все пакеты, мы завершили; сообщаем ядру и снова разрешаем прерывания */

    *budget -= npackets;

    dev->quota -= npackets;

    if (! priv->rx_queue) {

        netif_rx_complete(dev);

        snull_rx_ints(dev, 1);

        return 0;

    }

    /* Мы не смогли обработать всё. */

    return 1;

}

 

Центральная часть функция связана с созданием skb, удерживающего пакет; этот код такой же, что мы видели ранее в snull_rx.  Ряд вещей, однако, отличается:

 

Параметр budget определяет максимальное количество пакетов, которое нам разрешено передавать в ядро. Внутри структуры устройства поле quota даёт другой максимум; метод poll должен соблюдать меньший из двух пределов. Следует также уменьшить и dev->quota и *budget на количество фактически полученных пакетов. Значение budget является максимальным количеством пакетов, которые текущий процессор может получать от всех интерфейсов, а quota является значением для каждого интерфейса, которое обычно начинается как weight, назначенный интерфейсу во время инициализации.

Пакеты должны передаваться в ядро с помощью netif_receive_skb, а не netif_rx.

Если метод poll в состоянии обработать все имеющиеся пакеты в пределах данных ему ограничений, он должна снова включить прерывания при приёме пакетов, вызвав netif_rx_complete для выключения опроса, и вернуть 0. Возвращаемое значение 1 указывает, что есть пакеты, которые еще предстоит обработать.

 

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

Предыдущая  Содержание  Следующая