Готовый перевод Linux Device Drivers 3 / Драйверы устройств Линукс (третье издание): 17.5. Передача пакетов
17.5. Передача пакетов
Прием и передача данных являются наиболее важной задачей,
сетевого интерфейса. Мы начнем с передачи, т.к. она немного проще для
понимания.
Передача – действие направленное на пересылку пакета по сети.
Каждый раз когда ядру необходимо переслать пакет данных, оно
вызывает метод драйвера hard_start_transmit, что помещает данные в
выходную очередь. Каждый пакет, обработанный ядром, помещается в
структуру буфера сокета (sk_buff), которая определена в
<linux></linux>skbuff.h>. Название структуры происходит из абстракции сетевых
соединений Unix – сокетов. Хотя интерфейс не имеет ничего общего с
сокетами, каждый пакет являлся частью сокетов на более высоких
уровнях сети, и буфер ввода/вывода является списком структур sk_buff.
Структура sk_buff используется для хранения сетевых данных во всех
сетевых подсистемах Linux, но буфер сокетов находится далеко от
интерфейса.
Указатель на sk_buff обычно называется skb. Мы применим это практике в коде и тексте.
Буфер сокета – сложная структура, и ядро предлагает функции для
работы с ним. Функции будут описаны позже в подразделе «Буферы
сокетов». А теперь приведем немного основных понятий о sk_buff,
достаточных для того чтобы мы написали работающий драйвер.
Буфер сокета переданный в hard_start_xmit, содержит физический
пакет, для передающей среды, укомплектованный заголовками
передающего уровня. Интерфейс не должен изменять данные при
передачи. skb->data – указатель на передающийся пакет, skb->len –
длина в октетах. Ситуация немного усложняется если ваш драйвер
обрабатывает сегментированный ввод/вывод (scatter/gather I/O). Его мы
рассмотрим в подразделе «Сегментированный ввод/вывод».
Ниже находится код snull, передающий пакеты. Механизм
физической передачи размещен в другой функции, потому что каждый
драйвер интерфейса должен реализовать его в соответствии со
спецификацией аппаратуры.
int snull_tx(struct sk_buff *skb, struct net_device *dev)
{
int len;
char *data, shortpkt[ETH_ZLEN];
struct snull_priv *priv = netdev_priv(dev);
data = skb->data;
len = skb->len;
if (len < ETH_ZLEN) {
memset(shortpkt, 0, ETH_ZLEN);
memcpy(shortpkt, skb->data, skb->len);
len = ETH_ZLEN;
data = shortpkt;
}
dev->trans_start = jiffies; /*засекаем время*/
/* запоминаем skb, который мы сможем освободить в прерывании*/
priv->skb = skb;
/* доставка данных, зависит от специфики конкретного устройства и не
приведена здесь. */
snull_hw_tx(data, len, dev);
return 0; /*наше устройство не может не сработать*/
}
Функция передачи только выполняет некоторые проверки пакета и
передает данные, через связанную с аппаратурой функцию. Однако,
нужно проявить осторожность, т.к. длинна пакета может оказаться
меньше, чем поддерживает аппаратная среда (какой для snull является
наш виртуальный Ethernet). При этом во многих сетевых драйверах Linux
(и в других операционных системах) обнаруживается утечка данных. Чем
создавать защиту, лучше скопировать короткие пакеты в отдельный
массив, который можно дополнить нулями до требуемого размера. (Мы
можем хранить отправляемые данные в стеке, начиная с минимальной
длинны – 60 байт, что немного).
В случае успешной работы, hard_start_xmit должен возвращать 0.
Это делает ваш драйвер ответственным за пересылку пакета, сделайте это
наилучшим образом для гарантирования успешной пересылки, в конце можете освободить skb. Возврат не нулевого значения показывает, что
пакет не передался, позже ядро повторит попытку. В этом случае ваш
драйвер должен остановить очередь, пока проблема не будет устранена.
Аппаратно-зависимая функция передачи (snull_hw_tx) не приведена
здесь. Реализуется механизм устройства snull, включая манипуляцию с
адресом назначения и адресом отправителя. Это, конечно, пример для
тех, кому интересно увидеть, как все это работает.
17.5.1. Контроль одновременной передачи.
Функция hard_start_xmit защищена от одновременных вызовов
spinlock-ом (xmit_lock) в структуре net_device. Как только функция
закончит работу, она может быть вызвана вновь. Функцию заканчивает
работу, когда даст все указания аппаратуре для передачи пакета, но
аппаратная передача, возможно, не будет завершена. Это не проблема в
snull, который делает всю работу используя CPU, потому что передача
пакета – заканчивается до того как функцию завершает работу.
Реальный аппаратный интерфейс работает по-другому. Он передает
пакеты асинхронно, и имеет ограниченное количество памяти для
хранения отправляемых пакетов. Когда память (которая может быть
рассчитана на 1 пакет) – израсходована, драйвер сообщает сетевой
системе, что не следует производить других отправок, до тех пор, пока
аппаратура не будет готова принять новые данные.
Это можно сообщить вызовом netif_stop_queue, функция
останавливает очередь. Каждая остановка очереди должна
сопровождаться последующим возобновлением передачи. Делается это с
помощью функции:
void netif_wake_queue(struct net_device *dev);
Эта функция похожа на netif_start_queue, за исключением того,
что она снова заставляет сетевую систему начать передачу пакетов.
Современные устройства имеют внутреннюю очередь, рассчитанную
на несколько передаваемых пакетов, это улучшает производительность
сети. Сетевой драйвер для этих устройств должен поддерживать
множественное ожидание передачи в любое время, вне зависимости от
того заполнилась ли полностью или нет память устройства. Всякий раз,
когда память устройства заполняется, и нет места для новых пакетов,
драйвер должен остановить очередь до тех пор, пока место не появиться.
Если вам необходимо запретить передачу пакетов отовсюду, где
используется ваша функция hard_start_xmit (возможно, в ответ на
запрос реконфигурации), тогда используйте функцию:
void netif_tx_disable(struct net_device *dev);
Эта функция работает аналогично netif_stop_queue, но еще
гарантирует то, что когда она вернет управление, ваш метод
hard_start_xmit не будет запущен на другом CPU. Передача
возобновляется как обычно, вызовом netif_wake_queue.
17.5.2. Таймаут передачи.
Большинство драйверов делают это для аппаратуры, которая не
всегда информирует об ошибках. Интерфейс может «забыть» выполнить свою работу, или система может пропустить прерывание. Эта категория
проблем встречается с некоторыми устройствами, работающими в составе
персонального компьютера.
Многие устройства решают эту проблему установкой таймера. Если
операция не завершилась за некоторое время, значит, возникла ошибка.
Сетевая система, как оказывается, по существу сложная система из
массы таймеров, управляемых машиной. Поэтому детектирование
таймаутов передачи, хорошо вписывается в сетевой код, как часть его
нормальной работы.
Таким образом, сетевые устройства не должны беспокоиться по
поводу обнаружения этих проблем у себя. Они должны только установить
время таймаута, которое записывается в поле watchdog_timeo структуры
net_device. Это время, задаваемое в jiffies, должно быть достаточно
длинным по сравнению с нормальной задержкой при передачи (как при
коллизии, вызванной перегрузкой сетевой среды).
Если текущее системное время превысило trans_start на время
таймаута, сетевой уровень вызывает метод драйвера tx_timeout. Работа
этого метода заключается в том, чтобы ликвидировать проблему и
гарантировать правильное завершение любой выполняемой передачи. Это
особенно важно, когда драйвер не освобождает буфер сокета, а поручает
это действие сетевому коду.
Snull может симулировать блокирование передатчика, для этого
используются два загружаемых параметра:
static int lockup = 0;
module_param(lockup, int, 0);
static int timeout = SNULL_TIMEOUT;
module_param(timeout, int, 0);
Если драйвер загружен с параметром lockup=n, блокирование
симулируется 1 раз через каждые n переданных пакетов, и поле
watchdog_timeo устанавливается равным timeout. Когда происходит
симуляция блокировки, snull вызывает netif_stop_queue для
предотвращения других попыток пересылки.
Обработчик таймаута snull выглядит так:
void snull_tx_timeout (struct net_device *dev)
{
struct snull_priv *priv = netdev_priv(dev);
PDEBUG("Transmit timeout at %ld, latency %ld\n", jiffies,
jiffies - dev->trans_start);
/* симулируем прерывание окончания передачи */
priv->status = SNULL_TX_INTR;
snull_interrupt(0, dev, NULL);
priv->stats.tx_errors++;
netif_wake_queue(dev);
return;
}
Когда произошел таймаут передачи, драйвер должен занести факт
ошибки в статистику интерфейса, и привести устройство в нормальное
состояние, чтобы новый пакет мог передаться. Когда происходит таймаут
в snull, драйвер вызывает snull_interrupt, для выполнения отсутствующих прерываний, и запускает очередь netif_wake_queue.
17.5.3. Сегментированный ввод-вывод (Scatter/Gather I/O)
Процесс создания пакета для передачи по сети состоит из
компоновки его из многих частей. Пакет данных много раз копируется из
пользовательского пространства, добавляются заголовки различных
уровней сетевого стека. Все это приводит к большому числу копирований.
Если сетевой интерфейс хочет, в конце концов, передать пакет, следует
использовать сегментированный ввод-вывод, который позволяет не
собирать пакет в целый кусок, и обойтись без множественного
копирования. Сегментированный ввод-вывод также позволяет избежать
копирования буферов из пользовательского пространства.
Ядро не передает сегментированные пакеты в метод
hard_start_xmit, если бит NETIF_F_SG не установлен. Если вы установили
этот флаг, вам необходимо проверять специальное поле «совместно
используемых данных» в skb, чтобы узнать состоит ли пакет из одного
фрагмента или из нескольких, и найти фрагменты при необходимости.
Для доступа к этой информации существует специальный макрос –
skb_shinfo. Первый шаг, при передачи потенциально
фрагментированного пакета, обычно выглядит так:
if (skb_shinfo(skb)->nr_frags == 0) {
/* используйте skb->data и skb->len как обычно*/
}
Поле nr_frags информирует о количестве используемых
фрагментов в пакете. Если оно равно 0, пакет состоим из единственного
фрагмента, и доступ к нему осуществляется через поле data, как обычно.
Если же оно не равно 0, ваш драйвер должен передать каждый фрагмент
индивидуально. Поле data структуры skb, указывает на первый фрагмент.
Длина фрагмента вычисляется вычитанием skb->data_len из skb->len
(которое содержит длину всего пакета). Оставшиеся фрагменты могут
быть найдены в массиве frags, который находится в совместно
используемой информационной структуре. Каждый элемент frags
является структурой:
struct skb_frag_struct {
struct page *page;
_ _u16 page_offset;
_ _u16 size;
};
Как видите, мы снова столкнулись со структурой page, что лучше
чем виртуальные адреса ядра. Ваш драйвер проходит в цикле по
фрагментам, передает каждый по ПДП, и не забываете про первый
сегмент, на который имеется ссылка из skb. Ваша аппаратура, конечно,
должна собирать фрагменты и передавать их как один пакет. Обратите
внимание, если установлен флаг NETIF_F_HIGHDMA, некоторые или все
фрагменты могут находиться в верхней памяти.
Внимание! Этот перевод, возможно, ещё не готов.
Его статус: перевод редактируется
Переведено на Нотабеноиде
http://notabenoid.org/book/11832/38385
Переводчики: hardorsoft
Настройки
Готово:
