----报文接收(续)
3.下半部处理
Net_rx_action是软中断NET_RX_SOFTIRQ的处理函数。处理函数执行时,帧有可能在两个地方:CPU的入报文队列,设备内存中。前者对应应用老接口的驱动,后者则是采用了NAPI接口的驱动的情形。Net_rx_action的处理比较简单:对当前cpu上poll_list队列上的每个设备,调用它的poll函数。下面分析一下主要的流程:
流程较为简单,注意几个地方:1)退出时机:本次处理的报文达到了配额;执行的时间超过了一个jiffies;完全处理完全部的帧;2)如果dev处理完了全部的帧,则要自己把dev从cpu的队列中删除,如果是老接口,则不用担心,process_backlog会处理;但是如果NAPI接口,则需要自己handle了。
另外一个地方,程序中的budget管理是必须的,一个明显的原因是帧的处理过程中是中断使能的,这当中可能有新的帧到达,不能任由该函数无限制地处理下去。当然每一个驱动的处理函数也必须遵守规则,不能一次处理过多的帧,甚至任意改动自己的quota或者传入的budget以求得到更多的执行机会。
NAPI稍后用一个例子来看一下,先看一下老接口的代理人:process_backlog。这个函数也是非常简单,遍历当前cpu的队列:input_pkt_queue,找到报文的dev,然后呼叫:netif_receive_skb(skb)。中断也只是在操作input_pkt_queue时禁止,至于权重,队列操作在上面代码分析中已经列出,还需要关注的可能是dev的状态,但是我还不是很熟悉。再来看一下netif_receive_skb函数。
绕了这么个大弯,才真正进到处理报文的函数。处理报文必须要知道报文的格式,也就是所遵守的协议。二层,三层的协议一大把。但是每一个网络接口知道自己应用的网络协议,这样他的驱动就能理解他首先接到的帧的二层协议,上面提到老接口在中断处理函数中就会初始化sk_buff中的protocol字段,这样netif_receive_skb就知道该用什么协议的处理函数来处理该帧了。在该函数中,完成的任务有三项:1)将帧的拷贝,传给任何感兴趣的协议模块;2)将帧的拷贝传递到sk_buff->protocol对应的协议模块;3)需要在二层完成的工作也必须做完,因为马上就要进入路由了。来看源码,是怎样完成这些任务的。
源码里面有些细节不是很好理解,因为不是很能理解应用的意义,暂且记下,往后碰到了再来查看。首先处理bond特性(不知道这种特性的应用场景,开始以为和trunk类似,但是想想又不对),接下来初始化网络层和传输层头的位置偏移,并利用在eth_type_trans中初始化的mac层偏移计算出二层的报文头的长度(为什么不在eth_type_trans中直接计算呢?因为在eth_type_trans中仅仅做一些简单的辨认工作,二层协议即使是以太协议也有很多变化,不宜在中断环境中做太多的工作,并且在netif_receive_skb处理后马上进入三层,这里计算也很合理)。然后是加锁,这里应用了一种新锁--RCU机制(详细的请款在IBM developer上有一篇很不错的文章:http://www.ibm.com/developerworks/cn/linux/l-rcu/),防止对协议注册链表的写入竞争。接下来的一个处理比较有意思:sniffer,这里给注册在ptype_all上的所有合适的协议模块发送了一份报文的拷贝,这应该网络监听之类软件起作用的主要原因了吧。因为现在二层的特性不是我关注的特点,暂且掠过响应的处理。最后是找到和报文对应的注册协议,并也发送一份拷贝过去(书上说是发送了一份拷贝,但是代码里确实是直接传来一个指针,莫非协议的注册函数做了什么拷贝工作,后面注意一下),这样报文就能被正确的协议模块所处理了。那么,报文又是如何找到对应的协议模块,内核是如何管理的呢?下一篇文章学习一下。
在进入第三层处理之前,还有一个问题:NAPI接口。
前面提到了NAPI接口没有使用backlog设备,并且中断处理函数也是异常简单,仅仅是禁止了本设备的中断而已,下面结合具体的驱动代码看看这个新的接口到底是怎么工作的,有可能的话,可以分析一下这样做有什么好处。
还是来看e100.c中的源码。上面说了他的中断处理函数,来看一下他的poll函数,里面呼叫e100_rx_clean来进行接收工作,里面又呼叫了e100_rx_indicate,这里面才真正呼叫netif_receive_skb,由他来进行完成最后的工作----和老接口是殊途同归!
个人感觉只要理解了接口的含义和工作的机制就可以,真正写驱动的时候和硬件的关系非常大,硬件提供了哪些功能,综合系统提供的机制,才能写出强大,高效率的驱动。单纯这样去研究一个硬件的驱动没多少意义,而且很多和特定硬件相关的东西也不会看得懂,这也是我在上面尽量避开特定硬件的原因,比如e100.c里面的代码和硬件关系非常大,觉得没多少必要弄清楚细节。
这样接收报文方面和底层相关的东西基本弄完了,发送报文的机制缓一缓,先来看一下内核对协议模块的管理和ipv4协议的一些实现,到时候再来看一下发送。
上面的东西总的来说还是比较简单的,机制的设计还是比较清晰明了的。但是自己还是花了不少时间,一部分原因是总结不及时,花费了很多不必要的时间。学习这些东西不是我的最终目的,真是希望能有一个适当的项目能够参与一下,在实践中提高才是真理!