Буферы памяти, манипулирование сокетом и ввод/вывод сокета

Большинство сетевых KPIs позволяет Вам записать расширения, изменяющие поведение сетевого стека. mbuf KPI подпрограммы являются центральными к этим KPIs, обеспечивая функции для управления пакетами отдельной сети в ядре. Можно также иногда считать полезным выполнить связь с сокетом в ядре, например, связаться с удаленным сервером в клиенте сетевой файловой системы, таком как AFS. Сокет подпрограммы KPI был разработан, чтобы помочь Вам работать с сокетами на уровне ядра, с помощью mbufs как основная единица данных.

Если Вы привыкли к сетевому программированию пространства пользователя, mbuf KPIs может быть незнакомой территорией для Вас. Концептуально, они - просто связанные списки объектов, которые могут или содержать пакетные данные или указатели на внешние буферы, содержащие пакетные данные. Для входящего трафика заголовок пакета также инкапсулируется в mbuf структуре.

Работа с буферами памяти

Все сети, KPIs создаются поверх совместно используемой структуры данных, вызвали буфер памяти или mbuf. mbuf является основной единицей потока данных через сетевой стек и представляет пакет (или часть этого). В этом разделе описываются путь mbufs, и mbuf цепочки организованы, и описывает много общих операций на mbufs. Для полного списка mbuf операций посмотрите kpi_mbuf.h.

Структура mbuf

Буфер памяти или mbuf, представляет содержание единственного пакета данных. Его структура состоит из заголовка пакета (который может отсутствовать для недавно сгенерированного исходящего трафика), и полезная нагрузка (который содержит фактические данные).

Рисунок 2-1  цепочка mbuf цепочек
A chain of mbuf chains

Для меньших полезных нагрузок данные могут инкапсулироваться в самой mbuf структуре как смещение от запуска структуры. Для большей полезной нагрузки (вне длины самого mbuf), полезная нагрузка может быть сохранена отдельно путем соединения mbuf с внешним буфером, названным кластером. Можно учиться, имеет ли mbuf кластер путем вызова mbuf_flags и проверка, чтобы видеть, если MBUF_EXT бит установлен.

mbuf может быть частью отдельно-связанного-списка, названного mbuf цепочкой. Это позволяет Вам цепочечным данным вместе, не существующим в физически непрерывном буфере, не копируя данные. Можно найти следующий узел в mbuf цепочке путем вызова mbuf_next. (Вы не можете получить предыдущий узел, потому что mbuf цепочка является отдельно-связанным-списком.)

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

Пакет, представленный mbuf или mbuf цепочкой, может быть фрагментом большего пакета.

Управление mbuf или mbuf Цепочкой

Используя подпрограммы в kpi_mbuf.h, можно управлять mbuf или mbuf цепочкой многими способами, включая копирование данных к или от mbuf или mbuf цепочки, добавление нового mbufs к цепочке, освобождение mbuf или mbuf цепочки, смещение данных между mbufs в цепочке, чтобы заставить байт расположиться непрерывный (если пространство доступно), и т.д. Этот раздел объясняет некоторые более общие mbuf операции.

Наиболее распространенная работа, которую необходимо будет выполнить, копирует данные в и из mbuf. Для копирования данных с mbuf или mbuf цепочки в локальный буфер выбора использовать mbuf_copydata. Бойтесь переполнять буфера. Для копирования данных назад в mbuf или mbuf цепочку использовать mbuf_copyback. В случае необходимости, mbuf_copyback функция расширит цепочку для размещения большего количества данных. Для примера того, как использовать mbuf_copydata, см. Перечисление 2-1.

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

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

Если Вы пишете код, который должен избежать обрабатывать mbuf несколько раз, подход, который необходимо использовать, зависит от типа фильтра, который Вы пишете. Сетевой стек автоматически отследит, какие фильтры IP обработали mbuf. Таким образом фильтр IP не должен видеть mbuf несколько раз. Если фильтр сокета повторно введет пакет, однако, то система вызовет все фильтры сокета снова для обработки недавно измененного пакета.

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

Наконец, OS X поддерживает изрядное количество статистической информации об этих связанных с сетями структурах данных, включая число mbufs, число кластеров, число байтов полезной нагрузки, доступных в mbuf (не используя кластер), и т.д. Можно получить эту информацию путем вызова mbuf_stats и исследование информации, которую это возвращает.

Блокирование и Неблокирование mbuf Операции

Много mbuf операций — особенно операции, выделяющие новые буферы — берут дополнительные флаги типа mbuf_how_t запрашивать блокирование или неблокирование поведения. При вызове их в блокировании режима операции блокируют, пока требуемое хранение не доступно. Если Вы вызовете их в неблокировании режима, то они сразу возвратят или требуемое хранение (при наличии) или код ошибки, указывающий, почему не могла быть завершена работа. Вот некоторые подсказки для выбора, запросить ли блокирование или неблокирование поведения:

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

  • Если Ваш код выполняется на критическом пути производительности, используйте операции неблокирования:

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

    • Когда высокоуровневый уровень может повторить работу без потери данных. В то время как Вы ожидаете, это будет обычно приводить к лучшей производительности, позволяя другой работе произойти.

  • Если Ваш код вызывают в контексте, где отказ не позволяется, необходимо всегда использовать операции блокирования.

  • Если ни одно из вышеупомянутого не применяется, можно выбрать, какой бы ни режим является самым совместимым с моделью использования. Когда в сомнении, выберите работу неблокирования.

Работа с сокетами в ядре

KPIs сокета очень подобны функциям сокета пространства пользователя за исключением того, что они снабжаются префиксом sock_. Например, функция KPI sock_accept почти идентично функции accept. В отличие от его эквивалентного пространства пользователя, однако, sock_accept не возвращает сокет (дескриптор файла) через его возвращаемое значение. Вместо этого это возвращает a socket_t непрозрачный объект через параметр указателя и возвраты код ошибки (errno_t) через его возвращаемое значение. В результате функции KPI являются более непротиворечивыми, и Ваш код может выполнить лучшую проверку ошибок и создание отчетов.

Вне этих различий в стиле кодирования программирование сокета пространства ядра требует гораздо больше ухода из-за нескольких тонких различий от их эквивалентов пространства пользователя. Эти различия, описанные в разделах, следующих, находятся в двух главных областях: снабдите сокетом I/O и манипулирование сокетами и самими дескрипторами файлов.

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

Управление сокетами и дескрипторами файлов

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

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

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

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

    Если Вы создаете сокет с sock_socket и не вызывайте sock_close на том сокете это будет жить на до следующей перезагрузки, крадя драгоценные ресурсы из других расширений ядра и запущенных приложений. Необходимо очистить после себя. Ядро не может сделать этого для Вас. Это означает:

    • Если Вы создаете сокет с sock_socket, необходимо закрыть его с sock_close.

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

Снабдите ввод и вывод сокетом

Сокет I/O в ядре отличается от пространства пользователя, снабжает I/O сокетом двумя основными способами:

  • В ядре, read и write системные вызовы не доступны. Вместо этого необходимо скопировать данные между mbuf_t возразите и локальное буферное использование mbuf_copydata и mbuf_copyback.

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

Если Ваш код находится в критической по отношению к производительности части ядра (в противоположность вызову от пространства пользователя), необходимо обычно выполнять, снабжают I/O сокетом асинхронно. В ядре этот асинхронный I/O основывается на механизме обратного вызова, с помощью обратных вызовов типа sock_upcall. Перечисление 2-1 показывает, как открыть сокет асинхронно и выполнить асинхронное чтение на использовании сокета sock_receivembuf.

Перечисление 2-1  , Читающее из пространства ядра, снабжает сокетом асинхронно

#include <kern/debug.h>
#include <sys/errno.h>
#include <sys/kpi_mbuf.h>
#include <sys/kpi_socket.h>
#include <net/kpi_protocol.h>
#include <net/ethernet.h>
#include <sys/param.h>
#include <sys/filio.h>
 
#define ULTIMATE_ANSWER 0x00000042
 
/* Forward declarations */
errno_t set_nonblocking(socket_t so, int value);
static void my_sock_callback(socket_t so, void* cookie, int waitf);
 
/* This function opens a connection and sets it up to wait
   for data.  When data is received, the network stack will
   call the callback function my_sock_callback(). */
errno_t open_socket_and_start_listener(void)
{
    socket_t so;
    errno_t err;
    int protocol = 0; // usually the right choice
    struct sockaddr to;
    uint32_t cookie = ULTIMATE_ANSWER; // Normally, we would
                                       // point to a private
                                       // data structure here.
 
    if ((err = sock_socket(PF_INET, SOCK_STREAM, protocol,
        (sock_upcall)&my_sock_callback, (void *)cookie, &so))) {
            return err;
    }
 
    /* Set the socket to non-blocking mode */
    set_nonblocking(so, 1);
 
    /* ... Fill in sockaddr value here ... */
 
    err = sock_connect(so, &to, MSG_DONTWAIT);
    if (err == EINPROGRESS) return 0; // it worked.
    return err;
}
 
/* This function is called when data is available on the socket.
   It reads data from the socket. */
static void my_sock_callback(socket_t so, void* cookie, int waitf)
{
    errno_t err;
    size_t len = sizeof(value);
    mbuf_t data;
    int value;
 
    /* The socket should have some data available now. */
    if (cookie == (void *)ULTIMATE_ANSWER) {
        // This socket's cookie matches the desired magic value,
        // so read data from the socket here.
        err = sock_receivembuf(so, NULL, &data, MSG_WAITALL, &len);
        if (err == EWOULDBLOCK) {
            // The kernel hasn't seen enough data yet.
            return;
        } else if (err || len < sizeof(value)) {
            /* This example does no error recovery.  Your code should. */
            panic("Something is very wrong.  Maybe the other end closed the connection....\n");
        }
    }
 
    // Copy the data from the mbuf chain into local storage.
    err = mbuf_copydata(data, 0, sizeof(value), &value); // Copy 4 bytes at start
 
    // Call a function with the value received.
    dont_panic(htonl(value));
 
    // We no longer need this socket, so close it.
    sock_close(so);
}
 
/* This is a short example of how to use the sock_ioctl()
   function on a socket within the kernel.  This is the
   KPI equivalent of the ioctl() system call. */
errno_t set_nonblocking(socket_t so, int value)
{
    errno_t err;
    int val = value; // taking the address of parameters is bad
 
    if (value != 0 && value != 1) return EINVAL;
    err = sock_ioctl(so, FIONBIO, &val);
    return err;
}

Перечисление 2-1 также показывает использование mbuf подпрограммы mbuf_copydata. Эта подпрограмма копирует данные с mbuf цепочки на буфер. При использовании этой функции необходимо удостовериться, что Вы не переполняете целевого буфера.

Обработка входящих соединений работает так же. Если Вы хотите блокировать, пока соединение не находится на рассмотрении, можно использовать sock_accept в блокировании режима для ожидания входящего соединения. Если Вы предпочли бы обрабатывать входящие соединения асинхронно, можно передать в обратном вызове типа sock_upcall когда Вы создаете сокет с sock_socket. Тот обратный вызов вызовут каждый раз, когда клиент соединяется с Вашим слушать сокет. От Вашего обработчика обратного вызова необходимо тогда вызвать sock_accept (дополнительно с MSG_DONTWAIT флаг).

Запись данных к сокету подобна чтению данных синхронно. Шаги для записи данных к сокету следующие:

  1. Если у Вас уже нет mbuf, вызвать mbuf_allocpacket создать mbuf с кластером для содержания внешних данных. Это не должно быть столь же большим как Ваши данные; следующий шаг развернет цепочку по мере необходимости.

  2. Вызвать mbuf_copyback скопировать данные с локального буфера в mbuf. Для копирования данных с дополнительных буферов просто повторите этот шаг для каждого последующего буфера, указав смещение от запуска mbuf, где должно быть сохранено содержание буфера.

  3. Вызвать sock_sendmbuf отправить данные.

Для получения дополнительной информации

Для получения информации о сокете пространства пользователя, программирующем в целом, необходимо считать Сокет UNIX FAQ. Эта доска объявлений предоставляет ответы к большому количеству основных, промежуточных, и усовершенствованных вопросов о программировании сокета пространства пользователя и включает многочисленные примеры. Можно также найти подробные данные о функциях сокета пространства пользователя в Страницах справочника OS X.

Для узнавания больше о KPI сетевые функции считайте Ссылку Платформы Ядра — в частности ядро mbuf структуры данных и присоединенные функции, описанные в kpi_mbuf.h, и снабдите сокетом APIs, описанный в kpi_socket.h.