0%

【Linux内核网络】net_device 数据结构

在 Linux 内核中,net_device 数据结构用来代表网络设备,包含设备的硬件和软件配置信息,是网络驱动层最核心的数据结构。

结构体解析

通用字段

设备名称、next指针指向下一个net_device、设备状态state、网络设备索引值 ifindex(用来标志网络设备以便快速定位,设备被创建后由dev_get_index函数分配)、refcnt表示网络设备的引用次数。

1
2
3
4
5
6
struct net_device {
char name[IFNAMSIZ]; // 设备名称,如果被驱动程序设置的名称中包含 %d 格式化字符串,register_netdev 将使用一个数字替换它
unsigned long state; // 设备状态,包含若干标识,驱动程序通常无需直接操作这些标识,内核提供了一组工具函数
void *priv; /* pointer to private data */
/* ... */
};

硬件信息

内存共享字段(描述网络适配器与内核共享的内存空间,指定发送包和接受包所在的区域)、I/O基地址(用于驱动程序搜索设备)、设备使用的中断号irq、分配给设备的DMA通道号、多端口设备使用的不同端口if_port(网络介质类型决定)。

1
2
3
4
5
6
7
8
9
struct net_device {
unsigned long mem_end; /* shared mem end */
unsigned long mem_start; /* shared mem start */
unsigned long base_addr; /* device I/O address */
unsigned int irq; /* device IRQ number */
unsigned char if_port; /* Selectable AUI, TP,..*/
unsigned char dma; /* DMA channel */
/* ... */
};

物理层数据字段

指定2层协议头部长度、最大传输单元mtu(以太网1500byte)、网络设备输出队列的最大长度、网络设备类型type、地址字段(广播地址、多播地址表等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct net_device {
unsigned mtu; /* interface MTU value */
unsigned short type; /* interface hardware type */
unsigned short hard_header_len; /* hardware hdr length */
unsigned int flags; /* interface flags (a la BSD) */

/* Interface address info used in eth_type_trans() */
unsigned char dev_addr[MAX_ADDR_LEN]; /* hw address, (before bcast because most packets are unicast) */
unsigned char broadcast[MAX_ADDR_LEN]; /* hw bcast add */

unsigned long tx_queue_len; /* Max frames per queue allowed */

/* ... */
};

设备方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
struct net_device {
// 打开接口,在 ifconfig 激活接口时,接口将被打开。open 函数应该注册所有的系统资源(I/O端口、IRQ、DMA等等),打开硬件,并对设备执行其他所需的设置
int (*open)(struct net_device *dev);
// 停止接口,当设备终止时应该被停止,其执行操作与open相反
int (*stop)(struct net_device *dev);

// 执行数据包的发送
int (*hard_start_xmit) (struct sk_buff *skb, struct net_device *dev);

// 在 hard_start_xmit 前被调用,将传入的信息组织成硬件头
int (*hard_header) (struct sk_buff *skb,
struct net_device *dev,
unsigned short type,
void *daddr,
void *saddr,
unsigned len);

// 该函数用来在传输数据包之前,完成 ARP 解析之后,重新建立硬件头
int (*rebuild_header)(struct sk_buff *skb);

// 改变接口配置,是配置驱动程序的入口点,可在运行中改变设备的I/O地址和中断号
int (*set_config)(struct net_device *dev, struct ifmap *map);

// 如果数据包的传输在合理的时间段内失败,则假定丢失了中断或接口被锁住
// 这时网络代码将调用该方法,它负责解决问题并重新开始数据包的传输
void (*tx_timeout) (struct net_device *dev);

// 当应用程序需要获得接口的统计信息时,将调用该函数
struct net_device_stats* (*get_stats)(struct net_device *dev);


/* 下面是可选设备操作 */

// NAPI兼容驱动程序提供这个方法,在禁止中断时,以轮询模式操作接口
int (*poll) (struct net_device *dev, int *quota);

// 当设备的组播列表发生改变,或者设备标识位发生改变时,将调用该方法
void (*set_multicast_list)(struct net_device *dev);
// 如果接口支持硬件地址的改变,则可实现该方法
int (*set_mac_address)(struct net_device *dev, void *addr);

// 执行接口的 ioctl 命令
int (*do_ioctl)(struct net_device *dev, struct ifreq *ifr, int cmd);

// 在接口的 MTU 改变时,该函数负责采取相应的动作。
// 如果驱动程序在用户改变MTU时需要完成某些特定工作,则应该声明自己的函数,否则默认的函数可正确实现相关处理
int (*change_mtu)(struct net_device *dev, int new_mtu);

/* ... */
};

网络设备的创建和注册

一个网络设备被使用前,需要先被创建成为一个struct net_device并注册。下面描述注册过程:

和其他所有内核数据结构一样,net_device 包含了一个 kobject 和引用计数,并且通过 sysfs 导出信息。由于与其他结构相关,因此它必须被动态分配。用来执行分配的内核函数是 alloc_netdev,其原型如下:

1
struct net_device *alloc_netdev(int sizeof_priv, const char *name, void (*setup)(struct net_device *));

函数参数定义如下:

  • sizeof_priv 是驱动程序的私有数据区的大小,这个区成员和 net_device 结构一同分配给网络设备。
  • name 是接口的名字,在用户空间可见,可以使用类似 printf%d 的格式
  • setup 是一个初始化函数,用来设置 net_device 结构剩余的部分

使用示例如下,注意必须要检查函数返回值,以确定分配工作完成:

1
2
3
4
struct net_device snull;
snull = alloc_netdev(sizeof(struct snull_priv), "sn%d", snull_init);
if (snull == NULL)
goto out;

网络子系统针对 alloc_netdev 函数,为不同种类的接口封装了许多函数,最常用的是 alloc_etherdev

1
struct net_device *alloc_etherdev(int sizeof_priv);

该函数使用 eth%d 的形式指定分配给网络设备的名字,它提供了自己的初始化函数 ether_setup,用正确的值为以太网设备设置 net_device 中的许多成员。因此在驱动程序中没有为 alloc_etherdev 提供初始化函数。

一旦 net_device 结构体被初始化后,剩余的工作就是将该结构体传递给 register_netdev 函数:

1
int		register_netdev(struct net_device *dev);

网络设备的开启与关闭

net/core/dev.c

开启网络设备函数dev_open(struct net_device *dev)。如果网络设备已经激活或者它尚未被注册,函数返回错误信息。

  • 判断设备是否激活;
  • 使用set_bit函数修改设备状态为__LINK_STATE_START,调用net_device中的open指向函数设置该设备;
  • 激活网络设备的队列和调度器;
  • 将事件(NETDEV_UP)登记到通知链:notifier_call_chain(&netdev_chain,事件,dev)。

关闭网络设备函数dev_close(struct net_device *dev)

  • 如果网络设备未被激活,则不需要关闭;
  • 将事件(NETDEV_GOING_DOWN)登记到通知链:notifier_call_chain(&netdev_chain,事件名,dev);
  • 删除包调度器中的相应信息:dev_deactivate;
  • 清除设备的活动状态:clear_bit;
  • 调用net_device中的stop指向函数,执行停止操作。

参考资料

  • Understanding Linux Kernel Internals:Part II