Содержание


Разработка модулей ядра Linux

Часть 31. Структуры данных, используемые при работе с сетевой подсистемой

Comments

Серия контента:

Этот контент является частью # из серии # статей: Разработка модулей ядра Linux

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Разработка модулей ядра Linux

Следите за выходом новых статей этой серии.

Реализация сетевой подсистемы Linux организована так, чтобы не зависеть от особенностей реализации протоколов. Основной структурой данных, описывающей сетевой интерфейс (устройство), является struct net_device, а основной структурой, с помощью которой происходит обмен данными между сетевыми уровнями и на основе которой построена работа всей сетевой подсистемы является буфер сокета - struct sk_buff (файл <linux/skbuff.h>).

Cтруктура sk_buff

Буфер сокета состоит их двух частей:

  • управляющие данные, находящиеся в структуре struct sk_buff;
  • данные пакета (указываемые в struct sk_buff указателями head и data).

Буферы сокетов упорядочиваются в виде очереди (struct sk_queue_head) посредством своих двух первых полей next и prev.

Листинг 1. Фрагмент структуры sk_buff
typedef unsigned char *sk_buff_data_t; 
struct sk_buff { 
   struct sk_buff *next; /* эти два элемента должны быть объявлены первыми. */ 
   struct sk_buff *prev; 
...
   sk_buff_data_t  transport_header;
   sk_buff_data_t  network_header;
   sk_buff_data_t  mac_header;
...
   unsigned char *head, 
                 *data; 
...
};

Структура вложенности заголовков сетевых уровней в точности соответствует структуре инкапсуляции сетевых протоколов внутри друг друга.

Экземпляры данных типа struct sk_buff:

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

Прохождение экземпляра буфера сокета сквозь стек сетевых протоколов будет детально проанализировано в следующей статье.

Создание сетевого интерфейса

Сетевой интерфейс – это название той конечной точки, где начинается или завершается обработка буфера сокета (пакета данных). Имя сетевого интерфейса завершается числовым суффиксом — порядковым номером данного интерфейса и является уникальным в системе, соответствуя имени в каталоге /dev для потоковых устройств. Сетевой интерфейс увязывает воедино множество параметров канального уровня (например, аппаратный MAC-адрес) с параметрами более высоких уровней (например, IP адрес).

$ ifconfig wlan0 
wlan0     Link encap:Ethernet  HWaddr 00:13:02:69:70:9B  
          inet addr:192.168.1.22  Bcast:192.168.1.255  Mask:255.255.255.0 
          inet6 addr: fe80::213:2ff:fe69:709b/64 Scope:Link 
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1 
...

Создадим и зарегистрируем в системе новый сетевой интерфейс, исходный код которого можно найти в архиве network.tgz в разделе "Материалы для скачивания". Приведенные примеры основаны на концепциях, приведенных в книге "Writing Linux Device Drivers" (см. ссылку в разделе "Ресурсы").

Листинг 2. Создание сетевого интерфейса
#include <linux/module.h> 
#include <linux/netdevice.h> 

static struct net_device *dev; 

static int my_open( struct net_device *dev ) { 
   printk( KERN_INFO "Hit: my_open(%s)\n", dev->name ); 
   /* запуск очереди для передачи данных */ 
   netif_start_queue( dev ); 
   return 0; 
}

static int my_close( struct net_device *dev ) { 
   printk( KERN_INFO "Hit: my_close(%s)\n", dev->name ); 
   /* остановка очереди для передачи данных */ 
   netif_stop_queue( dev ); 
   return 0; 
} 

static int stub_start_xmit( struct sk_buff *skb, struct net_device *dev ) { 
   dev_kfree_skb( skb ); 
   return 0; 
}

static struct net_device_ops ndo = { 
   .ndo_open = my_open, 
   .ndo_stop = my_close, 
   .ndo_start_xmit = stub_start_xmit, 
}; 

static void my_setup( struct net_device *dev ) { 
   int j; 
   printk( KERN_INFO "my_setup(%s)\n", dev->name ); 
   /* указать значение MAC-адреса */ 
   for( j = 0; j < ETH_ALEN; ++j ) 
      dev->dev_addr[ j ] = (char)j; 
   ether_setup( dev ); 
   dev->netdev_ops = &ndo; 
} 

static int __init my_init( void ) { 
   printk( KERN_INFO "Loading stub network module:...." ); 
   dev = alloc_netdev( 0, "fict%d", my_setup ); 
   if( register_netdev( dev ) ) { 
      printk( KERN_INFO " Failed to register\n" ); 
      free_netdev( dev ); 
      return -1; 
   } 
   printk( KERN_INFO "Succeeded in loading %s!\n", dev_name( &dev->dev ) ); 
   return 0; 
} 

static void __exit my_exit( void ) { 
   printk( KERN_INFO "Unloading stub network module\n" ); 
   unregister_netdev( dev ); 
   free_netdev( dev ); 
} 

module_init( my_init ); 
module_exit( my_exit );

Стоит обратить внимание на вызов alloc_netdev(), который в качестве параметра получает форматный шаблон (%d) имени нового интерфейса: мы задаём префикс имени (fict), а система сама присваивает первый свободный номер интерфейсу с таким префиксом. Обратите также внимание, как в цикле MAC-адрес интерфейса заполняется фиктивным значением 00:01:02:03:04:05. Созданное сетевое устройство можно установить в системе, как показано ниже.

$ sudo insmod ./network.ko 
$ dmesg | tail -n4 
[ 7355.005588] Loading stub network module:.... 
[ 7355.005597] my_setup() 
[ 7355.006703] Succeeded in loading fict0! 
$ ip link show dev fict0 
5: fict0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN qlen 1000 
    link/ether 00:01:02:03:04:05 brd ff:ff:ff:ff:ff:ff 
$ sudo ifconfig fict0 192.168.56.50 
$ dmesg | tail -n6 
[ 7355.005588] Loading stub network module:.... 
[ 7355.005597] my_setup() 
[ 7355.006703] Succeeded in loading fict0! 
[ 7562.604588] Hit: my_open(fict0) 
[ 7573.442094] fict0: no IPv6 routers present 
$ ping 192.168.56.50 
PING 192.168.56.50 (192.168.56.50) 56(84) bytes of data. 
64 bytes from 192.168.56.50: icmp_req=1 ttl=64 time=0.253 ms 
...
^C 
--- 192.168.56.50 ping statistics --- 
4 packets transmitted, 4 received, 0% packet loss, time 3000ms 
rtt min/avg/max/mdev = 0.056/0.105/0.253/0.085 ms 
$ ifconfig fict0 
fict0     Link encap:Ethernet  HWaddr 00:01:02:03:04:05 
          inet addr:192.168.56.50  Bcast:192.168.56.255  Mask:255.255.255.0 
          inet6 addr: fe80::201:2ff:fe03:405/64 Scope:Link 
...

Cтруктура net_device

Как отмечалось выше, основу описания сетевого интерфейса составляет структура struct net_device, описанная в файле <linux/netdevice.h>. Эта структура, содержит не только описание аппаратных средств, но и конфигурационные параметры сетевого интерфейса по отношению к выше лежащим протоколам.

Листинг 3. Фрагмент структуры net_device
struct net_device { 
  char  name[ IFNAMSIZ ] ; 
...
   unsigned long  base_addr; /* I/O-адрес устройства */ 
   unsigned int   irq;       /* IRQ-номер устройства */ 
...
   unsigned short type;      /* тип интерфейса */ 
...
}

Поле type, например, определяет тип аппаратного адаптера с точки зрения ARP-механизма разрешения MAC-адресов (файл <linux/if_arp.h>), как показано ниже:

#define ARPHRD_ETHER       1    /* Ethernet 10Mbps           */
...
#define ARPHRD_IEEE80211 801    /* IEEE 802.11               */

Подробный разбор полей структуры struct net_device или их возможных значений не имеет смысла, так как эта структура может радикально меняться между подверсиями ядра; и её анализ должен проводиться «по месту» на основе изучения названных выше заголовочных файлов.

Вместе со структурой сетевого интерфейса обычно создаётся и связывается в коде модуля приватная структура данных, в которой пользователь может размещать произвольные данные, ассоциированные с интерфейсом. Это стандартная практика для ядра Linux, которая применяется не только в сетевой подсистеме. Указатель такой приватной структуры помещается в структуру сетевого интерфейса, и доступ к нему (а значит и к приватной структуре) должен определяться исключительно в специально определённой для этого функции netdev_priv(). Ниже приведен фрагмент данной функции, определенной в ядре 3.09, но нет никаких гарантий, что в другой версии ядра не появится никаких изменений.

static inline void *netdev_priv( const struct net_device *dev ) 
{ 
        return (char *)dev + ALIGN( sizeof( struct net_device ), NETDEV_ALIGN ); 
}

При создании интерфейса размер этой пользовательской структуры передаётся в первом параметре функции alloc_netdev():

child = alloc_netdev( sizeof( struct priv ), "fict%d", &setup );

При успешном создании сетевого интерфейса данная структура будет размещена в "хвостовой" части структуры struct net_device и станет доступна по вызову netdev_priv().

Все структуры, описывающие сетевые интерфейсы, доступные в системе, объединены в единый список. Исходный код для получения такого списка находится в файле devices.c в архиве network.tgz.

Листинг 4. Получение списка сетевых интерфейсов
#include <linux/module.h> 
#include <linux/init.h> 
#include <linux/netdevice.h> 

static int __init my_init( void ) { 
   struct net_device *dev; 
   printk( KERN_INFO "Hello: module loaded at 0x%p\n", my_init ); 
   dev = first_net_device( &init_net ); 
   printk( KERN_INFO "Hello: dev_base address=0x%p\n", dev ); 
   while ( dev ) { 
      printk( KERN_INFO 
              "name = %6s irq=%4d trans_start=%12lu last_rx=%12lu\n", 
              dev->name, dev->irq, dev->trans_start, dev->last_rx ); 
      dev = next_net_device( dev ); 
   } 
   return -1; 
} 

module_init( my_init );

Загрузим ранее созданный модуль network.ko и просмотрим список существующих сетевых интерфейсов.

$ sudo insmod network.ko 
$ sudo insmod devices.ko 
insmod: error inserting 'devices.ko': -1 Operation not permitted 
$ dmesg | tail -n8 
Hello: module loaded at 0xf8853000 
Hello: dev_base address=0xf719c400 
name =     lo  irq=   0  trans_start=           0  last_rx=           0 
name =   eth0  irq=  16  trans_start=  4294693516  last_rx=           0 
...
name = mynet0  irq=   0  trans_start=           0  last_rx=           0

Заключение

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


Ресурсы для скачивания


Похожие темы


Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux, Open source
ArticleID=844076
ArticleTitle=Разработка модулей ядра Linux: Часть 31. Структуры данных, используемые при работе с сетевой подсистемой
publish-date=11012012