Подключение к ядру

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

Мы начинаем просмотр структуры сетевых драйверов анализируя исходный код snull. Сохранение под рукой исходного кода нескольких драйверов может помочь вам следить за обсуждением и увидеть, как работают реальные сетевые драйверы Linux. В качестве места для начала, мы предлагаем loopback.c, plip.c и e100.c, в порядке возрастания сложности. Все эти файлы находятся в drivers/net в дереве исходных текстов ядра.

Регистрация устройства

Когда модуль драйвера загружается в работающее ядро, он запрашивает ресурсы и предлагает возможности; в этом нет ничего нового. И также нет ничего нового в том, как запрашиваются ресурсы. Драйверу необходимо проверить его устройство и размещение оборудования (порты ввода/вывода и линию прерывания), но не регистрировать их, как описано в разделе "Установка обработчика прерываний" в Главе 10. Способ, которым сетевой драйвер регистрируется своей функцией инициализации модуля, отличается от символьных и блочных драйверов. Поскольку для сетевых интерфейсов не существует эквивалента старшего и младшего номеров, сетевой драйвер не запрашивает такой номер. Вместо этого, драйвер помещает структуру данных для каждого вновь обнаруженного интерфейса в глобальный список сетевых устройств.

 

Каждый интерфейс описывается объектом struct net_device, которая определена в <linux/netdevice.h>. Драйвер snull хранит указатели на две такие структуры (sn0 и sn1) в простом массиве:

 

struct net_device *snull_devs[2];

 

Структура net_device, как и многие другие структуры ядра, содержит kobject и является, таким образом, учитываемой по ссылке и экспортируемой через sysfs. Как и в случае с другими такими структурами, она должна быть создана динамически. Функцией ядра, предоставленной для выполнения такого выделения памяти, является alloc_netdev, которая имеет следующий прототип:

 

struct net_device *alloc_netdev(int sizeof_priv,

                                const char *name,

                                void (*setup)(struct net_device *));

 

Здесь, sizeof_priv является размером области "личных данных" драйвера; в случае сетевых устройств, эта область выделяется вместе со структурой net_device. На самом деле, они обе создаются вместе в одном большом участке памяти, но авторы драйверов должны притворяться, что они не знают этого. name является именем этого интерфейса, как оно видится пользовательским пространством; это имя может содержать %d в стиле printf. Ядро заменяет %d следующим доступным номером интерфейса. Наконец, setup является указателем на функцию инициализации, которая вызывается для настройки остальной части структуры net_device. Мы скоро доберёмся до функции инициализации, но на данный момент достаточно сказать, что snull выделяет память для своих двух структур устройства таким образом:

 

snull_devs[0] = alloc_netdev(sizeof(struct snull_priv), "sn%d", snull_init);

snull_devs[1] = alloc_netdev(sizeof(struct snull_priv), "sn%d", snull_init);

if (snull_devs[0] == NULL || snull_devs[1] == NULL)

    goto out;

 

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

 

Сетевая подсистема предоставляет ряд вспомогательных интерфейсных функций вокруг alloc_netdev для различных типов интерфейсов. Наиболее распространённой является alloc_etherdev, которая определена в <linux/etherdevice.h>:

 

struct net_device *alloc_etherdev(int sizeof_priv);

 

Эта функция выделяет память для сетевого устройства используя для аргумента имени eth%d. Он обеспечивает свою функцию инициализации (ether_setup), которая устанавливает несколько полей в net_device в соответствующие значения для устройств Ethernet. Таким образом, для функции alloc_etherdev нет поставляемой драйвером функции инициализации, драйвер должен просто выполнять требуемую инициализацию непосредственно после успешного выделения памяти. Авторы драйверов для других типов устройств могут захотеть воспользоваться одной из других вспомогательных функций, такой как alloc_fcdev (определена в <linux/fcdevice.h>) для волоконно-оптических устройств, alloc_fddidev (<linux/fddidevice.h>) для FDDI устройств или alloc_trdev (<linux/trdevice.h>) для устройств Token Ring.

 

snull мог бы без проблем использовать alloc_etherdev; мы решили использовать вместо этого alloc_netdev, как способ демонстрации низкоуровневого интерфейса и получения контроля над именем, назначаемым интерфейсу.

 

После того как структура net_device была проинициализирована, завершение этого процесса - просто вопрос передачи структуры в register_netdev. В snull вызов выглядит следующим образом:

 

for (i = 0; i < 2; i++)

    if ((result = register_netdev(snull_devs[i])))

        printk("snull: error %i registering device \"%s\"\n",

                result, snull_devs[i]->name);

 

Здесь применимо обычное предостережение: как только вы вызвали register_netdev, ваш драйвер может быть вызван для операций с устройством. Таким образом, вы не должны регистрировать устройство, пока всё не было полностью проинициализировано.

Инициализация каждого устройства

Мы посмотрели на создание и регистрацию структур net_device, но мы прошло мимо промежуточного шага полной инициализации такой структуры. Обратите внимание, что struct net_device всегда собирается воедино во время работы, она не может быть создана во  время компиляции тем же способом, как структуры file_operations или block_device_operations. Эта инициализация должна быть завершена перед вызовом register_netdev. Структура net_device большая и сложная; к счастью, ядро заботится о некоторых общих значениях по умолчанию для Ethernet через функцию ether_setup (которую вызывает alloc_etherdev).

 

Так как snull использует alloc_netdev, он имеет отдельную функцию инициализации. Суть этой функции (snull_init) выглядит следующим образом:

 

ether_setup(dev); /* assign some of the fields */

 

dev->open            = snull_open;

dev->stop            = snull_release;

dev->set_config      = snull_config;

dev->hard_start_xmit = snull_tx;

dev->do_ioctl        = snull_ioctl;

dev->get_stats       = snull_stats;

dev->rebuild_header  = snull_rebuild_header;

dev->hard_header     = snull_header;

dev->tx_timeout      = snull_tx_timeout;

dev->watchdog_timeo  = timeout;

/* сохраняем флаги по умолчанию, добавляем только NOARP */

dev->flags           |= IFF_NOARP;

dev->features        |= NETIF_F_NO_CSUM;

dev->hard_header_cache = NULL; /* Запрет кэширования */

 

Вышеприведённый код является довольно обычной инициализацией структуры net_device; это, главным образом, вопрос  сохранения указателей на наши разнообразные функции драйвера. Одно необычной особенностью этого кода является установка IFF_NOARP в флагах. Это указывает, что данный интерфейс не может использовать протокол разрешения адреса (Address Resolution Protocol, ARP). ARP является низкоуровневым протоколом Ethernet; его работа заключается в превращении IP адресов в Ethernet адреса протокола управления доступом к среде (medium access control, MAC). Поскольку "удалённые" системы, моделируемые snull, в действительности не существуют, никто не способен ответить на ARP запросы для них. Вместо того, чтобы усложнить snull добавлением реализации ARP, мы решили пометить интерфейс как неспособный обработать этот протокол. Установка hard_header_cache делается по той же причине: она отключает кэширование (несуществующих) ARP ответов на этом интерфейсе. Эта тема подробно обсуждается в разделе "Разрешение MAC адреса" далее в этой главе.

 

Код инициализации также устанавливает пару полей (tx_timeout и watchdog_timeo), которые связаны с обработкой таймаутов передачи. Мы тщательно освещаем эту тему в разделе "Таймауты при передаче".

 

Сейчас мы рассмотрим ещё одно поле struct net_device, priv. Его роль аналогична указателю private_data, который мы использовали для символьных драйверов. В отличие от fops->private_data, этот указатель priv создаётся вместе со структурой net_device. Прямой доступ к полю priv также не рекомендуется по причинам производительности и гибкости. Когда драйверу необходимо получить доступ к закрытому указателю данных, он должен использовать функцию netdev_priv. Таким образом, драйвер snull полон деклараций, таких как:

 

struct snull_priv *priv = netdev_priv(dev);

 

Модуль snull объявляет структуру данных snull_priv, которая будет использоваться для priv:

 

struct snull_priv {

    struct net_device_stats stats;

    int status;

    struct snull_packet *ppool;

    struct snull_packet *rx_queue; /* Список входящих пакетов */

    int rx_int_enabled;

    int tx_packetlen;

    u8 *tx_packetdata;

    struct sk_buff *skb;

    spinlock_t lock;

};

 

Структура включает, среди прочего, экземпляр struct net_device_stats, который является стандартным местом для хранения статистики интерфейса. Следующие строки в snull_init создают и инициализируют dev->priv:

 

priv = netdev_priv(dev);

memset(priv, 0, sizeof(struct snull_priv));

spin_lock_init(&priv->lock);

snull_rx_ints(dev, 1);      /* разрешаем приём прерываний */

Выгрузка модуля

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

 

void snull_cleanup(void)

{

    int i;

 

    for (i = 0; i < 2; i++) {

        if (snull_devs[i]) {

            unregister_netdev(snull_devs[i]);

            snull_teardown_pool(snull_devs[i]);

            free_netdev(snull_devs[i]);

        }

    }

    return;

}

 

Вызов unregister_netdev удаляет интерфейс из системы; free_netdev возвращает структуру net_device ядру. Если где-то на эту структуру существует ссылка, она может продолжить существовать, но драйверу не надо заботиться об этом. После того, как вы отменили регистрацию интерфейса, ядро уже не вызывает его методы.

 

Обратите внимание, что наша внутренняя очистка (выполненная в snull_teardown_pool) не может произойти, пока устройство не разрегистрировано. Однако, это должно произойти прежде, чем мы вернём структуру net_device системе; после того, как мы вызвали free_netdev, мы не сможем делать любые дальнейшие ссылки на устройство или нашу закрытую область.

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