Буферы сокетов

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

На данный момент мы охватили большинство вопросов, связанных с сетевыми интерфейсами. Но всё ещё отсутствует более подробное обсуждение структуры sk_buff. Эта структура находится в центре сетевой подсистемы ядра Linux и теперь мы представим как основные поля этой структуры, так и функции, используемые для работы с ними.

 

Хотя не существует строгой необходимости понимать внутренности sk_buff, умение посмотреть на её содержимое может быть полезным, когда вы ищете проблемы и когда вы пытаетесь оптимизировать свой код. Например, если вы посмотрите в loopback.c, вы  найдёте оптимизацию на основе знаний внутренностей sk_buff. Здесь применимо обычное предупреждение: если вы пишете код, который использует знание структуры sk_buff, вы должны быть готовы увидеть его поломку в будущих версиях ядра. Всё же, иногда преимущества производительности оправдывают дополнительные затраты на техническую поддержку.

 

Мы не будем описывать здесь всю структуру, только те поля, которые могут быть использованы в пределах драйвера. Если вы хотите увидеть больше, вы можете посмотреть на <linux/skbuff.h>, где определяется эта структура и прототипы функций. Дополнительная информация о том, как использовать эти поля и функции может быть легко получена поиском в исходных текстах ядра.

Важные поля

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

 

struct net_device *dev;

Устройство принимает или отправляет этот буфер.

 

union { /* ... */ } h;

union { /* ... */ } nh;

union { /* ... */ } mac;

Указатели на заголовки различных уровней, содержащиеся в пакете. Каждое поле объединения является указателем на другой тип структуры данных. h содержит указатели на заголовки транспортного уровня (например, struct tcphdr *th); nh включает в себя заголовки сетевого уровня (такие, как struct iphdr *iph) и mac собирает указатели заголовков канального уровня (такие, как struct ethdr *ethernet).

Если вашему драйверу необходимо посмотреть на адреса источника и назначения TCP пакета, он может найти их в skb->h.th. Смотрите файл заголовка для полного набора заголовочных типов, которые могут быть доступны таким образом.

Отметим, что сетевые драйверы несут ответственность за установку указателя mac для входящих пакетов. Этой задачей обычно занимается eth_type_trans, но не-Ethernet драйверы вынуждены устанавливать skb->mac.raw напрямую, как показано в разделе "Не-Ethernet заголовки".

 

unsigned char *head;

unsigned char *data;

unsigned char *tail;

unsigned char *end;

Указатели, используемые для адресации данных в пакете. head указывает на начало выделенного пространства, data является началом достоверных октетов (и обычно немного больше, чем head), tail является окончанием достоверных октетов и end указывает на максимальный адрес, который может достичь tail. Ещё один способ посмотреть на это заключается в том, что доступным пространством буфера является skb->end - skb->head, а в настоящее время используемым пространством данных является skb->tail - skb->data.

 

unsigned int len;

unsigned int data_len;

len является полной длиной данных в пакете, в то время как data_len является длиной части пакета, хранящейся в отдельных фрагментах. Поле data_len равно 0, если используется ввод/вывод с разборкой/сборкой.

 

unsigned char ip_summed;

Политика подсчёта контрольной суммы для этого пакета. Поле устанавливается драйвером для входящих пакетов, как описано в разделе "Приём пакетов".

 

unsigned char pkt_type;

Классификация пакета, используемая при его доставке. Драйвер несёт ответственность за установку его в PACKET_HOST (этот пакет для меня), PACKET_OTHERHOST (нет, этот пакет не для меня), PACKET_BROADCAST или PACKET_MULTICAST. Ethernet драйверы не изменяют явно pkt_type, поскольку это делает за них eth_type_trans.

 

shinfo(struct sk_buff *skb);

unsigned int shinfo(skb)->nr_frags;

skb_frag_t shinfo(skb)->frags;

По причинам производительности, некоторые информация skb хранится в отдельной структуре, которая появляется в памяти сразу после skb. К этой "общей информации" (называется так потому, что может использоваться совместно копиями skb в рамках сетевого кода) необходимо обращаться через макрос shinfo. В этой структуре находятся несколько полей, но большинство из них выходят за рамки этой книги. В разделе "Ввод/вывод с разборкой/сборкой" мы видели nr_frags и frags.

 

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

Функции, работающие с буферами сокетов

Сетевые устройства, которые используют структуру sk_buff, работают с ней с помощью официальных интерфейсных функций. С буферами сокетов работает много функций; здесь самые интересные из них:

 

struct sk_buff *alloc_skb(unsigned int len, int priority);

struct sk_buff *dev_alloc_skb(unsigned int len);

Выделение буфера. Функция alloc_skb выделяет буфер и инициализирует skb->data и skb->tail в skb->head. Функция dev_alloc_skb является ярлыком, который вызывает alloc_skb с приоритетом GFP_ATOMIC и резервирует некоторое пространство между skb->head и skb->data. Эти пространство данных используется для оптимизаций внутри сетевого уровня и не должно быть затронуто драйвером.

 

void kfree_skb(struct sk_buff *skb);

void dev_kfree_skb(struct sk_buff *skb);

void dev_kfree_skb_irq(struct sk_buff *skb);

void dev_kfree_skb_any(struct sk_buff *skb);

Освобождение буфера. Вызов kfree_skb используется внутри ядра. Драйвер должен вместо этого использовать одну из форм dev_kfree_skb: dev_kfree_skb для контекста без прерывания, dev_kfree_skb_irq для контекста прерывания или dev_kfree_skb_any для кода, который может работать в любом контексте.

 

unsigned char *skb_put(struct sk_buff *skb, int len);

unsigned char *__skb_put(struct sk_buff *skb, int len);

Обновление полей tail и len структуры sk_buff; они используются для добавления данных в конец буфера. Возвращаемым значением каждой функции является предыдущее значение skb->tail (иными словами, оно указывает на только что созданное пространство данных). Драйверы могут использовать возвращаемое значение, чтобы скопировать данные, вызывая memcpy skb_put(...), data, len) или другой эквивалент. Разница между этими двумя функциями в том, что skb_put выполняет проверку, чтобы убедиться, что данные помещаются в буфер, а __skb_put проверку опускает.

 

unsigned char *skb_push(struct sk_buff *skb, int len);

unsigned char *__skb_push(struct sk_buff *skb, int len);

Функции для уменьшения skb->data и увеличения skb->len. Они похожи на skb_put, за исключением того, что данные добавляются в начало пакета, а не в конец. Возвращаемое значение указывает на только что созданное пространство данных. Функции используются для добавления аппаратного заголовка перед передачей пакета. Вновь, __skb_push отличается тем, что она не проверяет имеющееся пространство на достаточность.

 

int skb_tailroom(struct sk_buff *skb);

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

 

int skb_headroom(struct sk_buff *skb);

Возвращает количество пространства, доступного перед данными, то есть, сколько октетов можно "затолкать" в буфер.

 

void skb_reserve(struct sk_buff *skb, int len);

Увеличивает как data, так и tail. Функция может быть использована для резервирования запаса до заполнения буфера. Большинство интерфейсов Ethernet резервируют два байта перед пакетом; таким образом, заголовок IP выравнивается по 16-ти байтной границе, после 14-го байта Ethernet заголовка. Так же это делает и snull, хотя команда не была показана в разделе "Приём пакетов", чтобы избежать введения дополнительных концепций в том месте.

 

unsigned char *skb_pull(struct sk_buff *skb, int len);

Удаляет данные из головы пакета. Драйвер нет необходимости использовать эту функцию, но она включена здесь для полноты. Она уменьшает skb->len и увеличивает skb->data; это способ отделения аппаратного заголовка (Ethernet или эквивалентного) от начала входящих пакетов.

 

int skb_is_nonlinear(struct sk_buff *skb);

Возвращает истинное значение, если этот skb разделён на несколько фрагментов для ввода/вывода с разборкой/сборкой.

 

int skb_headlen(struct sk_buff *skb);

Возвращает длину первого сегмента skb (ту часть, на которую указывает skb->data).

 

void *kmap_skb_frag(skb_frag_t *frag);

void kunmap_skb_frag(void *vaddr);

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

 

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

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