Tags: Linux, 计算机网络, 驱动程序
引子
当我们为字符设备或块设备编写驱动程序时,我们实现逻辑设备的接口是文件读写接口——file_operations,这个接口基本上直接面向用户空间程序的。而我们要为网络设备编写驱动则不然了,用户空间程序是通过标准套接口(sockets)系统调用来使用网络功能的,用户空间程序与网络设备之间夹着一层TCP/IP协议栈程序。也就是说,当我们为网络设备编写驱动程序时,实现的设备接口是给TCP/IP协议栈“用”的。
事实上,现代操作系统的实现里,TCP/IP协议栈还不是直接“用”网络设备发送接收数据包的,因为为了实现更好网络性能、控制网络流量等,协议栈的第三层——IP网络层和第二层——网络设备层之间发生很多代码活动(activity)。如果不深入理解这两层之间的代码活动(数据流和控制流),那是很难理解和编写网络设备驱动的。下图概观了这些代码活动:
这些活动包括:
1. 网卡的硬件中断将收到数据包的入列CPU的收包队列;
2. 收包软中断(NET_RX_SOFTIRQ)将数据向上层传递或入列网卡的发包队列进行转发;
3. 用户进程通过系统调用将数据发给协议栈,最后入列网卡的发包队列;
4. 各种内核活动(softirq,tasklet,timer)将数据入网卡的发包队列;
5. 发包软中断(NET_TX_SOFTIRQ)直接或间接(经队列调度算法)将数据包出列,通过网卡发走。
术语
为了方便描述,有必要统一一下术语。
packet、包与分组
“packet”可以一般地译为数据包,也可以特别地译为网络帧,为了不产生歧义,这里在描述宏观原理时译为[网络分组],简称为分组,特指分组交换网络(packet switching)原理中两个网络节点间传输的数据包格式;在描述微观原理时简单的使用一个字,包。如收包队列,发包过程。
什么是packet?
TCP/IP协议规定,在因特网传输的消息(message)必须打包成一个个叫分组(packets)的数据包进行传输,这些分组数据从一个主机到另一个主机,直到它来到它的目标主机为止。分组网络的主要原理是将每一次通信的消息分解为数个大小相同的分组包,这些分组包单独地被发到网络上,经过网络路由到达目标主机后再组装为原来的消息。由于分组的路由线路不是固定的,当网络部分出现故障时,分组仍然可通过可用的线路到达目标主机。
[网络分组]的长度一般为1K,因而,比较大数据可能需要分解为数千个分组。每个分组都有一个“头”,包括以下一些信息:
* Source. 源主机IP地址
* Destination. 目标主机IP地址
* Length. 分组长度,以字节为单位
* Number. 分组数,此分组所属的消息的长度
* Sequence. 分组序号,此分组在所属的消息的序号,目标主机或路由器在传送出错时可根据此序号要求原主机单独重发分组。
收包与发包过程
本文只关注二三层的数据交互,所以[收包过程]与[发包过程]指一层到三层或反方向止。
入列与出列
分别对[CPU的收包队列]和[网络设备的发包队列]的两个操作——enqueue和dequeue的中译。
收包过程
对内核(设备驱动)而言,IP网络分组(packet)的到来是没法预知的,因此网络设备驱动必须使用中断处理函数来接收IP网络分组。每当网卡设备收到一个分组时,它向内核发出中断请求,内核调用相应的中断处理函数,将网卡设备上的分组数据拷入RAM[注]。
注:在具体的实现上,例如PCI以太网卡,驱动程序会与网卡芯片[协作]将收到的分组数据保存在RAM中一个DMAble的缓冲区上,再转拷进协议栈可访问的socket buffer,DMAble缓冲区是在网络设备驱动初始化的分配的,可以理解为逻辑设备的一部分,所以上面的“网卡设备上的分组数据”在这个前提下成立。
目前版本的内核(的网络子系统)实现了两种[收包]算法。其中一种已经存在内核中很长一段时间了,我们称其为传统算法。传统算法的大致原理是,网络设备的分组的异步收与发统一使用中断方式,一次中断一个分组;CPU和网络设备之间使用[等待队列]缓解二者速度上的差异,并作相应的拥塞管理——网络设备的启停,防止队列溢出。
由于传统算法跟不上网络设备的发展&#x