Linux Device Drivers 3 / Драйверы устройств Линукс (третье издание): 17.5. Передача пакетов

Оглавление
Английский оригинал Перевод на русский

Сожалеем, но текст оригинала доступен только зарегистрированным пользователям.

17.5. Передача пакетов

hardorsoft 26.10.10 в 02:39

0

Прием и передача данных являются наиболее важной задачей,
сетевого интерфейса. Мы начнем с передачи, т.к. она немного проще для
понимания.

hardorsoft 26.10.10 в 02:39

0

Передача – действие направленное на пересылку пакета по сети.
Каждый раз когда ядру необходимо переслать пакет данных, оно
вызывает метод драйвера hard_start_transmit, что помещает данные в
выходную очередь. Каждый пакет, обработанный ядром, помещается в
структуру буфера сокета (sk_buff), которая определена в
<linux></linux>skbuff.h>. Название структуры происходит из абстракции сетевых
соединений Unix – сокетов. Хотя интерфейс не имеет ничего общего с
сокетами, каждый пакет являлся частью сокетов на более высоких
уровнях сети, и буфер ввода/вывода является списком структур sk_buff.
Структура sk_buff используется для хранения сетевых данных во всех
сетевых подсистемах Linux, но буфер сокетов находится далеко от
интерфейса.

hardorsoft 26.10.10 в 02:39

0

Указатель на sk_buff обычно называется skb. Мы применим это практике в коде и тексте.

hardorsoft 26.10.10 в 02:40

0

Буфер сокета – сложная структура, и ядро предлагает функции для
работы с ним. Функции будут описаны позже в подразделе «Буферы
сокетов». А теперь приведем немного основных понятий о sk_buff,
достаточных для того чтобы мы написали работающий драйвер.

hardorsoft 26.10.10 в 02:40

0

Буфер сокета переданный в hard_start_xmit, содержит физический
пакет, для передающей среды, укомплектованный заголовками
передающего уровня. Интерфейс не должен изменять данные при
передачи. skb->data – указатель на передающийся пакет, skb->len –
длина в октетах. Ситуация немного усложняется если ваш драйвер
обрабатывает сегментированный ввод/вывод (scatter/gather I/O). Его мы
рассмотрим в подразделе «Сегментированный ввод/вывод».

hardorsoft 26.10.10 в 02:40

0

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

hardorsoft 26.10.10 в 02:41

0

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 &lt; 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; /*наше устройство не может не сработать*/
}

hardorsoft 26.10.10 в 02:41

0

Функция передачи только выполняет некоторые проверки пакета и
передает данные, через связанную с аппаратурой функцию. Однако,
нужно проявить осторожность, т.к. длинна пакета может оказаться
меньше, чем поддерживает аппаратная среда (какой для snull является
наш виртуальный Ethernet). При этом во многих сетевых драйверах Linux
(и в других операционных системах) обнаруживается утечка данных. Чем
создавать защиту, лучше скопировать короткие пакеты в отдельный
массив, который можно дополнить нулями до требуемого размера. (Мы
можем хранить отправляемые данные в стеке, начиная с минимальной
длинны – 60 байт, что немного).

hardorsoft 26.10.10 в 02:41

0

В случае успешной работы, hard_start_xmit должен возвращать 0.
Это делает ваш драйвер ответственным за пересылку пакета, сделайте это
наилучшим образом для гарантирования успешной пересылки, в конце можете освободить skb. Возврат не нулевого значения показывает, что
пакет не передался, позже ядро повторит попытку. В этом случае ваш
драйвер должен остановить очередь, пока проблема не будет устранена.

hardorsoft 26.10.10 в 02:42

0

Аппаратно-зависимая функция передачи (snull_hw_tx) не приведена
здесь. Реализуется механизм устройства snull, включая манипуляцию с
адресом назначения и адресом отправителя. Это, конечно, пример для
тех, кому интересно увидеть, как все это работает.

hardorsoft 26.10.10 в 02:42

0

17.5.1. Контроль одновременной передачи.

hardorsoft 26.10.10 в 02:42

0

Функция hard_start_xmit защищена от одновременных вызовов
spinlock-ом (xmit_lock) в структуре net_device. Как только функция
закончит работу, она может быть вызвана вновь. Функцию заканчивает
работу, когда даст все указания аппаратуре для передачи пакета, но
аппаратная передача, возможно, не будет завершена. Это не проблема в
snull, который делает всю работу используя CPU, потому что передача
пакета – заканчивается до того как функцию завершает работу.

hardorsoft 26.10.10 в 02:42

0

Реальный аппаратный интерфейс работает по-другому. Он передает
пакеты асинхронно, и имеет ограниченное количество памяти для
хранения отправляемых пакетов. Когда память (которая может быть
рассчитана на 1 пакет) – израсходована, драйвер сообщает сетевой
системе, что не следует производить других отправок, до тех пор, пока
аппаратура не будет готова принять новые данные.

hardorsoft 26.10.10 в 02:43

0

Это можно сообщить вызовом netif_stop_queue, функция
останавливает очередь. Каждая остановка очереди должна
сопровождаться последующим возобновлением передачи. Делается это с
помощью функции:

hardorsoft 26.10.10 в 02:43

0

void netif_wake_queue(struct net_device *dev);

hardorsoft 26.10.10 в 02:43

0

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

hardorsoft 26.10.10 в 02:44

0

Если вам необходимо запретить передачу пакетов отовсюду, где
используется ваша функция hard_start_xmit (возможно, в ответ на
запрос реконфигурации), тогда используйте функцию:

hardorsoft 26.10.10 в 02:44

0

void netif_tx_disable(struct net_device *dev);

hardorsoft 26.10.10 в 02:43

0

Эта функция работает аналогично netif_stop_queue, но еще
гарантирует то, что когда она вернет управление, ваш метод
hard_start_xmit не будет запущен на другом CPU. Передача
возобновляется как обычно, вызовом netif_wake_queue.

hardorsoft 26.10.10 в 02:44

0

17.5.2. Таймаут передачи.

hardorsoft 26.10.10 в 02:45

0

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

hardorsoft 26.10.10 в 02:45

0

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

hardorsoft 26.10.10 в 02:45

0

Таким образом, сетевые устройства не должны беспокоиться по
поводу обнаружения этих проблем у себя. Они должны только установить
время таймаута, которое записывается в поле watchdog_timeo структуры
net_device. Это время, задаваемое в jiffies, должно быть достаточно
длинным по сравнению с нормальной задержкой при передачи (как при
коллизии, вызванной перегрузкой сетевой среды).

hardorsoft 26.10.10 в 02:46

0

Если текущее системное время превысило trans_start на время
таймаута, сетевой уровень вызывает метод драйвера tx_timeout. Работа
этого метода заключается в том, чтобы ликвидировать проблему и
гарантировать правильное завершение любой выполняемой передачи. Это
особенно важно, когда драйвер не освобождает буфер сокета, а поручает
это действие сетевому коду.

hardorsoft 26.10.10 в 02:46

0

Snull может симулировать блокирование передатчика, для этого
используются два загружаемых параметра:

hardorsoft 26.10.10 в 02:46

0

static int lockup = 0;
module_param(lockup, int, 0);
static int timeout = SNULL_TIMEOUT;
module_param(timeout, int, 0);

hardorsoft 26.10.10 в 02:43

0

Если драйвер загружен с параметром lockup=n, блокирование
симулируется 1 раз через каждые n переданных пакетов, и поле
watchdog_timeo устанавливается равным timeout. Когда происходит
симуляция блокировки, snull вызывает netif_stop_queue для
предотвращения других попыток пересылки.

hardorsoft 26.10.10 в 02:46

0

Обработчик таймаута snull выглядит так:

hardorsoft 26.10.10 в 02:46

0

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;
}

hardorsoft 26.10.10 в 02:46

0

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

hardorsoft 26.10.10 в 02:47

0

17.5.3. Сегментированный ввод-вывод (Scatter/Gather I/O)

hardorsoft 26.10.10 в 02:47

0

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

hardorsoft 26.10.10 в 02:47

0

Ядро не передает сегментированные пакеты в метод
hard_start_xmit, если бит NETIF_F_SG не установлен. Если вы установили
этот флаг, вам необходимо проверять специальное поле «совместно
используемых данных» в skb, чтобы узнать состоит ли пакет из одного
фрагмента или из нескольких, и найти фрагменты при необходимости.
Для доступа к этой информации существует специальный макрос –
skb_shinfo. Первый шаг, при передачи потенциально
фрагментированного пакета, обычно выглядит так:

hardorsoft 26.10.10 в 02:48

0

if (skb_shinfo(skb)->nr_frags == 0) {
/* используйте skb->data и skb->len как обычно*/
}

hardorsoft 26.10.10 в 02:48

0

Поле nr_frags информирует о количестве используемых
фрагментов в пакете. Если оно равно 0, пакет состоим из единственного
фрагмента, и доступ к нему осуществляется через поле data, как обычно.
Если же оно не равно 0, ваш драйвер должен передать каждый фрагмент
индивидуально. Поле data структуры skb, указывает на первый фрагмент.
Длина фрагмента вычисляется вычитанием skb->data_len из skb->len
(которое содержит длину всего пакета). Оставшиеся фрагменты могут
быть найдены в массиве frags, который находится в совместно
используемой информационной структуре. Каждый элемент frags
является структурой:

hardorsoft 26.10.10 в 02:48

0

struct skb_frag_struct {
struct page *page;
_ _u16 page_offset;
_ _u16 size;
};

hardorsoft 26.10.10 в 02:44

0

Как видите, мы снова столкнулись со структурой page, что лучше
чем виртуальные адреса ядра. Ваш драйвер проходит в цикле по
фрагментам, передает каждый по ПДП, и не забываете про первый
сегмент, на который имеется ссылка из skb. Ваша аппаратура, конечно,
должна собирать фрагменты и передавать их как один пакет. Обратите
внимание, если установлен флаг NETIF_F_HIGHDMA, некоторые или все
фрагменты могут находиться в верхней памяти.

hardorsoft 26.10.10 в 02:48

0
Минутку...