免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 5558 | 回复: 6
打印 上一主题 下一主题

[转]网络处理的软中断机制分析 [复制链接]

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-02-17 17:47 |只看该作者 |倒序浏览
网络处理的软中断机制分析
if !supportLists]-->内核默认软中断机制分析(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函数并且进一步向上层协议递交和处理。至此,内核的软中断的主要过程已经结束了。

评分

参与人数 1可用积分 +15 收起 理由
yidou + 15 hao wen

查看全部评分

论坛徽章:
36
IT运维版块每日发帖之星
日期:2016-04-10 06:20:00IT运维版块每日发帖之星
日期:2016-04-16 06:20:0015-16赛季CBA联赛之广东
日期:2016-04-16 19:59:32IT运维版块每日发帖之星
日期:2016-04-18 06:20:00IT运维版块每日发帖之星
日期:2016-04-19 06:20:00每日论坛发贴之星
日期:2016-04-19 06:20:00IT运维版块每日发帖之星
日期:2016-04-25 06:20:00IT运维版块每日发帖之星
日期:2016-05-06 06:20:00IT运维版块每日发帖之星
日期:2016-05-08 06:20:00IT运维版块每日发帖之星
日期:2016-05-13 06:20:00IT运维版块每日发帖之星
日期:2016-05-28 06:20:00每日论坛发贴之星
日期:2016-05-28 06:20:00
2 [报告]
发表于 2009-02-17 20:21 |只看该作者
好文章。正好借着文章好好了解一下网络方面的中断机制。

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
3 [报告]
发表于 2009-02-17 20:41 |只看该作者

回复 #2 Godbach 的帖子

呵呵,好文章与大家一起分享:wink:

论坛徽章:
0
4 [报告]
发表于 2009-02-18 17:33 |只看该作者
不错,源码版也有一篇讲网络在链路层处理的精华文章,也很不错。

论坛徽章:
0
5 [报告]
发表于 2009-03-13 11:03 |只看该作者
这个是2.4内核的

论坛徽章:
0
6 [报告]
发表于 2009-03-13 14:51 |只看该作者
看了这段讲解,有些要补充的是:
>>poll默认为process_backlog
实际上就是对核心队列的poll.

但事实上一个数据包从驱动过来的路径并不一定经过核心队列.
如果驱动自己维护一个接受队列,则通用的方法应该是,在接受中断程序里加入dev的poll.
这样中断返回时就调用到这个poll,这个poll里可能直接调用到netif_receive_skb,而无需把skb丢给核心的队列,一直运行到上层协议或发送到需要交换设备的发送队列中,整个都是在一个软中断下运行,是不可打断的,我现在在想那些防火墙策略等等所以的代码全部都在这个不可打断的流程中,是不是太霸道了?

论坛徽章:
0
7 [报告]
发表于 2012-08-16 15:04 |只看该作者
这个要学习下
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP