找回密码
 注册

QQ登录

只需一步,快速开始

搜索
查看: 6313|回复: 0

[linux] NAPI技术在Linux网络驱动上的应用(转)

[复制链接]
发表于 2007-12-11 00:24:19 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

×
NAPI 是 Linux 上采用
方式读取数据,而代之以 P
式);但是目前在 Linux
高效的改善方式供大家参考
的一种提高网络处理效率的技术
OLL 的方法来轮询数据,类似于
的 NAPI 工作效率比较差,本文

,它的核心概念就是不采用中断的
底半方式(bottom-half 的处理模
在分析 NAPI 的同时,提供了一种



  前言:

  NAPI 是 Linux 上采用
方式读取数据,而代之以首
据,(类似于底半(bottom
网络的接收速度的增加,NI
和网络层得到了广泛的应用
3c50X 系列等主流的网络适
应用到了著名的 netif_rx
理轮询的方法;根据实验数
中断触发的时间;由于 RTL
说明了NAPI技术在网络适配
的一种提高网络处理效率的技术
先采用中断唤醒数据接收的服务
-half)处理模式);从我们在
C 触发的中断能做到不断减少,
,驱动层次上已经有 E1000 系
配器都采用了这个技术,而在网
函数中间,并且提供了专门的 P
据表明采用NAPI技术可以大大改
8139CP 是一种应用比较广泛的
器上的应用和基本原理。
,它的核心概念就是不采用中断的
程序,然后 POLL 的方法来轮询数
实验中所得到的数据来看,在随着
目前 NAPI 技术已经在网卡驱动层
列网卡,RTL8139 系列网卡,
络层次上,NAPI 技术已经完全被
OLL 方法--process_backlog 来处
善短长度数据包接收的效率,减少
网络适配器,所以本文以其为例,



  但是 NAPI 存在一些比
包接收到的时候都可以及时
的内存,经过实验表明在 L
NAPI 所造成的另外一个问
层上的时候耗费的时间比短
样,NAPI 技术适用于对高
法,和实验数据。
较严重的缺陷:而对于上层的应
地去处理它,而且随着传输速度
inux 平台上这个问题会比在 Fr
题是对于大的数据包处理比较困
数据包长很多(即使是采用 DMA
速率的短长度数据包的处理,在

用程序而言,系统不能在每个数据
增加,累计的数据包将会耗费大量
eeBSD 上要严重一些;另外采用
难,原因是大的数据包传送到网络
方式),所以正如前面所说的那
本文的末尾提出了 NAPI 的改善方



  使用 NAPI 先决条件:

  驱动可以继续使用老的 2.4 内核的
容性的丧失,但是 NAPI 的使用至少要得
网络驱动程序接口,NAPI 的加入并不会导致向前兼
到下面的保证:

  A. 要使用 DMA 的环形输入队列(也
的部分有详细的介绍),或者是有足够的
就是 ring_dma,这个在 2.4 驱动中关于 Ethernet
内存空间缓存驱动获得的包。

  B. 在发送/接收数据包
NIC 以后,并不影响数据包

产生中断的时候有能力关断 NIC
接收到网络设备的环形缓冲区(

中断的事件处理,并且在关断
以下简称 rx-ring)处理队列中。



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


  应当注意的是,经过测试如果 DEC T
部分网卡芯片测试表明,如果把从前中断
会造成轻微的延迟,因此在进行 MII(介
mii_check_media的函数处理流程,本文
ulip 系列(DE21x4x芯片)以及 National Semi 的
处理的部分都改换用设备的 POLL 方法去执行,那么
质无关)的操作上就需要一些小小的诀窍,详见
不做详细讨论。


  在下面显示的例子表示
的原来中断应该处理的过程

了在 8139 中如何把处理过程放
放在了 POLL 方法里面,篇幅起

在 dev 的 poll 方法中,把所有
见,我们只介绍接收的 POLL 方法



  在下面的 8139CP 驱动
方法中去做,当然不同的 N
程序介绍中表明了可以把在中断
IC 在中断中所要处理的状态和
程序中所做的任何事情放在 POLL
事件是不一样的。


  对于所有的 NIC 设备,以下两种类型的 NIC 接收事件寄存器响应机制:

  COR 机制:当用户程序
的状态队列将被清零,nats
所有以前的中断响应的处理
读状态/事件寄存器,读完成的
emi 和 sunbmac 的 NIC 会这样
部分都移动到 POLL 方法中去。
时候寄存器和NIC的rx-ring中表示
做,在这种情况下,必须把 NIC



  COW 机制:用户程序写状态寄存器的
的 8139CP 就是这样的类型,大多数的 N
最好,它只需要把接收的数据包处理部分
在原先的中断控制程序中,我们等下将要
时候,必须对要写的位先写 1 清 0,如下面要介绍
IC 都属于这种类型,而且这种类型对 NAPI 响应得
放置在 POLL 方法中,而接收事件的状态处理部分放
介绍的 8139CP 类型网卡就是属于这种类型。


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



  锁定和防冲突机制:

  - 1.SMP 的保证机制:
在下面看到同时只有一个处
备调用POLL 方法。
保证同时只有一个处理器调用网
理器可以对调用 netif_rx_sche

络设备的 POLL 方法,因为我们将
dule 挂在 POLL 队列中的 NIC 设


  - 2. 网络核心层(net
层接收数据包的时候完全无
用软中断处理接收队列。
core)调用设备驱动程序使用
锁的接收,而网络核心层则同样

循环方式发送数据包,在设备驱动
要保证每次只有一个处理器可以使


  - 3. 在多个处理器对 NIC 的 rx-ri
close)和挂起(suspend)方法的时候(
ng 访问的时刻只能发生在对循环队列调用关闭(
在这个时刻会试图清除接收循环队列)

  - 4. 数据同步的问题
程序已经把这些事情做完了
(对于接收循环队列来说),驱

动程序是不需要考虑的网络层上的


  - 5. 如果没有把全部
链路状态发生变化和发送完
设备负载最大的的情况,当
的部分交给 POLL 方法处理,那
成中断仍然和以前的处理步骤一
然并不能说这样一定正确。
么 NIC 中断仍然需要使能,接收
样,这样处理的假设是接收中断是


  下面的部分将详细介绍在接收事件中调用设备的 POLL 方法。

  NAPI 提供的重要函数和数据结构和函数:

  核心数据结构:

  struct softnet_data 结构内的字段
,它从 NIC中断和 POLL 方法之间传递数
就是 NIC 和网络层之间处理队列,这个结构是全局的
据信息。其中包含的字段有:


  struct softnet_data
  {
   int throttle; /*为 1 表示当前队列的数据包被禁止*/
   int cng_level; /*表示当前处理器的数据包处理拥塞程度*/
   int avg_blog; /*某个处理器的平均拥塞度*/
   struct sk_buff_head
input_pkt_queue; /*接收缓冲
区的sk_buff队列*/

   struct list_head      poll_
list; /*POLL设备队列头*/

   struct net_device
     output_queue; /*网
络设备发送队列的队列头*/

   struct sk_buff com
pletion_queue; /*完成发送的
数据包等待释放的队列*/

  struct net_device backlog_dev; /
*表示当前参与POLL处理的网络设备*/

  };
  
  核心 API:

  1. netif_rx_schedule(dev)
  这个函数被中断服务程
去,排队并且准备接收数据
的数为 1,并且触发一个 N
序调用,将设备的 POLL 方法添
包,在使用之前需要调用 netif
ET_RX_SOFTIRQ 的软中断通知网
加到网络层次的 POLL 处理队列中
_rx_reschedule_prep,并且返回
络层接收数据包。


  2. netif_rx_schedule_prep(dev)
  确定设备处于运行,而且设备还没有
netif_rx_schedule之前会调用这个函数
被添加到网络层的 POLL 处理队列中,在调用



  3. netif_rx_complete(dev)
  把当前指定的设备从 POLL 队列中清
POLL 队列处于工作状态的时候是不能把
除,通常被设备的 POLL 方法调用,注意如果在
指定设备清除的,否则将会出错。
如何在8139CP使用NAPI:

  从 POLL 方法的本质意
据包的时候,减少中断,以
上,以便把赢得的时间用来
的处理过程中,目的就在于
直接调用设备的POLL方法来
间片内的调度完成。
义上来说就在于尽量减少中断的
达到不要让整个操作系统花费太
在我网络层上的处理数据的传输
尽快把产生中断的设备挂在 pol
处理数据包的接收,直到收到数

数目,特别在于大量的小长度的数
多的时间在中断现场的保护和恢复
,例如在下面介绍的 8139CP 中断
l_list,并且关闭接收中断,最后
据包收无可收,或者是达到一个时


  


  RTL8139C+ 的数据接收环形缓冲队列:

  RTL8139C+ 的接收方式
适合大型的服务器使用,适
802.1Q,VLAN等网络形式;
个不同的环形缓冲队列--一
,一个是接收符描述队列,
符有 4 个连续的双字组成
前,软件需要预先分配一个
物理地址链接在描述符的 D
冲区的接收状态。
是一种全新的缓冲方式,能显著
合 IP,TCP,UDP 等多种方式的数
在 8139CP 中分别有 64 个连续
个是高优先级传输描述符队列,
每个环形缓冲队列右 64 个4个
,每个描述符的开始地址在 256
DMA 缓冲区,一般对于传输而
MA 地址描述单元,另外还有两

的降低CPU接收数据造成的花费,
据下载,以及连接 IEEE802.1P,
的接收/发送描述符单元,对应三
一个是普通优先级传输符描述队列
双字的连续描述符组成,每个描述
个字节的位置对齐,接收数据之
言,缓冲区最大为 8Kbyte 并且把
个双字的单元表示对应的 DMA 缓



  在 /driver/net/8139C
P.C 中对于环形缓冲队列描述符
的数据单元如下表示:

  struct cp_desc { u32 opts1;/*缓
位*/ u32 opts2;/*专门用于VLAN部分*/
冲区状态控制符,包含缓冲区大小,缓冲区传输启动
u64 addr; /*缓冲区的DMA地址*/ };


  8139CP 的 NIC 中断:

  static irqreturn_t
  cp_interrupt (int irq, void *dev
_instance, struct pt_regs *regs)

  {
   struct net_device *dev = dev_instance;
   struct cp_private *cp = dev->priv;

   u16 status;
  /*检查rx-ring中是否有中断到达*/
   status = cpr16(IntrStatus);
   if (!status || (status == 0xFFFF))
   return IRQ_NONE;

   if (netif_msg_intr(cp))
   printk(KERN_DEBUG
"%s: intr, status %04x cmd %
02x cpcmd %04x ",

       dev->name, status, cpr8(Cmd), cpr16(CpCmd));

  /*清除NIC中断控制器的内容*/
   cpw16(IntrStatus, status & ~cp_
rx_intr_mask);


   spin_lock(&cp->lock);

  /*接收状态寄存器表示有数据包到达*/
   if (status & (RxOK
| RxErr | RxEmpty | RxFIFOOv
r)) {

  /*把当前的产生中断的
用程序处理*/
NIC设备挂在softnet_data中的P

OLL队列上,等待网络上层上的应


   if (netif_rx_schedule_prep(dev)) {
  /*关闭接收中断使能*/

   cpw16_f(IntrMask,
cp_norx_intr_mask);

   __netif_rx_schedule(dev);
   }
   }
  /*发送中断的处理过程以及8139C+的
专门软中断的处理过程,这里我们不关心*/

   if (status & (TxOK | TxErr | Tx
Empty | SWInt))

   cp_tx(cp);
  /*如果发生链路变化的
,否则就要准备重新启动MI
情况,需要检查介质无关接口(
I接口*/
MII)的载波状态同样也发生变化


   if (status & LinkChg)
   mii_check_media(&cp->mii_if, netif_msg_link(cp), FALSE);

  /*如果PCI总线发生错误,需要对8139C+的设备重新复位*/
   if (status & PciErr) {
   u16 pci_status;

   pci_read_config_word(cp->pdev, PCI_STATUS, &pci_status);

   pci_write_config_word(cp->pdev, PCI_STATUS, pci_status);

   printk(KERN_ERR "%
s: PCI bus error, status=%04
x, PCI status=%04x ",

       dev->name, status, pci_status);


   /* TODO: reset hardware */
   }

   spin_unlock(&cp->lock);

   return IRQ_HANDLED;
  }
  
  把 NIC 挂在 POLL 队列(poll_list)上

  在 8139CP 的中断程序可以看到 __n
softnet_data 结构中的 poll_list 队列
bottom-half部分来进行处理,我们先来
etif_rx_schedule 的调用方式,它把 NIC 设备挂在
上,以便及时的返回中断,让专门数据包处理
看一下 __netif_rx_schedule 的内部工作流程。


  static inline void _
_netif_rx_schedule(struct ne
t_device *dev)

  {
   unsigned long flags;

   local_irq_save(flags);
   dev_hold(dev);
  /*把当前NIC设备挂在P
OLL(poll_list)队列中,等待
唤醒软中断以后进行轮询*/

   list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list);

  /*确定当前该设备所要准备接收的包大小*/
   if (dev->quota quota += dev->weight;

   else
   dev->quota = dev->weight;

  /*启动软中断,在表示所有中断的状
__softirq_pending中,把关于网络轮循
断的句柄net_rx_action。*/
态字irq_cpustat_t中关于软中断字段
接收软中断位置1,等待调度时机来临时候运行该中



   __raise_softirq_irq
off(NET_RX_SOFTIRQ);

   local_irq_restore(flags);
  }
  
  由 __netif_rx_schedule 启动的软中断的处理过程分析

  软中断事件触发前已经在此设备子系统初始化时刻调用 subsys_initcall(net_dev_init) 在软中断控制台上被激活,挂在任务队列 tasklet 上准备在任务调度 schedule 的时刻运行它了,这个里面最主要的部分是调用了 8139C+ 网络设备的 POLL 方法(dev->poll),从网络设备的 rx-ring队列中获得数据,本来它应当放在网络设备中断服务程序中执行的,按照我们前面解释的那样,POLL方法以空间换取时间的机制把它放在软中断部分来执行轮循机制(采用类似老的 Bottom-half 机制也可以达到同样效果,而且更加容易理解一些)在每次进行进程调度的时候就会执行网络设备软中断,轮询 rx-ring 对 NIC 进行数据的接收。


  软中断的处理过程:

  static void net_rx_action(struct
softirq_action *h)

  {
   struct softnet_data
*queue = &__get_cpu_var(sof
tnet_data);

   unsigned long start_time = jiffies;
   int budget = netdev
_max_backlog;/*表示队列的最
大长度*/

  /*锁定当前线程,多处理器的情况之下不能被其他处理器中断处理*/
   preempt_disable();
   local_irq_disable();
  /*检查POLL队列(poll_list)上是
否有设备在准备等待轮询取得数据*/

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

   struct net_device *dev;
  /*这里保证执行当前的 POLL 过程的时间不超过一个时间片,这样不至于被软中断占用太多的时间,这样在一次调度的时间内执行完毕当前的 POLL 过程,budget 表示一个时间片内最大数据传输的"块数",块的意思为每个 POLL 所完成 sk_buff数量,每块中间的 sk_buff 数量为 dev->quota 决定,在 8139CP 驱动中,budget 为 300,而 quota 为 16 表示每给时间片最多可以接收到 4.8K 的 sk_buff 数量*/

   if (budget 1)

   goto softnet_break;

   local_irq_enable();
  /*从公共的 softnet_d
ata 数据结构中的轮循队列上获
得等待轮循的设备结构*/

   dev = list_entry(queue->poll_list.next,

   struct net_device, poll_list);
  /*调用设备的POLL方法从NIC上的Ring Buffer中读入数据*/
   if (dev->quota poll(dev, &budget)) {

  /*完成一次POLL过程的数据的接收,
sk_buff缓冲区的数量,每次调用POLL方
sk_buff缓冲区数目,这个参数很重要在高
据接收的情况下,需要增加该数值)*/
重新定义设备接收数据的"配额"(事实上就是
法的时候可以创建并且最多可以向上层提交的
速处理的时候有需要慎重优化这个数值,在有大量数


   local_irq_disable();
   list_del(&dev->poll_list);

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

   if (dev->quota quota += dev->weight;

   else
   dev->quota = dev->weight;

   } else {
  /*发生了错误的数据接收状况,或者
进来,这个也可能表示已经完成了传输的
队列上清除(介绍POLL过程的时候详细介
没有完成"规定"配额的数据接收,并且没有新的数据
过程,调用__netif_rx_complete把网络设备从POLL
绍)*/

   dev_put(dev);
   local_irq_disable();
   }
   }
  out:
   local_irq_enable();
   preempt_enable();
   return;

  softnet_break:

   __get_cpu_var(netde
v_rx_stat).time_squeeze++;


   __raise_softirq_irq
off(NET_RX_SOFTIRQ);

   goto out;
  }
  
  8139CP 驱动中的轮询方法

  dev->poll 方法:

  这个方法通常被网络层在向驱动的接收循环队列获取新的数据包时刻调用,而驱动的接收循环队列中可以向网络层交付的包数量则在 dev->quota 字段中表示,我们来看 8139cp 中 POLL 的原型:

  static int cp_rx_poll (struct ne
t_device *dev, int *budget)


  参数 budget 的上层任务所需要底层
netdev_max_backlog 的值。
传递的数据包的数量,这个数值不能超过



  总而言之,POLL 方法
数量的数据包。8139CP 的
候进行,如下:
被网络层调用,只负责按照网络
POLL 方法注册通常在设备驱动

层的要求值("预算"值)提交对应
程序模块初始化(调用 probe)的时



  static int cp_init_o
ne (struct pci_dev *pdev, co
nst struct pci_device_id *ent)

  {
  … …
  dev->poll = cp_rx_poll;

  … …
  }
  
  设备的 POLL 方法正如
来看具体的流程:
前所说的是被网络层上的软中断

net_rx_action 调用,我们现在



  static int cp_rx_pol
l (struct net_device *dev, i
nt *budget)

  {

   struct cp_private *
cp = netdev_priv(dev);

   unsigned rx_tail = cp->rx_tail;

   /*设定每次进行调度的时候从设备发送到网络层次最大的数据包的大小*/
  unsigned rx_work = dev->quota;

   unsigned rx;

  rx_status_loop:
   rx = 0;
  /*重新打开NIC中断,在 cp_interru
理环行缓冲队列中的数据,所以中断可以
pt 中断句柄中中断关闭了,现在 POLl 已经开始处
打开,准备接收新的数据包*/

   cpw16(IntrStatus, cp_rx_intr_mask);

   while (1) {/*POLL循环的开始*/
   u32 status, len;
   dma_addr_t mapping;
   struct sk_buff *skb, *new_skb;
   struct cp_desc *desc;
   unsigned buflen;
  /*从下标为rx_tail的
内存中的环行缓冲队列接收队列
rx_skb上"摘下"套接字缓冲区*/

   skb = cp->rx_skb[rx_tail].skb;

   if (!skb)
   BUG();

   desc = &cp->rx_ring[rx_tail];

  /*检查在 NIC 的环形
FIFO 的错误,是否*/
队列(rx_ring)上的最后的数

据接收状态,是否有出现接收或者


   status = le32_to_cpu(desc->opts1);

   if (status & DescOwn)
   break;

   len = (status & 0x1fff) - 4;
   mapping = cp->rx_skb[rx_tail].mapping;


   if ((status & (FirstFrag | Las
tFrag)) != (FirstFrag | LastFrag)) {

   /* we don't suppo
rt incoming fragmented frame
s.

   * instead, we attempt to ens
ure that the

   * pre-allocated
RX skbs are properly sized s
uch


   * that RX fragme
nts are never encountered

   */
   cp_rx_err_acct(cp, rx_tail, s
tatus, len);

   cp->net_stats.rx_dropped++;

   cp->cp_stats.rx_frags++;

   goto rx_next;
   }

   if (status & (RxError | RxErrFIFO)) {

   cp_rx_err_acct(cp
, rx_tail, status, len);

   goto rx_next;
   }

   if (netif_msg_rx_status(cp))
   printk(KERN_DEBUG "%s: rx slo
t %d status 0x%x len %d ",

       cp->dev->name, rx_tail, status, len);


   buflen = cp->rx_buf_sz + RX_OFFSET;

  /*创建新的套接字缓冲区*/
   new_skb = dev_alloc_skb (buflen);
   if (!new_skb) {
   cp->net_stats.rx_dropped++;

   goto rx_next;
   }

   skb_reserve(new_skb, RX_OFFSET);
   new_skb->dev = cp->dev;

  /*解除原先映射的环行队列上的映射区域*/
   pci_unmap_single(cp->pdev, mapping,

   buflen, PCI_DMA_FROMDEVICE);
  /*检查套接字缓冲区(sk_buff)上得到的数据校验和是否正确*/
   /* Handle checksum offloading
for incoming packets. */

   if (cp_rx_csum_ok(status))
   skb->ip_summed = CHECKSUM_UNNECESSARY;

   else
   skb->ip_summed = CHECKSUM_NONE;

  /*按照数据的实际大小重新定义套接字缓冲区的大小*/
   skb_put(skb, len);

   mapping =
   cp->rx_skb[rx_tail].mapping =

  /*DMA影射在前面新创建的套接字缓冲区虚拟地址new_buf->tail到实际的物理地址上,并且把这个物理地址挂在接收缓冲区的队列中*/

   pci_map_single(cp->pdev, new_skb->tail,

       buflen, PCI_DMA_FROMDEVICE);
  /*把新建立的缓冲区的
个结构时候,POLL方法会从
虚拟地址挂在接收缓冲区的队列
这个虚拟地址读出接收到的数据
中,在下一次访问rx_skb数组的这
包*/

   cp->rx_skb[rx_tail].skb = new_skb;

  /*在cp_rx_skb调用netif_rx_skb,填
中调用ip_rcv接收网络数据,这个函数替
充接收数据包队列,等待网络层在Bottom half队列
代了以前使用的netif_rx*/

   cp_rx_skb(cp, skb, desc);
   rx++;

  rx_next:
  /*把前面映射的物理地址挂在NIC设
物理存储区进行了DMA映射的,而不是驱
行数据传输*/
备的环行队列上(也就是rx_ring上,它是在和NIC中
动在内存中动态建立的),准备提交给下层(NIC)进


   cp->rx_ring[rx_tail].opts2 = 0;

   cp->rx_ring[rx_tail].addr = cpu_to_le64(mapping);

  /*在相应的传输寄存器中写入控制字
,把rx_ring的控制权从驱动程序交还给NIC硬件*/

   if (rx_tail == (CP_RX_RING_SIZE - 1))
   desc->opts1 = cpu_to_le32(DescOwn | RingEnd |

    cp->rx_buf_sz);

   else
   desc->opts1 = cpu_to_le32(DescOwn | cp->rx_buf_sz);

  /*步进到下一个接收缓冲队列的下一个单元*/
   rx_tail = NEXT_RX(rx_tail);

   if (!rx_work--)
   break;
   }

   cp->rx_tail = rx_tail;

  /*递减配额值quota,一旦quota递减
数据到来的时候再次唤醒软中断执行POLL
到0表示这次的POLL传输已经完成了使命,就等待有
方法*/

   dev->quota -= rx;

   *budget -= rx;

   /* if we did not reach work lim
it, then we're done with

   * this round of polling
   */
   if (rx_work) {
  /*如果仍然有数据达到,那么返回POLL方法循环的开始,继续接收数据*/

   if (cpr16(IntrStat
us) & cp_rx_intr_mask)

   goto rx_status_loop;
  /*这里表示数据已经接
中断,并且调用__netif_rx
次中断产生的时候,再次把
收完毕,而且没有新的接收中断
_complete把已经完成POLL的设
设备挂上poll_list队列中。*/
产生了,这个时候使能NIC的接收
备从poll_list上摘除,等待下一


   local_irq_disable();
   cpw16_f(IntrMask, cp_intr_mask);
   __netif_rx_complete(dev);
   local_irq_enable();

   return 0; /* done */
   }

   return 1; /* not done */
  }
  
  其他的使用 NAPI 的驱动程序和 813
方法--proecess_backlog(/net/dev.c)
netif_rx(/net/dev.c)将硬件中断中接
识别帧类型, 放入接收队列(softnet_da
软中断作进一步处理. 软中断函数(net_r
POLL 方法)向上层提交数据。
9CP 大同小异,只是使用了网络层专门提供的 POLL
,在 NIC 中断接收到了数据包后,调用网络层上的
收到数据帧存入 sk_buff 结构, 然后检查硬件帧头,
ta 结构中的 input_pkt_queue 队列上), 激活接收
x_action)提取接收包,而 process_backlog(也就是
POLL 方法)向上层提交数据。
能让接收速度更快一些吗?

  我们现在来思考一下如
在linux 的文档中 NAPI_HO
和 8139 有一些不一样,其
用到的。
何提高 NAPI 效率的问题,在说
WTO.txt 中提供一个模型用来构
中 NIC 设备描述中有一个 dirt

到效率这个问题之前我们先看一下
造自己 NIC 的 POLL 方法,不过
y_rx 字段是在 8139CP 中没有使



  dirty_rx 就是已经开
收的缓冲,但是还没有完成
是 cur_rx 这个表示的是下
以看到这个字段的一些具体
辟了 sk_buff 缓冲区指针和已
传输的缓冲区和已经完成传输的
一个参与传输的缓冲区指针,我
使用方式:
经提交到 NIC 的 rx_ring 参与接
缓冲区的数量总和,与之相类似的
们在 NAPI_HOWTO.txt 的举例中可


  


  /*cur_rx为下一个需要
经有在rx-ring中开辟的rx-
refill_rx_ring 把一些已
dirty_rx的数值,为下一次
参与传输的缓冲区指针,如果cu
ring中的每个传输缓冲已经被耗
经向网络层提交了数据的rx-rin
数据接收做准备,*/
r_rx指针大于dirty_rx那么表示已
尽了,这个时候需要调用
g接收单元开辟新的缓冲区,增加


  if (tp->cur_rx - tp->dirty_rx > RX_RING_SIZE/2 ||

    tp->rx_buffers[tp->dirty_rx % RX_RING_SIZE].skb == NULL)

    refill_rx_ring(dev);

  /*如果已经当前的cur_rx和dirty_rx
剩下的一半中间有空的传输单元,那么我
经验推断的),就可以退出当前的程序,
据,(NAPI_HOWTO.txt中是重新启动时钟
/
之间相差不超过总的rx_ring接收单元的一半,而且
们不必担心了,因为还有足够的缓冲区可以使用(凭
等待下一次软中断调用POLL来处理在这之间收到的数
计数,这样做是在没有使能NIC中断处理的情况下)*


      if (tp->rx_buffers[tp->dirty_rx % RX_RING_SIZE].skb == NULL)

          restart_timer();
  /*如果执行到这里了,那表示有几种
差不超过总的rx_ring接收单元的一半,
rx-ring中大量的单元收到数据没有得到
也没有空闲的单元来接收新到的数据,这
调用设备的POLL方法,采集在rx-ring的
情况可能发生,第一当前的cur_rx和dirty_rx之间相
调用refill_rx_ring后dirty_rx并未增加,(也许在
网络层函数的处理),结果dirty_rx没有增加,而且
样就要重新调用netif_rx_schedule 来唤醒软中断,
数据。*/

   else netif_rx_sched
ule(dev); /* we are back on
the poll list */

  
  在 RTL-8169 的驱动程序中就使用了
其实这个并非 8139CP 驱动不成熟的表现
8139CP 中并未严格按照 NAPI 所提出的
8139CP 和 RTL-8169 这两个驱动之间的
中去完成数据从驱动层向网络层上的转发
了自己的一些独特的硬件特性,使 NIC
RxOK)通知到达事件,然后采用 POLL 方
要借助 softnet_data 结构中的 input_p
成从 NIC 中断到软中断之间的 sk_buff
要了 dirty_rx 字段和 cur_rx 字段来让
况,还包括不需要不时定期的调用 refil
说到坏处我想就是重写了一个 POLL 方法
process_backlog 来作为自己的POLL方法
dirty_rx 这个字段,但是在 8139CP 中并未使用,
,大家阅读 NAPI_HOWTO.txt 中可以知道,现在
要求去做,如果大家有兴趣的话,可以比较一下
不同,大家会发现虽然两者都没有在 NIC 中断处理
,而都放在了软中断之中完成,但是在 8139 中利用
在利用关断中断接收数据的同时利用数据包到达位(
法把数据从 NIC 直接转发到上层;而 RTL8169 还需
kt_queue(套接字缓冲(sk_buff)输入队列)来完
数据调度;这样对于 8139CP 来说最大的好处就是不
POLL 方法以及 NIC 中断知道当前的传输单元的状
l_rx_ring 来刷新 rx-ring 获得空闲的传输单元;
,完全不能借用 /net/core/dev.c 中的
,不过这个代价值得。


  说了这么多,似乎都和
softnet_data中的一些字段
8139CP 的基础上借用了 NA
应用场合之下比 Linux的 8
核使用 8139CP 在x86(PIII
提高效率没有关系,其实正好相
的意思应该更加清晰了,下面所
PI_ HOWTO.txt 中的一些方法,
139CP 的确是有了一定的提高,
-900Mhz)平台上的数据包接收处
反,通过这些了解我们对
叙述的,提高效率的方法就是在
从实际上的使用效果来看,在某些
我们首先看看在 Linux2.6.6 的内
理情况:比较表如下:


  Psize  Ipps    Tput   Rxint      Done
  --------------------
----------------------------
----

  60   490000   254560   21       10
  128   358750   259946   27       11
  256   334454   450034   34       18
  512   234550   
556670   201239     
193455

  1024  119061   995645   
884526     882300

  1440   74568   995645   
995645     987154

  
  上表中表示:
  &quotszie"表示包的大小
  "Ipps" 每秒钟系统可以接收的包数量
  "Tput" 每次POLL超过 1M 个数据包的总量
  "Rxint" 接收中断数量
  "Done" 载入 rx-ring
rx-ring 的次数。
内数据所需要的 POLL 次数,这

个数值也表示了我们需要清除



  从上表可以看出,8139
断产生,只需要 10 次 POL
的情况,接收中断就会急剧
最后的结果就是每个中断都
效率就会大大降低,所以 N
数据包,而且低速率的,反
CP 中当接收速率达到 490K pac
L 就可以完成数据从 rx_ring
增加,直到最后每个数据包都需
需要一次 POLL 的方法,最后造
API 适用于大量的数据包而且尽
而会造成系统速度的下降。
kets/s 的时候仅仅只有 21 个中
的接收,然而对于大数据包低速率
要一次 POLL 的方法来进行处理,
成效率的急剧下降,以至于系统的
可能是小的数据包,但是对于大的



  如果要改善这种情况,我们可以考虑
平台上进行一系列的测试取得了良好的效
采用以下的方法,我们在 MIPS,Xsacle 和 SA1100
果:


  1. 完全取消 NIC 中断,使用 RXOK 位置控制接收中断。
  2. 采用定时器中断 t
间隔周期依据平台不同而异
上直接使用了中断向量 0--
个平台上选择的 HZ 数值是
数程序,让 Wall-Time 的
序的状况选择合适的定时时
imer_list 的控制句柄,根据硬
),对 rx-ring 直接进行 POLL
irq0 作为对 rx-ring 进行轮询
1000,而通常这个数值是 100,
记数还是以 10MS 为间隔),当
间。
件平台设定一个合适的间隔周期(
轮询,我们在 MIPS 和 Xscale
的 top-half(注意我们在上述两
并且重新编写了 Wall-time 的记
然也可以根据自己的平台和应用程


  3. 借助 softnet_dat
成 POLL 方法之后,并不直
input_pkt_queue队列上,
代价,而且实时性能也要差
a 中的 input_pkt_queue 队列
接把数据传输到网络层进行处理
唤醒软中断在过后处理,当然可
一些。
,在时钟中断 bottom-half 中完
,而是把 sk_buff 挂在
以想象,这样需要付出一定的内存


  4. 使用 dirty_rx 字段和 refill_
层程序比较空闲的时候为一些 rx-ring
在新的数据包达到的时候节省时间,操作
据。
rx_ring 函数,在调用完 POLL 方法以后,而且网络
中的单元建立新缓冲挂在环形缓冲队列上,这样可以
系统不必要手忙脚乱地开辟新的空间去应付新来的数


  5. 最后请注意:我们
很多后台进程的复杂的应用
而单独改善了网络数据的处
上层的应用程序是以网络数据转
,上述的 1 到 4 点中所做的显
理。
发为主的,并没有在应用层面上有
而易见是以牺牲系统效率整体效率



  我们再来看改善的 8139CP 驱动程序
况:
使用 8139CP 在 x86(PIII-900Mhz) 平台上的接收情



  Psize  Ipps    Tput   Rxint    Done
  --------------------
----------------------------
----

  60   553500  354560  17     7
  128   453000  350400  19     10
  256   390050  324500  28     13
  512   305600  456670  203    455
  1024   123440  340020  772951   123005
  1440   64568   344567  822394   130000
  
  从上图来看,数据传输
的POLL 次数的差距以前的
高到了 553K/s,我们在 MI
15%-25%。
的效率和波动性有很明显的改善
8139CP 驱动程序那么显著了,
PS 系列以及 Xscale 系列平台

,在高速率和低速率的时候所需要
这样的而且最大的包接收能力也提
上最大的包接收能力可以提高大约



  最后使用 NAPI 并不是改善网络效率
是在于上层应用程序能够独占网络设备,
实验数据表明可以提高 100%-150% 以上
的唯一途径,只能算是权益之策,根本的解决途径还
或者是提供大量的缓冲资源,如果这样,根据我们的
的接收效率。
routeros
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|Archiver|手机版|小黑屋|软路由 ( 渝ICP备15001194号-1|渝公网安备 50011602500124号 )

GMT+8, 2024-5-29 10:31 , Processed in 0.063321 second(s), 4 queries , Gzip On, Redis On.

Powered by Discuz! X3.5 Licensed

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表