【摘要】前文我们分析了一个虚拟硬件的网络驱动样例。从中我们看到了网络设备的一些接口。事实上网络设备驱动和块设备驱动的功能比較相似,都是发送和接收数据包(数据请求)。
当然它们实际是有非常多不同的。
1、引言
首先块设备在/dev文件夹下有设备节点。而网络设备没有这种设备入口。
read,write等常规的文件接口在网络设备下也没有意义。
最大的差别在于:块设备仅仅响应内核的数据请求;而网络设备驱动要异步地接收来自外部的数据包。简单地说。块设备驱动是被要求数据传输而网络设备是主动请求数据传输。网络设备驱动还须要支持设置地址,改动传输參数等等这种操作。所以网络设备驱动的api须要提供这些接口。
本文是对上文虚拟硬件的网络驱动样例进行一个简单的梳理。
(1)网络设备注冊
头文件:<linux/netdevice.h>
struct net_device 网络设备结构体
struct net_device *alloc_netdev (int size_priv, const char *name, void (*setup)(struct net_device *));
int register_netdev(struct net_device *device); 注冊网络设备
void unregitster_netdev(struct net_device *device); 注销网络设备
(2)打开和关闭
驱动在载入入内核后,内核会调用probe函数来探測它。在网络接口能够传送数据包时,内核必须首先打开它并给它设置地址。内核打开和关闭网络接口是由ifconfig命令触发的。
int (*open)(struct net_device*); 打开网络设备
int (*stop)(struct net_device*); 关闭网络设备
void netif_start_queue(struct net_device*); 启动网络传输队列
void netif_stop_queue(struct net_device*); 关闭网络传输队列
(3)网络数据的发送
网络接口最关键的数据是发送和接收网络数据。
头文件:<linux/skbuff.h> 定义了网络驱动中传输的基本单元。struct sk_buff
struct netdeviceops 网络设备驱动须要实现的接口函数
netdev_tx_t (*ndo_start_xmit) (struct sk_buff *skb, struct net_device *dev); 传输网络数据包的函数
void (*ndo_tx_timeout) (struct net_device *dev); 传输超时函数
(4)网络数据的接收
接收网络数据相对于发送数据要复杂一些。由于你须要在原子上下文中把分配一个 sk_buff 并把它移交给上层处理。
数据包接收有两种实现方式:中断驱动和轮询。大多数驱动都是中断驱动的。有一些高吞吐量的驱动会使用轮询的方式。
struct sk_buff *dev_alloc_skb(unsigned int length); 原子上下文中分配skb
printk_ratelimit() 限制printk的输出频率,在中断对应函数中降低输出
实现高吞吐量的网络驱动,要降低网络堵塞最好的方法是使用napi,后面会介绍
(5)中断处理
硬件能够中断cpu发送两种事件:新的数据包到来和发送的数据包已经发送完毕。
推断并处理数据包事件
假设在其它地方临时停止了发送队列。应该在中断函数中又一次启动它
(6)NAPI
高吞吐量的网络接口假设每一个数据包都用中断来处理的话会给系统带来非常大的负担。这个时候应该使用基于轮询的 NAPI。这样能够减轻系统的负担,降低堵塞的时间。
仅仅有极少数的设备实现了NAPI,由于实现起来比中断要复杂。并且有其它的一些条件。
在中断处理函数中,首先禁止进一步的中断处理,然后调度轮询函数,进入轮询函数后连续处理多个数据发送请求。
int (*poll)(struct net_device *dev, int *budget); 网络驱动轮询函数
int netif_rx_schedule(struct net_device *dev); 准备调用轮询函数
int (*poll)(struct net_device *dev, int *budget); 轮询函数