关键字 RTL8139;网卡; 驱动程序;收包过程;发包过程
正文
一、 前言
RTL8139 可能是目前最受欢迎的网卡,它的价格便宜,功能上也还能接受。虽然在效能上有时会略不及Intel 的 eepro100,但因为价格实在太便宜了,所以芯片上的一点小问题通常也就忽略不计。8139 虽然价格不高,但该有的功能一点也不缺。它内建了符合 MII 规格的 tranceiver,可以自动判断连接的网络是那一种型态。它也可以使用 DMA 直接使用位于主记忆体的缓存区来存储网络上接收的封包,同样的,待传送的封包也可利用 DMA 传送到网卡上。所以虽然在 8139 芯片上只有 2K 的接收缓冲区和 2K 的传送缓冲区,其效能仍十分不错。
除了 realtek 本身外,有不少的厂商也使用相同的内核生产了和 8139 相容的网络芯片,包括了SMC 1211 ,MPX 5030 ,DELTA 8139 ,ADDTRON 8139 ,DFE 538 可能还有更多。
所以,掌握RTL8139网卡的驱动编程技术是很重要的,这对于编写其他网卡的驱动也是个良好的基础。目前,RTL8139网卡的驱动在Linux下有公开的源代码,但是在Windows下还没有,毕竟微软还不乐意做这种善事。下面将要讨论的就是我在Windows2000环境下编译通过且能实际使用的RTL8139网卡驱动的源码中最重要的两部分—收包过程和发包过程。
二、 网卡的发包和收包过程
网卡也叫“网络适配器”,英文全称为“Network Interface Card”,简称“NIC”,网卡是局域网中最基本的部件之一,它是连接计算机与网络的硬件设备。无论是双绞线连接、同轴电缆连接还是光纤连接,都必须借助于网卡才能实现数据的通信。
网卡的主要工作原理是整理计算机上发往网线上的数据,并将数据分解为适当大小的数据包之后向网络上发送出去。当网络上一台计算机准备发送数据时,他的网卡开始工作了,首先网卡的芯片侦听在网络上是否有数据在流动,如果没有,他就把数据发送到网络上,在发送完成后再校验返回的数据,检验和发送的数据是否一致。对于全双工网卡,计算机的网卡芯片同时会接收到数据包,当接收到一个完整的数据包后,芯片的一引脚通知中断控制器,中断控制器再发出中断给CPU,由此,CPU随即调用该网卡的中断例程。从而完成收发包的过程。
每个NDIS_PACKET包表示一个可以由网卡发送的数据包。也就是说,这个包已经完全被上层封装好了,就差加上一个MAC头就可以发了(当然这个MAC头是网卡发送时自动加上的)。NDIS_PACKET要发送的数据是由一个链表结构的NDIS_BUFFER组成。之所以要这样来封装,是因为每经过一层协议的时候,该层的协议都要在数据的包头加上自己的信息,采用链表结构,只需要在原来链表的表头加上自己的信息,不需要再拷贝任何数据,就能够把包往下传给下一层驱动了,节约了时间和空间。
2.1 发包的过程:
要预先把发送的数据拷贝到一个物理连续的缓冲区里,然后把缓冲区的物理地址传递给网卡,启动网卡传输,网卡就用DMA方式把数据发送出去。发送成功后给出一个中断,表示发送完成。
具体细节:
1.)获取到一个NDIS_PACKET Packet包以后,使用
NdisQueryPacket(
Packet,
&PhysicalBufferCount,
&BufferCount,
&pFirstBuffer,
&TotalPacketLength);
获取整个包要发送的数据长度,就是TotalPacketLength里返回值。得到的返回值可以用来分配物理连续的缓冲区。
2.)把数据拷贝到刚才分配的物理连续的缓冲区里。循环使用
NdisQueryBufferSafe(
pCurrBuff,
&pVirtualAddress,
&Length,
NormalPagePriority);
获取NDIS_PACKET Packet包链表结构里的每一个NDIS_BUFFER pCurrBuff的线性地址(linear address),然后使用这些线性地址,
NdisMoveMemory(
(PVOID) ((ULONG) 物理连续的缓冲区,
(PVOID) pVirtualAddress,
(ULONG) Length);
把包拷贝到发送的缓冲区里。
3.)在物理连续的缓冲区有了一个完整的包以后,启动网卡DMA传输,开始传送。
2.2 收包的过程:
网卡接受到数据以后,就产生一个中断,在这个中断的DPC里,调用
NdisMIndicateReceivePacket(
pRtlAdapter->MiniportAdapterHandle,
RevPacket,
NumOfPkt
);
告诉系统收到了NumOfPkt个数据包了,RevPacket是收到的包的数组。
1.)在网卡驱动初始化的时候,需要分配一个Ring Buffer,这个Ring Buffer是给网卡接受数据使用的,由于是给网卡的DMA传输数据用的,所以Ring Buffer必须要是一个物理连续的内存。Ring Buffer顾名思义,是一个环状的缓冲区,当使用到缓冲区的尽头时,又重新从Buffer的开始循环使用。接受到的数据包刚开始就是被连续放在这个环状缓冲区里。
2.)当收到一个完好的数据包(就是Ring Buffer包含了完好的数据包),网卡发出中断,指出有数据包到来。然后,所有的处理都是在DPC里完成的。
每个收到的包都包含了如下的网卡额外加入的信息头:
typedef struct tagPACKETHEADER{
USHORT ROK : 1; //接受到一个好的数据包。
USHORT FAE : 1; //帧对齐错误。
USHORT CRC : 1; //CRC校验错误。
USHORT LONG: 1; //一个超过4KBytes的包。
USHORT RUNT: 1; //一个小于64Bytes的包。
USHORT ISE : 1;
USHORT reserved : 7;
USHORT BAR : 1; //收到一个广播包。
USHORT PAM : 1; //收到发给自己的包。
USHORT MAR : 1; //收到一个组播包。
USHORT PacketLength; //很重要,这个长度是数据包长度+网卡加入的4个字节CRC。
}PACKETHEADER, *PPACKETHEADER;
有了这些信息,先判断包是否是好的,如果是好的,那么创建一个NDIS_PACKET Packet包,然后,把长度为(PacketLength-4)个字节的数据从接受Ring Buffer里拷贝出来,然后,调用NdisMIndicateReceivePacket通知系统,收到了包。
如果这个包是坏包,那么从新置位Ring Buffer缓冲区。这个做法很重要,不然后面收到的包就没法保证正确了。
2.3 注意事项:
1.)在收包的时候,如果采用事先分配的Packet,那么这个Packet必须调用:
NDIS_SET_PACKET_HEADER_SIZE(Packet,ETHERNET_HEADER_SIZE);
不然,收到的数据包MAC层的协议内容会被拷贝到上层的内容里,造成错误。
2.)在资源分配之前,需要调用NdisMSetAttributesEx函数,否则会得出资源分配失败。
3.)从Ring Buffer拷贝数据出来,拷贝的起始(read_ptr)地址是由我们的驱动来维护的,所以,应该尽量小心维护这个指针。
4.)MyRtlQueryInformation和MyRtlSetInformation都只是系统获取驱动支持的特性的手段,对硬件的操作很少,不需要很在意。
5.)数据包接收的算法直接影响到驱动的效率,应该尽量优化。
6.)在编写收包和发包的过程中,可以使用Sniffer工具来获取包,验证是否确实有包被发送或接收。
三、 结束语
随着互联网的高速发展,各种网卡包括高速网卡驱动的编程日益显示出其重要性。而网卡驱动程序的编写最主要的就是要掌握三个部分:初始化,发包过程,收包过程。其中后两部分尤为重要。本文结合自己的编程实际,对RTL8139网卡的驱动程序收发包部分进行了深入分析,由于网卡驱动的相通性,本文对于其他网卡的驱动编程也具有一定的参考价值。