每个网络驱动程序都提供了一系列非常实用的函数,这些函数都是底层的基本的函数;
每个设备还包含了一组标准的例程,协议层可以将这些例程当作设备链路层的部分而调用。关于这些函数和例程,下面我们详细介绍。
1.初始化设置(init)
init 函数在设备初始化和注册时被调用,它执行的是底层的确认和检查工作。在
初始化程序里你可以完成对硬件资源的配置。如果设备没有就绪或设备不能注册或其他任何原因而导致初始化工作不能正常进行,该函数就返回出错信息。一旦初始化函数返回出错信息,register_netdev()也返回出错信息,这样该设备就不能安装。
2.打开 ( open)
open 这个函数在网络设备驱动程序里是网络设备被激活的时候被调用(即设备状 态由down-->up)。所以实际上很多在 init 中的工作可以放到这里来做。比如资源的申请,硬件的激活。如果dev->open返回非零(error),则硬件的状态还是down。 open函数另一个作用是如果驱动程序做为一个模块被装入,则要防止模块卸载时设备处于打开状态。 在open方法里要调用MOD_INC_USE_COUNT宏。
3. 关闭 ( stop)
close 函数做和
open 函数相反的工作。可以释放某些资源以减少系统负担。close是在设备状态由up转为down时被调用的。另外如果是做为模块装入的驱动程序,close里应调用MOD_DEC_USE_COUNT, 减少设备被引用的次数
, 以使驱动程序可以被卸载。 另外close方法必须返回成功(0==success)。
4.数据帧传输例程
所有的设备驱动程序都必须提供传输例程,如果一个设备不能传输,也就没有存在的必要性。事实上,设备的所谓的传输仅仅是释放传送给它的缓冲区,而真正实现传输功能是虚拟设备。
dev->hard_start_xmit(): 该函数的功能是将网络缓冲区,也就是sk_buff 发送到硬件设备。如果设备不能接受缓冲区,它就会返回1,并置 dev->tbusy
为非零值。这样缓冲区就排成队列,等待着dev->tbusy置零以后会再次发送。如果协议层决定释放被设备抛弃的缓冲区,那么缓冲区就不会再被送回设备;如果设备知道缓冲区短时间内不被能传送,例如设备严重堵塞,那么它就调用 dev_kfree_skb()函数丢掉缓冲区,该函数返回零值标明缓冲区已经被处理完毕。
当缓冲区被传送到硬件以后,硬件应答信号标识传输已经完毕, 驱动程序必须调用dev_kfree_skb(skb,
FREE_WRITE)函数释放缓冲区,一旦该调用结束,缓冲区就会很自然地消失,这样,驱动程序就不能再涉及缓冲区了。
该函数传送下来的sk_buff中的数据已经包含硬件需要的帧头。所以在发送方法里不需要再填充硬件帧头,数据可以直接提交给硬件发送。sk_buff是被锁住的(locked),确保其他程序不会存取它。
5.硬件帧头
前面我们讲过,数据帧在传送之前先要排成对列,在加入队列之前,还要在每个数据帧的开始添加硬件帧头,这项工作对于数据传送非常必要。网络设备驱动程序提供了一个dev->hard_header( ) 例程,来完成添加硬件帧头的工作。协议层在发送数据之前会在缓冲区的开始留下至少 dev->hard_header_len 长度字节的空闲空间。这样dev->hard_header( ) 程序只要调用 skb_push(),然后正确填入硬件帧头就可以了。
调用这个例程需要给出和缓冲区相关的信息:设备指针、协议类型、指向源地址和目标地址(指硬件地址)的指针、数据包的长度。因为这个例程是在协议层发送函数触发之前被调用,所以一个非常重要参数值得我们注意:在这个例程中用的是 length 参数,而不是用缓冲区的长度做参数,因为调用dev->hard_header( )时数据可能还没完全组织好。
源地址可以为“NULL”,这意味着“使用默认地址”;目标地址也可以为“NULL”,这意味着“目标未知”。
如果目标地址“未知”,数据帧头的操作就不能完成,本来为硬件帧头预留的空间全部被其他信息占用,那么函数就返回填充硬件帧头空间的字节数的相反数(一定为负数)。当硬件帧头完全建立以后,函数返回所添加的数据帧头的字节数。
如果一个硬件帧头不能够完全建立,协议层必须试图解决地址问题,因为硬件地址对于数据的发送是必需的。一旦这种情况发生, dev->rebuild_header( )
函数就会被调用,通常是利用ARP(地址解析协议)来完成。如果硬件帧头还不能被解决,该函数就返回零,并且会再次尝试,协议层总是相信硬件帧头的解决是可能的。
6.数据接收
网络设备驱动程序没有关于接收的处理,当数据到来时,总是驱动程序通知系统。对一个典型的网络设备,当它收到数据后都会产生一个中断,中断处理程序调用
dev_alloc_skb(),申请一个大小合适的缓冲区(sk_buff),把从硬件传来的数据放入缓冲区。接着,设备驱动程序分析数据包的类型,把 skb->dev设置为接收数据的设备类型 ,把 skb->protocol 设置为数据帧描述的协议类型,这样,数据帧就可以被发送到正确的协议层。硬件帧头指针保存在 skb->mac.raw中,并且硬件帧头通过调用skb_pull()被去掉,因此网络协议就不涉及硬件的信息。最后 还要设置skb->pkt_type,标明链路层数据类型,设备驱动程序必须按以下类型设置 skb->pkt_type :
PACKET_BROADCAST 链接层广播地址
PACKET_MULTICAST 链接层多路地址
PACKET_SELF 发给自己的数据帧
PACKET_OTHERHOST 发向另一个主机的数据帧 (监听模式时会有到)
最后,设备驱动程序调用 netif_rx( )(见图
在协议层,接收数据包的流程控制分两个层次: 首先,netif_rx()函数限制了从物理层到协议层的数据帧的数量。第二,每一个套接字都有一个队列,限制从协议层到套接字层的数据帧的数量。在传输方面,驱动程序的 dev->tx_queue_len 参数用来限制队列的长度。队列的长度通常是100帧,在进行大量数据传输的高速连接中,它足以容纳下所有等待传输的缓冲区,不会出现大量缓冲区阻塞的情况。在低速连接中,例如Slip 连接,队列的长度长设为10帧左右,因为传输10帧的数据就要花费数秒的时间排列数据。
12.6 本章小结
本章的主要目的是介绍Linux操作系统网络部分的基本工作原理。因为Linux支持多种协议类型和多种网络设备,加上网络部分本身就比较复杂,所以本章所涉及的内容十分有限。为了便于说明,采用了以点带面的方法,着重介绍了网络部分的四个核心对象。
本章首先介绍了Linux网络部分源代码的面向对象设计思想,指出了四个核心对象:
·网络协议
·套接字
·套接字缓冲区
·网络设备接口
随后,用面向对象的分析方法,分别介绍了这四个核心对象的相关内容和它们之间的关系,这对于理解Linux网络的工作原理有很大帮助。
关于内容,本章尽可能详细具体,贴近实际应用,对于和实际应用有关系的地方都做了深入介绍。如果读者要做网络编程,请参考12.2网络协议,12.3套接字,12.4套接字缓冲区三节;如果要编写网络驱动程序,请参考12.4套接字缓冲区和12.5网络设备接口两节。