网络处理的软中断机制分析

http://blog.csdn.net/joshua_yu/archive/2006/01/27/589451.aspx


内核默认软中断机制分析( process_backlog

首先需要介绍的就是 netif_rx (在 net/core/dev.c 中定义)函数,这个函数在网卡驱动程序与 linux 内核之间建立了一道桥梁,将网卡接收上来的数据包( sk_buff 形式)插入内核维护的接收缓冲 区队列当中:

int netif_rx(struct sk_buff *skb)

{

       int this_cpu = smp_processor_id();

       struct softnet_data *queue;

       unsigned long flags;

 

       if (skb->stamp.tv_sec == 0)

              do_gettimeofday(&skb->stamp);

       /*

       获取当前处理 CPU 的接收数据包缓冲区队列指针。系统为 每一个 CPU 都维 护一个独立的列表,这样可以避免共享访问互斥问题。

       */

       queue = &softnet_data[this_cpu];

 

       local_irq_save(flags);

 

       netdev_rx_stat[this_cpu].total++;

       /*

       这里判断当前输入队列 的长度是否超过预定义的一个值,如果没有超过,则向下执行。

       如果当前队列的长度大 于 0 ,则将 sk_buff 插入队列,并且返回,注意这 里并没有调用 __cpu_raise_softirq 产生一个软中断,而较老的内核版本当中,插入队列以后立刻调用这个函数产生软中断。

       另外,如果队列长度为 0 ,则需要调用 netif_rx_schedule 及后续的 __netif_rx_schedule 函 数将当前网络设备加入 softnet_data 的轮询列表( poll_list )当中,这个列表维护所有网络设备的列表,当系统软中断处理函数运行时,逐个检索处于 poll_list 中的设备,然后调用设备 的 dev->poll 方法处理输入数据包队列中的数据。

       将设备加入 poll_list 列表当中后,返回 enqueue 标记处继续将 sk_buff 加入输入数据包队列中,然后 返回。

       */

       if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {

              if (queue->input_pkt_queue.qlen) {

                     if (queue->throttle)

                            goto drop;

 

enqueue:

                     dev_hold(skb->dev);

                     /*

                     将当前 sk_buff 插入 input_pkt_queue 队列的尾部,即刻返回。

                     */

                     __skb_queue_tail(&queue->input_pkt_queue,skb);

                     local_irq_restore(flags);

                     return queue->cng_level;

              }

 

              if (queue->throttle) {

                     queue->throttle = 0;

              }

 

              netif_rx_schedule(&queue->blog_dev);

              goto enqueue;

       }

 

       if (queue->throttle == 0) {

              queue->throttle = 1;

              netdev_rx_stat[this_cpu].throttled++;

       }

 

drop:

       netdev_rx_stat[this_cpu].dropped++;

       local_irq_restore(flags);

 

       kfree_skb(skb);

       return NET_RX_DROP;

}

 

从上面的分析可以知道, netif_rx 函数主要负责将数据包插入内核队列中,并触发软中断,这一点与较早的版本是不同的,那么软中断是在 什么地方触发的呢?

以前的章节介绍过,硬件中断是在 irq.c do_IRQ 函数中调用 handle_IRQ_event 函数,进而调用相应硬件驱动程序的中断处理函数实现的。在 do_IRQ 函数执行完硬件处理函数以后,接 着就会调用 do_softirq 函数执行软中断,并且根据软中断号在 softirq_vec 数组中查找相应中断的 action 方法,对于 NET_RX_SOFTIRQ 类型的软中断来说,系统将其 action 注册为 net_rx_action ,这样我们就进入了 net_rx_action 函数当中:

static void net_rx_action(struct softirq_action *h)

{

       int this_cpu = smp_processor_id();

       struct softnet_data *queue = &softnet_data[this_cpu];

       unsigned long start_time = jiffies;

       int budget = netdev_max_backlog; (系统每次从队列中取 300 sk_buff 处理)

 

       br_read_lock(BR_NETPROTO_LOCK);

       local_irq_disable();

      

       /*

       在这里循环判断当前轮 询列表是否为空。如果不为空,则进入软中断处理过程。

       */

       while (!list_empty(&queue->poll_list)) {

              struct net_device *dev;

 

              if (budget <= 0 || jiffies - start_time > 1)

                     goto softnet_break;

 

              local_irq_enable();

              /*

              从轮询 列表中取出当前的设备 dev 指针,接着为 dev 调用 poll 方法,这是设备初始化过程中已经定义好的方法,如果设备未能自己实现该函数,则系统默认注册为 process_backlog poll 函数执行成功返回 0 ,失败返回- 1 。这里逻辑判断是, dev->quota 必须大于 0 ,而 poll 函数执行成功,则可以继续,直到所有 的设备都查询一遍为止。

              */

              dev = list_entry(queue->poll_list.next, struct net_device, poll_list);

 

              if (dev->quota <= 0 || dev->poll(dev, &budget)) {

                     /*

这里的 poll 函数是 netdevice 结构的一个函数指针,对于不同的网卡驱动程序,我们可以根据自己的情况定义 poll 方法的实现( e1000 网卡驱动程序就自己实现了一个 poll 方法,详情后面来分析),而系统默 认提供一个方法。

                     */

                     local_irq_disable();

                     list_del(&dev->poll_list);

                     list_add_tail(&dev->poll_list, &queue->poll_list);

                     if (dev->quota < 0)

                            dev->quota += dev->weight;

                     else

                            dev->quota = dev->weight;

              } else {

                     dev_put(dev);

                     local_irq_disable();

              }

       }

 

       local_irq_enable();

       br_read_unlock(BR_NETPROTO_LOCK);

       return;

 

softnet_break:

       netdev_rx_stat[this_cpu].time_squeeze++;

       /*

       触发软中断处理,等待 下一次调用本函数。

       */

       __cpu_raise_softirq(this_cpu, NET_RX_SOFTIRQ);

 

       local_irq_enable();

       br_read_unlock(BR_NETPROTO_LOCK);

}

 

软中断处理函数 net_rx_action 实际上就是调用各个网络设备的 poll 方法处理数据包的,一般的讲, poll 默认为 process_backlog (在 net/core/dev..c 中定义):

static int process_backlog(struct net_device *backlog_dev, int *budget)

{

       int work = 0;

       int quota = min(backlog_dev->quota, *budget);

       int this_cpu = smp_processor_id();

       struct softnet_data *queue = &softnet_data[this_cpu];

       unsigned long start_time = jiffies;

 

       for (;;) {

              struct sk_buff *skb;

              struct net_device *dev;

 

              local_irq_disable();

              /*

              从输入 缓冲区队列中取出 sk_buff ,调用 netif_receive_skb 函数将 sk_buff 交给上层协议进行处理。这里就是循环调用 __skb_dequeue 取出 skb ,直到所有的 skb 处理完毕为止。

              */

              skb = __skb_dequeue(&queue->input_pkt_queue);

              if (skb == NULL)

                     goto job_done;

              local_irq_enable();

 

              dev = skb->dev;

 

              netif_receive_skb(skb);

 

              dev_put(dev);

 

              work++;

 

              if (work >= quota || jiffies - start_time > 1)

                     break;

       }

}

 

接下来看一下 sk_buff 是如何被递交到上层协议进行处理的,只是通过调用 netif_receive_skb (在 net/core/dev.c 中定义)函数 实现的:

int netif_receive_skb(struct sk_buff *skb)

{

       struct packet_type *ptype, *pt_prev;

       int ret = NET_RX_DROP;

       unsigned short type = skb->protocol;

      

       /*

       给每个网络数据包打上 时间戳。

       */

       if (skb->stamp.tv_sec == 0)

              do_gettimeofday(&skb->stamp);

 

       skb_bond(skb);

 

       pt_prev = NULL;

       /*

       上层的每个协议在其初 始化的过程中会调用 dev_add_pack 函数将自己的 packet_type 结构加入到 ptye_all 列表当中,其中 packet_type 结构中定义了该协议的处理方法,对于 ip 协议来说, func 方法就注册为 ip_rcv 。另外,一般协议 packet_type 结构的 dev 字段设为 NULL ,所以下面的 ptype->dev 就为 NULL

       另外,如果我们需要增 加自己的协议,则需要创建一个 packet_type 结构,用我们自己的协议处理函数填充该结构的 func 方法,并且调用 dev_add_pack 函数将我们自己的协议加入 ptype_all 数组当中。

       */

       for (ptype = ptype_all; ptype; ptype = ptype->next) {

              /*

              这里每 一种协议在定义其 packet_type 结构时都设置接收这种

              数据包 协议类型的设备指针,如果设置为 NULL ,则可以从

任何设备接收数据包。

这里针对协议类型为 ETH_P_ALL 的情况进行处理,对于 IP

协议来说,类型定义为 ETH_P_IP ,因此不在这里处理。

              */

              if (!ptype->dev || ptype->dev == skb->dev) {

                     if (pt_prev) {

                            if (!pt_prev->data) {

                                   ret = deliver_to_old_ones(pt_prev, skb, 0);

                            } else {

                                   atomic_inc(&skb->users);

                                   ret = pt_prev->func(skb, skb->dev, pt_prev);

                            }

                     }

                     pt_prev = ptype;

              }

       }

       /*

这里针对各种协议进行处理, IP 包的类型为 ETH_P_IP ,因此在这里处理。

*/

       for (ptype=ptype_base[ntohs(type)&15];ptype;ptype=ptype->next) {

              if (ptype->type == type &&

                  (!ptype->dev || ptype->dev == skb->dev)) {

                     if (pt_prev) {

                            if (!pt_prev->data) {

                                   ret = deliver_to_old_ones(pt_prev, skb, 0);

                            } else {

                                   atomic_inc(&skb->users);

                                   ret = pt_prev->func(skb, skb->dev, pt_prev);

                            }

                     }

                     pt_prev = ptype;

              }

       }

 

       if (pt_prev) {

              if (!pt_prev->data) {

                     ret = deliver_to_old_ones(pt_prev, skb, 1);

              } else {

                     ret = pt_prev->func(skb, skb->dev, pt_prev);

              }

       } else {

              kfree_skb(skb);

              /* Jamal, now you will not able to escape explaining

               * me how you were going to use this. :-)

               */

              ret = NET_RX_DROP;

       }

 

       return ret;

}

       在软中断处理函数当中,我们根据数据包的类型,调用相应的底层数据处理函数,对于 IP 包来说,就是调用 ip_rcv 函数并且进一步向上层协议递交 和处理。至此,内核的软中断的主要过程已经结束了。

       接下来我们分析一下 IP 层以上的网络协议栈是如何进一步处理数据的,详细的说明文档见《 网络数据接收的协议处理过程分析 》。

你可能感兴趣的:(网络,list,struct,null,action,linux内核)