Linux网络系统底层机制分析(3)----报文接收(续)

  Linux网络系统底层机制分析(3)

                 ----报文接收(续)

3.下半部处理

Net_rx_action是软中断NET_RX_SOFTIRQ的处理函数。处理函数执行时,帧有可能在两个地方:CPU的入报文队列,设备内存中。前者对应应用老接口的驱动,后者则是采用了NAPI接口的驱动的情形。Net_rx_action的处理比较简单:对当前cpu上poll_list队列上的每个设备,调用它的poll函数。下面分析一下主要的流程:

  static void net_rx_action(struct softirq_action *h)
{
struct softnet_data *queue = &__get_cpu_var(softnet_data);
     unsigned long start_time = jiffies;
    int budget = netdev_budget;
  void *have;
/*只是在操作本cpu的队列的时候才加锁加以保护*/
  local_irq_disable();
  while (!list_empty(&queue->poll_list)) {
    struct net_device *dev;
/*如果此次用光了配额或者处理的时间超过了一个jiffe,那么再次触发NET_RX_SOFTIRQ,本次退出*/
    if (budget <= 0 || jiffies - start_time > 1)
      goto softnet_break;
    local_irq_enable();
    dev = list_entry(queue->poll_list.next,
         struct net_device, poll_list);
    have = netpoll_poll_lock(dev);/*这句话没看懂是什么意思,似乎是防止SMP上某个设备的poll函数出现竞争的情况*/
    if (dev->quota <= 0 || dev->poll(dev, &budget)) {
      netpoll_poll_unlock(have);
      local_irq_disable();
      list_move_tail(&dev->poll_list, &queue->poll_list);/*将此dev移到队列的末尾*/
/*更新dev的配额*/
/*观察老接口的poll函数--process_backlog的处理可知,在没有处理完全部的帧时,process_backlog也会把自己的当前的份额减去处理完的个数,此时, net_rx_action又会把它加上设备的权重,也算是一种调度吧。不过如果每个驱动都是一个内核的好公民,那么dev->quota应该不会小于零的*/
      if (dev->quota < 0)
        dev->quota += dev->weight;
      else
        dev->quota = dev->weight;
    } else {
/* 进入到else意味着:dev的配额仍然大于0,并且poll完成了全部的工作。此时,dev的poll函数必须自己把自己从cpu的poll_list中删除*/
      netpoll_poll_unlock(have);
      dev_put(dev);
      local_irq_disable();
    }
  }
out:
  local_irq_enable();
....
  return;
softnet_break:
  __get_cpu_var(netdev_rx_stat).time_squeeze++;
  __raise_softirq_irqoff(NET_RX_SOFTIRQ);
  goto out;
}

流程较为简单,注意几个地方: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协议的一些实现,到时候再来看一下发送。

上面的东西总的来说还是比较简单的,机制的设计还是比较清晰明了的。但是自己还是花了不少时间,一部分原因是总结不及时,花费了很多不必要的时间。学习这些东西不是我的最终目的,真是希望能有一个适当的项目能够参与一下,在实践中提高才是真理!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值