Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5703095
  • 博文数量: 675
  • 博客积分: 20301
  • 博客等级: 上将
  • 技术积分: 7671
  • 用 户 组: 普通用户
  • 注册时间: 2005-12-31 16:15
文章分类

全部博文(675)

文章存档

2012年(1)

2011年(20)

2010年(14)

2009年(63)

2008年(118)

2007年(141)

2006年(318)

分类: LINUX

2008-08-12 16:29:15

在看e1000驱动的时候,顺便看了一下NAPI,了解了一下NAPI的原理。
注意:分析的内核源代码为2.6.18

记得以前组成原理的课程上讲到过中断与轮询两种方式的IO方法,以为NAPI就是简单的轮询,直接一个while(1)循环,探测设备状态,读取数据。今天,跟踪代码,才发现实际上NAPI也是中断驱动的。是不是感觉有些怪异,中断驱动,那么跟传统的中断IO方法有什么区别呢?

传统中断IO
传统的中断IO方法,大家都比较熟悉,拿intel的千兆网卡举例来讲。网卡接收数据包,将数据包DMA到环形数据缓冲区后,触发中断,然后执行中断处理程序。中断处理程序,将数据包推向上层协议栈。
流程:
e1000_intr
e1000_clean_rx_irq
e1000_receive_skb
netif_rx

netif_rx:
1704int netif_rx(struct sk_buff *skb)
1705{
1706        struct softnet_data *queue;
1707        unsigned long flags;
1708
1709        /* if netpoll wants it, pretend we never saw it */
1710        if (netpoll_rx(skb))
1711                return NET_RX_DROP;
1712
1713        if (!skb->tstamp.tv64)
1714                net_timestamp(skb);
1715
1716        /*
1717         * The code is rearranged so that the path is the most
1718         * short when CPU is congested, but is still operating.
1719         */
1720        local_irq_save(flags);
1721        queue = &__get_cpu_var(softnet_data);
1722
1723        __get_cpu_var(netdev_rx_stat).total++;
1724        if (queue->input_pkt_queue.qlen <= netdev_max_backlog) {
1725                if (queue->input_pkt_queue.qlen) {
1726enqueue:
1727                        dev_hold(skb->dev);
1728                        __skb_queue_tail(&queue->input_pkt_queue, skb);
1729                        local_irq_restore(flags);
1730                        return NET_RX_SUCCESS;
1731                }
1732
1733                netif_rx_schedule(&queue->backlog_dev);
1734                goto enqueue;
1735        }
1736
1737        __get_cpu_var(netdev_rx_stat).dropped++;
1738        local_irq_restore(flags);
1739
1740        kfree_skb(skb);
1741        return NET_RX_DROP;
1742}

跟踪netif_rx_schedule函数:
netif_rx_schedule
__netif_rx_schedule
__raise_softirq_irqoff(NET_RX_SOFTIRQ);

这里触发了软件中断,NET_RX_SOFTIRQ在net_dev_init中初始化:
    open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
也就是说NET_RX_SOFTIRQ的软中断处理函数为net_rx_action。
1903static void net_rx_action(struct softirq_action *h)
1904{
1905        struct softnet_data *queue = &__get_cpu_var(softnet_data);
1906        unsigned long start_time = jiffies;
1907        int budget = netdev_budget;
1908        void *have;
1909
1910        local_irq_disable();
1911
1912        while (!list_empty(&queue->poll_list)) {
1913                struct net_device *dev;
1914
1915                if (budget <= 0 || jiffies - start_time > 1)
1916                        goto softnet_break;
1917
1918                local_irq_enable();
1919
1920                dev = list_entry(queue->poll_list.next,
1921                                 struct net_device, poll_list);
1922                have = netpoll_poll_lock(dev);
1923
1924                if (dev->quota <= 0 || dev->poll(dev, &budget)) {
1925                        netpoll_poll_unlock(have);
1926                        local_irq_disable();
1927                        list_move_tail(&dev->poll_list, &queue->poll_list);
1928                        if (dev->quota < 0)
1929                                dev->quota += dev->weight;
1930                        else
1931                                dev->quota = dev->weight;
1932                } else {
1933                        netpoll_poll_unlock(have);
1934                        dev_put(dev);
1935                        local_irq_disable();
1936                }
1937        }
1938out:
1939#ifdef CONFIG_NET_DMA
1940        /*
1941         * There may not be any more sk_buffs coming right now, so push
1942         * any pending DMA copies to hardware
1943         */
1944        if (net_dma_client) {
1945                struct dma_chan *chan;
1946                rcu_read_lock();
1947                list_for_each_entry_rcu(chan, &net_dma_client->channels, client_node)
1948                        dma_async_memcpy_issue_pending(chan);
1949                rcu_read_unlock();
1950        }
1951#endif
1952        local_irq_enable();
1953        return;
1954
1955softnet_break:
1956        __get_cpu_var(netdev_rx_stat).time_squeeze++;
1957        __raise_softirq_irqoff(NET_RX_SOFTIRQ);
1958        goto out;
1959}

从流程上来看,传统的IO方法是在中断处理函数中读取数据包。


NAPI
NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的服务程序,然后 POLL 的方法来轮询数据。虽然,读取数据不在中断中,并不意味着没有中断了。中断还是有的,并且还会触发中断处理函数。

NAPI使用的先决条件:
A. 要使用 DMA 的环形输入队列(也就是 ring_dma,这个在 2.4 驱动中关于 Ethernet 的部分有详细的介绍),或者是有足够的内存空间缓存驱动获得的包。
B. 在发送/接收数据包产生中断的时候有能力关断 NIC 中断的事件处理,并且在关断 NIC 以后,并不影响数据包接收到网络设备的环形缓冲区(以下简称 rx-ring)处理队列中。
C. 有防止 NIC 队列中排队的数据包冲突的能力。

NAPI 对数据包到达的事件的处理采用轮询方法,在数据包达到的时候,NAPI 就会强制执行dev->poll 方法。而和不象以前的驱动那样为了减少包到达时间的处理延迟,通常采用中断的方法来进行。

跟踪NAPI的处理流程:
e1000_intr
__netif_rx_schedule(netdev);

这边就跟上面传统中断IO模式的后面处理一样了,触发软中断,执行dev->poll函数。

实际上NAPI,关键的一点就是系统在关中断的情况下,还能够接收数据包,当然这需要网卡的硬件支持。
注意:这里的关中断并不是CPU的关中断,而是屏蔽网卡的中断,一般是通过设置网卡中的中断掩码寄存器来实现的。中断掩码寄存器设置后,网卡依然可以通过DMA接收数据包,只是无法发送中断,来向CPU通知网卡接收事件而已。当然,在中断处理函数最后,需要开中断(设置中断屏蔽寄存器)。在新的Intel千兆网卡中,支持自动设置中断掩码(interrupt automask:我理解为发出中断后,自动设置中断屏蔽寄存器,屏蔽中断;在网卡的poll函数(e1000_clean)中,会在最后调用e1000_irq_enable,重新开中断)。

080813:
关于interrupt auto-mask可以查看我的另外一篇文章:
intel千兆网卡的interrupt auto-mask分析
http://blog.chinaunix.net/u/12592/showart_1129657.html

相比之下,对于传统的中断IO方式,不需要自己对设备关中断和开中断(不是CPU的关中断和开中断指令。当外部发生中断时,硬件会自动关闭中断;当从中断处理程序返回时,在POP出FLAG寄存器时,就自动开中断了)。NAPI方式需要一个关中断的环境(网卡接受数据,但是不产生中断),因此需要在设置自己的中断屏蔽寄存器来进行屏蔽中断。
在e1000_probe函数中,设置ICR.INT_ASSERTED,来激活interrupt automask:
    /* Hardware features, flags and workarounds */
    if (adapter->hw.mac.type >= e1000_82571) {
        adapter->flags.int_assert_auto_mask = 1;
#ifdef CONFIG_PCI_MSI
        adapter->flags.has_msi = 1;
#endif
        adapter->flags.has_manc2h = 1;
    }

Interrupt asserted (ICR.INT_ASSERTED = 1b) - ICR content is cleared and Auto
Mask is active (the IAM register is written to the IMC register).

当关断发送/接收事件中断的时候,NAPI 将在 POLL 中被调用处理,由于 POLL 方法的时候,NIC 中断已经不能通知包到达,那么这个时候在如果在完成轮询,并且中断打开以后,会马上有一个 NIC 中断产生,从而触发一次 POLL 事件,这种在中断关断时刻到达的包我们称为"rotting";这样就会在 POLL 机制和 NIC 中断之间产生一个竞争,解决的方法就是利用网卡的接收状态位,继续接收环形队列缓冲 rx-ring 中的数据,直到没有数据接收以后,才使能中断。


兼容NAPI
softnet_data有个backlog_dev成员,其原因就是为了兼容在NAPI。
最新的内核的网卡驱动的编程接口都已经变为了NAPI的方式了, NAPI接口是以poll函数为核心的。但旧的netif_rx接口也要兼容,最糟糕的是旧的驱动模式中没有实现poll函数的硬性要求,怎么办呢?于是内核的编程者就想了个折衷的权宜之计,即在 softnet_data增加一个backlog_dev,意思就是后备的没有办法的方法,虽然这样做很ugly :)。
思想明白了,编程就很简单了。
现在看代码:
在实现了新的NAPI的网卡驱动程序中,中断函数要先调用netif_rx_schedule(),这
个函数做的事就讲本dev 挂到本CPU的 softnet_data成员poll_list中:
1118void __netif_rx_schedule(struct net_device *dev)
1119{
1120        unsigned long flags;
1121
1122        local_irq_save(flags);
1123        dev_hold(dev);
1124        list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);
1125        if (dev->quota < 0)
1126                dev->quota += dev->weight;
1127        else
1128                dev->quota = dev->weight;
1129        __raise_softirq_irqoff(NET_RX_SOFTIRQ);
1130        local_irq_restore(flags);
1131}
1132EXPORT_SYMBOL(__netif_rx_schedule);

注意上面红色标注的两个netif_rx_schedule的地方,可以发现两个的参数是不同的。
在软中断的net_rx_action中的softnet_data的成员poll_list中存放的是就是本dev了,而不是backlog_dev;在兼容就的接口netif_rx时,因为网卡的net_device没有poll函数,netif_rx只得把包挂到softnet_data的队列中, 并且借用backlog_dev的poll函数来处理,以达到从宏观上看所有的驱动(包括旧的netif_rx接口的旧驱动)都实现了NAPI。


参考:
http://www.ibm.com/developerworks/cn/linux/l-napi/

http://hi.baidu.com/linux_kernel/blog/item/b2919a506d11b46284352418.html

http://hi.baidu.com/zengzhaonong/blog/item/c48864f4ec5401daf3d3856e.html

阅读(1590) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~