以snull为例分析linux网络驱动程序的技术文档
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
以snull为例分析linux网卡驱动的技术文档
网络设备,即网络接口,在操作系统核心级上处理包的发送和接收。与块设备一样,网络接口也在特定的数据结构之中注册自己,以利于在跟外界进行包交换的时候被调用;但是它不象块设备一样存在于文件系统当中。二者最主要的区别在于:块设备是收到要求,才向内核发送一个块缓冲区的内容;网络接口是主动向内核推入从接口进入的包。Linux核心的网络子系统,在设计的时候是完全独立于协议的,对网络协议(如IP对IPX或其它)和硬件协议(以太网对令牌环等等)都是一样的。一个网络接口的驱动和内核的交互是一次处理一个网络包,这样就可以让协议问题巧妙的隐藏在驱动后面,也可以让物理上的传输隐藏在协议后面。
在下面的讲述中,我们将以一个基于内存的(即纯软件的)模块化网络接口,SNULL,来作为示例。为了简化讨论,我们让snull使用以太网协议并传输IP包。
How snull Is Designed
SNULL的设计
snull模块有两个接口,并且不同于loopback接口,它看起来就像两个外部的连接,但实际上是依赖于一个计算机本身。我们把snull指定上IP地址,这样并不影响通用性的编码,只是在示例的时候比较方便。两个接口sn0和sn1对应两个C类网络snullnet0和snullnet1,local0和local1是对sn0和sn1接口指定的IP地址,而remote0和remote1分别是snullnet0和snullnet1网络中的两个主机。我们在/etc/networks文件中加入:
snullnet0 192.168.0.0
snullnet1 192.168.1.0
在/etc/hosts文件中加入:
192.168.0.88 local0
192.168.0.99 remote0
192.168.1.99 local1
192.168.1.88 remote1
并使用下面的命令建立路由信息:
# ifconfig sn0 local0
# route add -net snullnet0 netmask 255.255.255.0 sn0
# ifconfig sn1 local1
# route add -net snullnet1 netmask 255.255.255.0 sn1
由于Linux的内核是不会把一个包从一个接口直接传送到本机另一个接口的,因此在实现上采用了一些技巧,就是在传输数据的过程中修改源和目的地址。换句话说就是让发出的包被本地的另一个接口所接收,但是接收接口由不被认为是本地的。具体的做法就是修改IP地址,把目标地址的第三个字节置反,那么发往remote0(192.168.0.99)的包就变成了去往local1(192.168.1.99)的包,回到了本机的另一个接口。这样我们就可以让remote端的接口变得可达,也显示了怎样让通过snull到达remote0和remote1.
The Physical Transprot of Packets
包的物理传输
就数据的传输过程来说,snull接口应该算作以太网一类的,示例代码也使用了内核对
以太网的支持。使用以太网模型建立snull首先是因为以太网设备太为通用了。其次snull 也可以在接口上运行tcpdump,当然,运行tcpdump的话,接口就要叫做ethx,而不是snx。snull模块已经准备好将自己声明为ethx. 如果在insmod命令中指定eth=1,则该功能就被选中。如果忘了给snull起一个eth的名字,tcpdump就会拒绝转储接口,并返回一个unknown physical layer type 的错误。
在实际中,snull的代码还会对包进行侦听甚至修改它,因为这是要求代码做的。snull 的代码会修改每个包的IP包头中的源、目的和校验和,但不检查这个包是否真正的包含IP 信息。这种修改方式捣毁非IP包。
Connecting to the Kernel
连接内核
我们通过剖析snull的源程序来看看网络驱动的结构。同时参看一些驱动程序的源码,有助于下面的讨论。内核的驱动程序,由易到难,可以参看loopback.c, plip.c, 3c509.c; 可以作为示例代码还有skeleton.c,尽管它并不能真正运行。3c59x.c和tulip.c是pci的并运用DMA 的例子。
Module Loading
模块加载
当一个驱动模块载入运行的内核中的时候,它需要申请资源并向核心提供调用接口。申请资源并没有什么特别的,驱动要探测它的设备和硬件的位置(I/O端口和IRQ中断请求号),但是并不进行登记。一个网络驱动的登记是通过它的init_module 函数完成的,这一点不同于字符设备和块设备的驱动程序,不同于取得一个设备描述符或者文件描述符,对于网络设备,有一张全局的网络设备列表,驱动程序对每一个新探测到的接口都会在该表中插入一个数据结构。
每个接口由一个struct device 项来描述。两个snull 接口sn0 和sn1 的结构声明如下:
char snull_names[16]; /* 2 8-byte buffers */
struct device snull_devs[2] = {
{
snull_name, /* name -- set at load time */
0, 0, 0, 0, /* shmem addresses */
0x000, /* ioport */
0, /* irq line */
0, 0, 0, /* various flags, init to 0 */
NULL, /* next ptr */
snull_init, /* init function, fill other fields with NULLs */
},
{
snull_name+8, /* name -- set at load time */
0, 0, 0, 0, /* shmem addresses */
0x000, /* ioport */
0, /* irq line */
0, 0, 0, /* various flags, init to 0 */
NULL, /* next ptr */