понедельник, 4 февраля 2013 г.

Перевод статьи описания kernel_flow

Попытка точного перевода статьи kernel_flow.

Предварительно


Обратитесь к Net:Network Overview  для обзора всех аспектов работы сетевого ядра: маршрутизация, обнаружение соседей, NAPI, фильтрация и т.п.
Сетевыми данными (включая заголовки) управляют через структуру данных 
sk_buff. Это минимизирует копирование издержек при прохождении через сетевые уровни. Требуется основное понимание sk_buff, чтобы понять сетевое ядро.
В целом ядро использует виртуальные методы. Они описаны как функции указателей на структуры данных. На диаграмме они обозначены ромбами. Эта статья не покажет всех возможных реализаций этих виртуальных методов, только основные.
Эта статья обсуждает только TCP/IPv4 при соединениях Ethernet. Конечно, возможны комбинации различных сетевых уровней ( таких как туннелирование, построение мостов и т.п.)

Передача

Уровень 5:  сеансовый уровень (сокеты и файлы)

Три системных вызова, которые могут отправлять данные через сеть:
  • write (данные из памяти в файловый дескриптор)
  • sendto (данные из памяти в сокет)
  • sendmsg (составное сообщение в сокет)
В конечном счете все они заканчиваются вызовом __sock_sendmsg(), который в свою очередь выполняет security_sock_sendmsg(), чтобы проверить полномочия, и затем отправляет сообщение на следующий уровень, используя виртуальный метод сокета  sendmsg.

Уровень 4: Транспортный уровень (TCP)

tcp_sendmsg: для каждого сегмента в сообщении
  1. найти sk_buff c доступным пространстве ( использовать конец оставшегося пространства, иначе выделить и добавить один новый)
  2. копировать данные из  пространства пользователя в пространство данных sk_buff (пространство ядра, возможно пространство DMA), используя skb_add_data().
    • Пространство буфера предварительно выделяется для каждого сокета. Если буфер исчерпывает свое пространство, то взаимодействие останавливается: данные возвращаются в пространство пользователя, пока пространство буфера не станет снова доступным ( или сразу вернется ошибка, если не было блокировки.
    • Размер выделенного для sk_buff пространства равен  MSS (Maximum Segment Size) + headroom (*что это?!) (MSS может измениться во время соединения или пользовательскими опциями).
    • на этом уровне проихсодит сегментация или объединение отдельных записей . Независимо от того, что заканчивается в том же sk_buff, его содержимое станет единственным сегментом TCP.  Однако сегменты могут быть фрагментированы на уровне IP.
  3. Активируется очередь TCP; пакеты отправляются с помощью tcp_transmit_skb() (вызывается многократно при наличии нескольких активных буферов).
  4. tcp_transmit_skb() создает TCP-заголовок builds the TCP header (выделение sk_buff оставило место для него). Он клонирует  skb, чтобы передать управление сетевому уровню. Сетевой уровень вызывается с помощью виртуальной функции семейства адресов сокета queue_xmit  (inet_connection_sock->icsk_af_ops). 

Уровень 3: Сетевой уровень (IPv4)

  1. ip_queue_xmit() делает маршрутизацию (если необходимо), создает IPv4-заголовок
  2. nf_hook() вызывается в нескольких местах, чтобы выполнить сетевую фильтрацию (файерволл, NAT, ...). Этот перехват может изменить дайтаграмму или отбросить ее.
  3. Решение по маршрутизации приводит к объекту места назначения (dst_entry). Эти места назначения моделируют IP-адрес получателя дейтаграммы. Вызывается виртуальный метод dst_entry, чтобы выполнить вывод.
  4. sk_buff передается в ip_output() ( или другой механизм вывода, как в случае туннелирования).
  5. ip_output() осуществляет фильтрацию полсе маршрутизации (post-routing), переделывает вывод в новое место назначения во время фильтрации (если это необходимо), фрагментирует дейтаграмму в пакеты (при необходимости) и , наконец, отправляет его в устройство вывода.
    • Фрагментация пытается снова использовать существующие фрагментированные буферы, если это возможно. Это происходит при передаче уже фрагментированного входящего пакета IP. Фрагментированные буферы - это специальные объекты sk_buff, указывающие на некоторую область данных  (т.о. не требуется их копирование).
    • Если нет доступных фрагментированных буферов, то выделяются  новые объекты  sk_buff с новым пространством данных, и данные копируются в них
    • важно помнить, что TCP уже удостоверился, что пакеты меньше, чем MTU, поэтому фрагментация обычно не требуется
  6. Специфичный для устройств вывод снова вызывает виртуальный метод, чтобы вывести соседнюю структуру данных типа dst_entry. Обычно это dev_queue_xmit. Это некоторая оптимизация для пакетов с известным местом назначения (hh_cache).

Уровень 2: канальный уровень (Ethernet)

Главная функция ядра на канальном уровне - это планирование пакетов, которые должны быть отправлены. Для этого Linux использует абстракцию организованной очереди (структура Qdisc). Более подробно можно узнать здесь Chapter 9 (Queueing Disciplines for Bandwidth Management) в the Linux Advanced Routing & Traffic Control HOWTO и документации Documentation/networking/multiqueue.txt.

dev_queue_xmit помещает содержимое tsk_buff в очередь устройства, используя виртуальный метод qdisc->enqueue.
  • при необходимости (когда устройство не поддерживает разрозненные данные) данные линеализируются в sk_buff. Это требует копирования.
  • устройства, которые не имеют очереди  Qdisc (например, loopback) отправляют dev_hard_start_xmit().
  • существует несколько политик планирования очереди Qdisc. Основная и наиболее используемая pfifo_fast имеет три приоритета.
Очередь вывода устройства была сразу инициирована с qdisc_run(). Это вызывает qdisc_restart(), которые берет skb из очереди, используя виртуальный метод qdisc->dequeue. Определенные дисциплины организации очередей могут задержать отправку, не возвращая skb, и настроить вместо этого qdisc_watchdog_timer (). По истечению таймера  вызывается netif_schedule() для запуска передачи.

В итоге sk_buff отправлен с dev_hard_start_xmit() и удален из очередиa Qdisc. Если при отправке возник сбой, skb вновь помещается в очередь. netif_schedule() вызывается для планирования повторной попытки

netif_schedule() повышает программное прерывание, которое заставляет вызвать net_tx_action() ,когда NET_TX_SOFTIRQ сработал через ksoftirqd. net_tx_action() вызывает qdisc_run() для каждого устройства с активной очередью.

dev_hard_start_xmit() вызывает виртуальный метод hard_start_xmit для net_device. Но сначала вызвается dev_queue_xmit_nit(), который проверяет, был ли зарегистрирован пакетный обработчик для протокола ETH_P_ALL (это используется для tcpdump).

Функция драйвера устройства hard_start_xmit будет генерировать одну или несколько команд  сетевому устройсту для планирования передачи буфера. Через некоторое время сетевое устройство ответит, что передача выполнена. Это инициирует освобождение sk_buff. При освобождении sk_buff в контексте прерывания используется dev_kfree_skb_irq(). Это задерживает действительное освобождение до следующего срабатывания NET_TX_SOFTIRQ, помещая skb в очередь softnet_data completion_queue. Этого не приходится делать при освобождении из контекста прерывания.

Прием данных


Уровень 2: канальный уровень (Ethernet)

Сетевое устройство заранее выделяет много буферов sk_buff для приема. Как много - настраивается для каждого устройства. Обычно , адреса пространства данных в этих sk_buff настраивается напрямую как область DMA для устройства. Обработчик прерываний устройств берет буфер sk_buff и производит обработку приема на нем. До NAPI для этого использовался  netif_rx(). В NAPI это проиходит в два этапа:
  1. От обработчика прерываний драйвер устройства просто вызывает netif_rx_schedule() и возвращается из прерывания. netif_rx_schedule() добавляет устройство в список  sofnet_data's poll_list и повышает программное прерывание NET_RX_SOFTIRQ.
  2. ksoftirqd запускает net_rx_action(), который вызывает  виртуальный метод опроса устройства. Метод опроса делает специфичную для устройств буферизацию данных, вызывая netif_receive_skb() для каждого sk_buff, выделяет новые при необходимости буферы sk_buff и завершается вызовом netif_rx_complete().
netif_receive_skb() находит, как передать sk_buff на верхние уровни.
  1. netpoll_rx() вызывается для поддержки Netpoll API
  2. вызываются пакетные обработчики для протокола ETH_P_ALL (для tcpdump)
  3. вызывается handle_ing() для организации входной очереди
  4. вызывается handle_bridge() для создания моста
  5. вызывается handle_macvlan() для VLAN
  6. вызывается пакетный обработчик, зарегистрированный для протокола L3, определенного пакетом
Пакетный обработчики вызываются с функцией deliver_skb(), которая вызывает виртуальный метод протокола для обработки пакета.

Уровень 3: сетевой уровень (IPv4, ARP)


ARP

ARP-пакеты обрабатываются с помощью arp_rcv(). Обрабатывается информация ARP, сохраняется в кэш соседей и отправляет ответ при необходимости. В последнем случае для ответа выделяется новый буфер sk_buff (с новым пространством данных).

IPv4

IPv4-пакеты обрабатываются с помощью ip_rcv(). Он анализирует заголовки, проверяет на правильность, отправляет ICMP-ответ или сообщение об ошибке при необходимости. Также находит адрес места назначения, используя ip_route_input(). Виртуальный метод входа места назначения вызывается с sk_buff.
  • ip_mr_input() вызывается для групповых адресов. Пакет может быть передан, используя ip_mr_forward(), и доставлен локально с помощью ip_local_delivery().
  • ip_forward() вызывается для пакетов с различным местом назначения, для которого есть маршрут. Непосредственно вызвает виртуальный метод вывода соседей.
  • ip_local_deliver() вызывается, если местом назначения пакета является данная машина. Фрагменты дейтаграмм собираются здесь
ip_local_deliver() сначала доставляет всем неструктуированным (raw) сокетам данного соединения, используя raw_local_deliver(). Тогда вызывается обработчик протокола L4 для протокола, указанного в дейтаграмме. Протокол L4 вызывается, даже если существует неструктуированный сокет.
Повсюду включены вызовы  xfrm4_policy_check для поддержки IPSec.

Уровень 4: Транспортный уровень (TCP)

Обработчик протокола для TCP - tcp_v4_rcv(). Большая часть кода здесь имеет дело с обработкой протокола в TCP для настройки соединений, выполнения управлением потока и т.п.
Полученный TCP-пакет может включать подтверждение предыдущего отправленного пакета, который может инициировать дальнейшую отправку пакетов ( tcp_data_snd_check() ) или подтверждений ( tcp_ack_snd_check() ).
Передача входящего пакета на верхний уровень выполнена в  tcp_rcv_established() и tcp_data_queue(). Эти функции обслуживают очередь TCP-соединений out_of_order_queue, а также очереди сокетов sk_receive_queue и sk_async_wait_queue. Если пользовательский процесс уже ждет данные для приема, то данные непосредственно копируются в пространство пользователя с помощью skb_copy_datagram_iovec(). В противном случае sk_buff добавляется в одну из очередей сокетов и будет скопирован позже.
Наконец, функции приема вызывают виртуальный метод сокета sk_data_ready, чтобы сообщить, что данные доступны. Это разбудит ожидающие данных процессы.


Уровень 5:  сеансовый уровень (сокеты и файлы)

Есть три системных вызова, которые могут принимать данные из сети:
  • read (данные в памяти из файлового дескриптора)
  • recvfrom ( данные в памяти из сокета)
  • recvmsg (сложное сообщение из сокета)
Все из них в конечном счете заканчиваются в __sock_recvmsg(), который вызывает security_sock_recvmsg() для проверки полномочий, и затем запрашивает сообщение на следующий уровень, используя виртуальный метод сокета recvmsg.  Часто используется sock_common_recvmsg(), который используется виртуальный метод recmsg протокола сокета.

tcp_recvmsg() либо копирует данные из очереди сокета с помощью skb_copy_datagram_iovec() или ожидает данных для приема, используя sk_wait_data(). Последние блокируются и подымаются при обработке на транспортном уровне (L4).

Комментариев нет:

Отправить комментарий