深度剖析Linux网卡数据收发过程分析(超详细)
一般来说,网卡主要有两个重要的功能:接收数据 和 发送数据。
所以,当网卡接收到数据包后,要通知 Linux 内核有数据需要处理。另外,网卡驱动应该提供让 Linux 内核把数据把发送出去的接口。
net_device 结构是 Linux 为了适配不同类型的网卡设备而抽象出来的对象,不同的网卡驱动只需要按 Linux 的规范来填充 net_device 结构的各个成员变量,Linux 内核就能够识别出网卡,并工作起来。
下面我们将分析网卡设备接收和发送数据包的实现原理。
net_device 结构
net_device 结构是 Linux 内核对网卡设备的抽象,但由于历史原因,net_device 结构的定义十分复杂。
不过本文主要分析网卡设备收发数据的实现,所以不会分析 net_device 结构的所有成员。下面主要列出收发数据相关的成员,如下:
下面介绍一下各个成员的作用:
name:设备的名字。用于在终端显示设备的名字或者通过设备名字来搜索设备。
irq:中断号。当网卡从网络接收到数据包后,需要产生一个中断来通知 Linux 内核有数据包需要处理,而 irq 就是网卡驱动注册到内核中断服务的中断号。
init、open、stop:分别为设备的初始化接口,打开接口和关闭接口。
hard_start_xmit:当需要通过网卡设备发送数据时,可以调用这个接口来发送数据。
所以,一个网卡驱动必须完成以下两个工作:
通过实现 net_device 结构的 hard_start_xmit 方法来提供发送数据的功能。
通过向内核注册硬件中断服务,来通知内核处理网卡设备接收到的数据包。
也就是说,发送数据的功能是由 net_device 结构的 hard_start_xmit 方法提供,而通知内核处理接收到的数据包的功能是由网卡的硬件中断提供的。
图1 展示了网卡接收和发送数据的过程:

上图展示的是 NS8390网卡 接收和发送数据的过程(红色括号为接收过程,蓝色括号为发送过程),从上图可以发现,NS8390网卡驱动 完成了两件事情:
将 net_device 结构的 hard_start_xmit 方法设置为 ei_start_xmit。
向 Linux 内核注册了 ei_interrupt 硬件中断服务。
所以,当网卡接收到数据包时,会触发 ei_interrupt 中断服务来通知内核有数据包需要处理。而当需要通过网卡发送数据时,将会调用 ei_start_xmit 方法把数据发送出去。
【文章福利】小编推荐自己的Linux内核技术交流群:【891587639】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100名进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)

当网卡从网络中接收到数据包后,会触发 ei_interrupt 中断服务,我们来看看 ei_interrupt 中断服务的实现:
上面的代码删除了很多硬件相关的操作,因为本文并不是分析网卡驱动的实现。
ei_interrupt 中断服务首先读取中断的类型,保存到 interrupts 变量中。然后判断中断类型是否为接收到数据包,如果是就调用 ei_receive 函数从网卡处读取数据。
我们继续分析 ei_receive 函数的实现:
ei_receive 函数主要完成以下几个工作:
申请一个 sk_buff 数据包对象,并且设置其 dev 字段为接收数据包的设备。
通过调用 ei_block_input 函数从网卡中读取接收到的数据,并保存到刚申请的 sk_buff 数据包对象中。ei_block_input 函数是由网卡驱动实现的,所以这里不作详细分析。
通过调用 eth_type_trans 函数从数据包的以太网头部中获取网络层协议类型。
调用 netif_rx 函数将数据包上送给内核网络协议栈。
当把数据包上送给内核网络协议栈后,数据包的处理就由内核接管。一般来说,内核网络协议栈会通过网络层的 IP协议 和传输层的 TCP协议 或者 UDP协议 来对数据包进行处理,处理完后就会把数据提交给应用层的进程进行处理。
发送数据过程
当网络协议栈需要通过网卡设备发送数据时,会调用 net_device 结构的 hard_start_xmit 方法,而对于 NS8390网卡 来说,hard_start_xmit 方法会被设置为 ei_start_xmit 函数。
也就是说,使用 NS8390网卡 发送数据时,最终会调用 ei_start_xmit 函数将数据发送出去。我们来看看 ei_start_xmit 函数的实现:
删减了硬件相关的操作后,ei_start_xmit 函数的实现就非常简单:
首先关闭网卡的硬件中断,防止发送过程中受到硬件中断的干扰。
调用 ei_block_output 函数把数据包的数据发送出去,此函数由网卡驱动实现,这里不作详细分析。
打开网卡的硬件中断,让网卡能够继续通知内核。
总结
本文主要简单的介绍了网卡设备接收和发送数据包的过程,而网卡设备的初始化过程并没有涉及。当然网卡设备的初始化过程也非常重要,会在后面的文章继续分析。
