1、引言
首先块设备在/dev目录下有设备节点,而网络设备没有这样的设备入口。read,write等常规的文件接口在网络设备下也没有意义。
最大的区别在于:块设备只响应内核的数据请求;而网络设备驱动要异步地接收来自外部的数据包。简单地说,块设备驱动是被要求传输数据而网络设备是主动请求传输数据。网络设备驱动还需要支持设置地址,修改传输参数等等这样的操作,所以网络设备驱动的api需要提供这些接口。
本文是对上文虚拟硬件的网络驱动例子进行一个简单的梳理。
(1)网络设备注册
<code class="hljs cs has-numbering">头文件:<linux/netdevice.h> <span class="hljs-keyword">struct</span> net_device 网络设备结构体 <span class="hljs-keyword">struct</span> net_device *alloc_netdev (<span class="hljs-keyword">int</span> size_priv, <span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span> *name, <span class="hljs-keyword">void</span> (*setup)(<span class="hljs-keyword">struct</span> net_device *)); <span class="hljs-keyword">int</span> register_netdev(<span class="hljs-keyword">struct</span> net_device *device); 注册网络设备 <span class="hljs-keyword">void</span> unregitster_netdev(<span class="hljs-keyword">struct</span> net_device *device); 注销网络设备</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li></ul>
(2)打开和关闭
驱动在加载入内核后,内核会调用probe函数来探测它。在网络接口可以传送数据包时,内核必须首先打开它并给它设置地址。内核打开和关闭网络接口是由ifconfig命令触发的。
<code class="hljs delphi has-numbering">int <span class="hljs-comment">(*open)(struct net_device*)</span>; 打开网络设备 int <span class="hljs-comment">(*stop)(struct net_device*)</span>; 关闭网络设备 void netif_start_queue(struct net_device*); 启动网络传输队列 void netif_stop_queue(struct net_device*); 关闭网络传输队列</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>
(3)网络数据的发送
网络接口最重要的作用是发送和接收网络数据。
<code class="hljs cs has-numbering">头文件:<linux/skbuff.h> 定义了网络驱动中传输的基本单元,<span class="hljs-keyword">struct</span> sk_buff <span class="hljs-keyword">struct</span> netdeviceops 网络设备驱动需要实现的接口函数 netdev_tx_t (*ndo_start_xmit) (<span class="hljs-keyword">struct</span> sk_buff *skb, <span class="hljs-keyword">struct</span> net_device *dev); 传输网络数据包的函数 <span class="hljs-keyword">void</span> (*ndo_tx_timeout) (<span class="hljs-keyword">struct</span> net_device *dev); 传输超时函数</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li></ul>
(4)网络数据的接收
接收网络数据相对于发送数据要复杂一些,因为你需要在原子上下文中把分配一个 sk_buff 并把它移交给上层处理。
数据包接收有两种实现方式:中断驱动和轮询。大多数驱动都是中断驱动的,有一些高吞吐量的驱动会使用轮询的方式。
<code class="hljs glsl has-numbering"><span class="hljs-keyword">struct</span> sk_buff *dev_alloc_skb(unsigned <span class="hljs-keyword">int</span> <span class="hljs-built_in">length</span>); 原子上下文中分配skb printk_ratelimit() 限制printk的输出频率,在中断相应函数中减少输出</code><ul style="display: block;" class="pre-numbering"><li>1</li><li>2</li></ul>
实现高吞吐量的网络驱动,要减少网络阻塞最好的方法是使用napi,后面会介绍
(5)中断处理
硬件可以中断cpu发送两种事件:新的数据包到来和发送的数据包已经发送完成。
判断并处理数据包事件
如果在其他地方暂时停止了发送队列,应该在中断函数中重新启动它
(6)NAPI
高吞吐量的网络接口如果每个数据包都用中断来处理的话会给系统带来很大的负担,这个时候应该使用基于轮询的 NAPI。这样可以减轻系统的负担,减少阻塞的时间。
只有极少数的设备实现了NAPI,因为实现起来比中断要复杂,而且有其他的一些条件。
在中断处理函数中,首先禁止进一步的中断处理,然后调度轮询函数,进入轮询函数后连续处理多个数据发送请求。
<code class="hljs perl has-numbering"><span class="hljs-keyword">int</span> (<span class="hljs-variable">*poll</span>)(struct net_device <span class="hljs-variable">*dev</span>, <span class="hljs-keyword">int</span> <span class="hljs-variable">*budget</span>); 网络驱动轮询函数 <span class="hljs-keyword">int</span> netif_rx_schedule(struct net_device <span class="hljs-variable">*dev</span>); 准备调用轮询函数 <span class="hljs-keyword">int</span> (<span class="hljs-variable">*poll</span>)(struct net_device <span class="hljs-variable">*dev</span>, <span class="hljs-keyword">int</span> <span class="hljs-variable">*budget</span>); 轮询函数</code>