Приём пакетов

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

Приём данных из сети сложнее, чем передача, потому что sk_buff должен быть выделен и передан в верхние уровни изнутри атомарного контекста. Существуют два режима приёма пакетов, которые могут быть реализованы сетевыми драйверами: управляемый прерыванием и опрос. Большинство драйверов реализуют технику управления прерыванием и именно её мы рассмотрим в первую очередь. Некоторые драйверы для адаптеров с высокой пропускной способностью могут также применять технику опроса; мы рассмотрим этот подход в разделе "Уменьшение числа прерываний".

 

Реализация в snull отделяет "аппаратные" детали от аппаратно-независимых служебных действий. Таким образом, функция snull_rx вызывается из обработчика "прерываний" snull после того, как оборудование получило пакет и он уже находится в памяти компьютера. snull_rx получает указатель на данные и длину пакета; его единственной обязанностью является отправить пакет и некоторую дополнительную информацию в вышележащие уровни сетевого кода. Этот код не зависит от способа получения указателя данных и длины.

 

void snull_rx(struct net_device *dev, struct snull_packet *pkt)

{

    struct sk_buff *skb;

    struct snull_priv *priv = netdev_priv(dev);

 

    /*

     * Пакет вернулся из среды передачи.

     * Построим вокруг него skb, чтобы верхние уровни могли его обработать

     */

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

    if (!skb) {

        if (printk_ratelimit( ))

            printk(KERN_NOTICE "snull rx: low on mem - packet dropped\n");

        priv->stats.rx_dropped++;

        goto out;

    }

    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; /* не проверяем это */

    priv->stats.rx_packets++;

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

    netif_rx(skb);

out:

    return;

}

 

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

 

Первым шагом является выделение буфера для хранения пакета. Обратите внимание, что функции выделение буфера (dev_alloc_skb) необходимо знать длину данных. Информация используется функцией для выделения памяти для буфера. dev_alloc_skb вызывает kmalloc с атомарным приоритетом, поэтому она может безопасно использоваться во время прерывания. Ядро предлагает и другие интерфейсы для выделения буфера сокета, но они не стоят представления здесь; буферы сокетов подробно описываются в разделе "Буферы сокетов".

 

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

 

Если есть действительный указатель skb, пакетные данные копируются в буфер с помощью вызова memcpy; функция skb_put  обновляет указатель на конец данных в буфере и возвращает указатель на вновь созданное пространство.

 

Если вы пишете высокопроизводительный драйвер для интерфейса, который может выполнять полное управление шиной ввода/вывода, существует возможность оптимизации, которую стоит рассмотрения здесь. Некоторые драйверы выделяют буферы сокетов для входящих пакетов до их приёма, а затем поручают интерфейсу разместить пакетные данные прямо в пространство буфера сокета. Сетевой слой сотрудничает с этой стратегией, выделяя все буферы сокетов в DMA-совместимом пространстве (которое может быть в верхней памяти, если ваше устройство имеет установленным флаг функции NETIF_F_HIGHDMA). Делая вещи таким образом, отпадает необходимость в отдельной операции копирования для заполнения буфера сокета, но обязывает быть осторожными с размерами буферов, потому что вы не будете знать заранее, как велик входящий пакет. В этой ситуации также важна реализация метода change_mtu, поскольку она позволяет драйверу реагировать на изменение максимального размера пакета.

 

Сетевой уровень должен иметь разъяснение некоторой информации, прежде чем он сможет понять смысл пакета. С этой целью до того, как буфер будет передан наверх должны быть присвоены значения полям dev и protocol. Код поддержки Ethernet экспортирует вспомогательную функцию (eth_type_trans), которая находит соответствующего значения для помещения в protocol. Затем необходимо указать, как должен быть выполнен подсчёт контрольной суммы или как она была посчитана для пакета (snull не требуется выполнять подсчёт каких-либо контрольных сумм). Возможными политиками для skb->ip_summed являются:

 

CHECKSUM_HW

Устройство уже выполнило подсчёт контрольных сумм на аппаратном уровне. Примером аппаратного подсчёта контрольной суммы является интерфейс SPARC jsE.

 

CHECKSUM_NONE

Контрольные суммы ещё не были проверены и задача должна быть выполнена системным программным обеспечением. Это значение по умолчанию во вновь выделенных буферах.

 

CHECKSUM_UNNECESSARY

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

 

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

 

Наконец, драйвер обновляет свой счётчик статистики, чтобы запротоколировать, что пакет был получен. Структура статистики состоит из нескольких полей; наиболее важными из них являются rx_packets, rx_bytes, tx_packets и tx_bytes, которые содержат количество пакетов, полученных и переданных, и общее количество переданных октетов. Все эти поля подробно описаны в разделе "Статистическая информация".

 

Последний шаг в приёме пакета выполняется netif_rx, которая вручает буфер сокета верхним уровням. netif_rx фактически возвращает целое число; NET_RX_SUCCESS(0) означает, что пакет был получен успешно; любое другое значение указывает на  проблемы. Существуют три возвращаемых значения (NET_RX_CN_LOW, NET_RX_CN_MOD и NET_RX_CN_HIGH), которые  свидетельствуют об увеличении уровня перегрузки в сетевой подсистеме; NET_RX_DROP означает, что пакет был отброшен. Драйвер  мог бы использовать эти значения для остановки снабжения пакетами ядра, когда перегруженность становится высокой, однако, на практике большинство драйверов игнорируют возвращаемое netif_rx значение. Если вы пишете драйвер для устройства с высокой пропускной способностью и хотите выполнять правильные действия в ответ на перегруженность, лучшим подходом является реализация NAPI, с которым мы познакомимся после быстрого обсуждения обработчиков прерываний.

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