NAPI-技术在-Linux-网络驱动上的应用和完善.docx
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- NAPI 技术 Linux 网络 驱动 应用 完善
- 资源描述:
-
NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以 POLL 的方法来轮询数据,类似于底半方式〔bottom-half 的处理模式〕;但是目前在 Linux 的 NAPI 工作效率比拟差,本文在分析 NAPI 的同时,提供了一种高效的改善方式供大家参考。 前言: NAPI 是 Linux 上采用的一种提高网络处理效率的技术,它的核心概念就是不采用中断的方式读取数据,而代之以首先采用中断唤醒数据接收的效劳程序,然后 POLL 的方法来轮询数据,〔类似于底半〔bottom-half〕处理模式〕;从我们在实验中所得到的数据来看,在随着网络的接收速度的增加,NIC 触发的中断能做到不断减少,目前 NAPI 技术已经在网卡驱动层和网络层得到了广泛的应用,驱动层次上已经有 E1000 系列网卡,RTL8139 系列网卡,3c50X 系列等主流的网络适配器都采用了这个技术,而在网络层次上,NAPI 技术已经完全被应用到了著名的 netif_rx 函数中间,并且提供了专门的 POLL 方法--process_backlog 来处理轮询的方法;根据实验数据说明采用NAPI技术可以大大改善短长度数据包接收的效率,减少中断触发的时间;由于 RTL8139CP 是一种应用比拟广泛的网络适配器,所以本文以其为例,说明了NAPI技术在网络适配器上的应用和根本原理。 但是 NAPI 存在一些比拟严重的缺陷:而对于上层的应用程序而言,系统不能在每个数据包接收到的时候都可以及时地去处理它,而且随着传输速度增加,累计的数据包将会消耗大量的内存,经过实验说明在 Linux 平台上这个问题会比在 FreeBSD 上要严重一些;另外采用 NAPI 所造成的另外一个问题是对于大的数据包处理比拟困难,原因是大的数据包传送到网络层上的时候消耗的时间比短数据包长很多〔即使是采用 DMA 方式〕,所以正如前面所说的那样,NAPI 技术适用于对高速率的短长度数据包的处理,在本文的末尾提出了 NAPI 的改善方法,和实验数据。 使用 NAPI 先决条件: 驱动可以继续使用老的 2.4 内核的网络驱动程序接口,NAPI 的参加并不会导致向前兼容性的丧失,但是 NAPI 的使用至少要得到下面的保证: A. 要使用 DMA 的环形输入队列〔也就是 ring_dma,这个在 2.4 驱动中关于 Ethernet 的局部有详细的介绍〕,或者是有足够的内存空间缓存驱动获得的包。 B. 在发送/接收数据包产生中断的时候有能力关断 NIC 中断的事件处理,并且在关断 NIC 以后,并不影响数据包接收到网络设备的环形缓冲区〔以下简称 rx-ring〕处理队列中。 NAPI 对数据包到达的事件的处理采用轮询方法,在数据包到达的时候,NAPI 就会强制执行dev->poll 方法。而和不象以前的驱动那样为了减少包到达时间的处理延迟,通常采用中断的方法来进行。 应当注意的是,经过测试如果 DEC Tulip 系列〔DE21x4x芯片〕以及 National Semi 的局部网卡芯片,的测试说明如果把从前中断处理的局部都改换用设备的 POLL 方法去执行,那么会造成轻微的延迟,因此在进行 MII〔介质无关〕的操作上就需要一些小小的诀窍,详见 mii_check_media的函数处理流程,本文不做详细讨论。 在下面显示的例子表示了在 8139 中如何把处理过程放在 dev 的 poll 方法中,把所有的原来中断应该处理的过程放在了 POLL 方法里面,篇幅起见,我们只介绍接收的 POLL 方法。 在下面的 8139CP 驱动程序介绍中说明了可以把在中断程序中所做的任何事情放在 POLL 方法中去做,当然不同的 NIC 在中断中所要处理的状态和事件是不一样的。 对于所有的 NIC 设备,以下两种类型的 NIC 接收事件存放器响应机制: COR 机制:当用户程序读状态/事件存放器,读完成的时候存放器和NIC的rx-ring中表示的状态队列将被清零,natsemi 和 sunbmac 的 NIC 会这样做,在这种情况下,必须把 NIC 所有以前的中断响应的处理局部都移动到 POLL 方法中去。 COW 机制:用户程序写状态存放器的时候,必须对要写的位先写 1 清 0,如下面要介绍的 8139CP 就是这样的类型,大多数的 NIC 都属于这种类型,而且这种类型对 NAPI 响应得最好,它只需要把接收的数据包处理局部放置在 POLL 方法中,而接收事件的状态处理局部放在原先的中断控制程序中,我们等下将要介绍的 8139CP 类型网卡就是属于这种类型。 C. 有防止 NIC 队列中排队的数据包冲突的能力。 当关断发送/接收事件中断的时候,NAPI 将在 POLL 中被调用处理,由于 POLL 方法的时候,NIC 中断已经不能通知包到达,那么这个时候在如果在完成轮询,并且中断翻开以后,会马上有一个 NIC 中断产生,从而触发一次 POLL 事件,这种在中断关断时刻到达的包我们称为"rotting";这样就会在 POLL 机制和 NIC 中断之间产生一个竞争,解决的方法就是利用网卡的接收状态位,继续接收环形队列缓冲 rx-ring 中的数据,直到没有数据接收以后,才使能中断。 锁定和防冲突机制: - 1.SMP 的保证机制:保证同时只有一个处理器调用网络设备的 POLL 方法,因为我们将在下面看到同时只有一个处理器可以对调用 netif_rx_schedule 挂在 POLL 队列中的 NIC 设备调用POLL 方法。 - 2. 网络核心层〔net core〕调用设备驱动程序使用循环方式发送数据包,在设备驱动层接收数据包的时候完全无锁的接收,而网络核心层那么同样要保证每次只有一个处理器可以使用软中断处理接收队列。 - 3. 在多个处理器对 NIC 的 rx-ring 访问的时刻只能发生在对循环队列调用关闭〔close〕和挂起〔suspend〕方法的时候〔在这个时刻会试图去除接收循环队列〕 - 4. 数据同步的问题〔对于接收循环队列来说〕,驱动程序是不需要考虑的网络层上的程序已经把这些事情做完了。 - 5. 如果没有把全部的局部交给 POLL 方法处理,那么 NIC 中断仍然需要使能,接收链路状态发生变化和发送完成中断仍然和以前的处理步骤一样,这样处理的假设是接收中断是设备负载最大的的情况,当然并不能说这样一定正确。 下面的局部将详细介绍在接收事件中调用设备的 POLL 方法。 NAPI 提供的重要函数和数据结构和函数: 核心数据结构: struct softnet_data 结构内的字段就是 NIC 和网络层之间处理队列,这个结构是全局的,它从 NIC中断和 POLL 方法之间传递数据信息。其中包含的字段有: struct softnet_data { intthrottle;/*为 1 表示当前队列的数据包被禁止*/ intcng_level;/*表示当前处理器的数据包处理拥塞程度*/ intavg_blog;/*某个处理器的平均拥塞度*/ struct sk_buff_headinput_pkt_queue;/*接收缓冲区的sk_buff队列*/ struct list_head poll_list;/*POLL设备队列头*/ struct net_device output_queue; /*网络设备发送队列的队列头*/ struct sk_buffcompletion_queue; /*完成发送的数据包等待释放的队列*/ struct net_devicebacklog_dev;/*表示当前参与POLL处理的网络设备*/ }; 核心 API: 1. netif_rx_schedule(dev) 这个函数被中断效劳程序调用,将设备的 POLL 方法添加到网络层次的 POLL 处理队列中去,排队并且准备接收数据包,在使用之前需要调用 netif_rx_reschedule_prep,并且返回的数为 1,并且触发一个 NET_RX_SOFTIRQ 的软中断通知网络层接收数据包。 2. netif_rx_schedule_prep(dev) 确定设备处于运行,而且设备还没有被添加到网络层的 POLL 处理队列中,在调用 netif_rx_schedule之前会调用这个函数。 3. netif_rx_complete(dev) 把当前指定的设备从 POLL 队列中去除,通常被设备的 POLL 方法调用,注意如果在 POLL 队列处于工作状态的时候是不能把指定设备去除的,否那么将会出错。 如何在8139CP使用NAPI: 从 POLL 方法的本质意义上来说就在于尽量减少中断的数目,特别在于大量的小长度的数据包的时候,减少中断,以到达不要让整个操作系统花费太多的时间在中断现场的保护和恢复上,以便把赢得的时间用来在我网络层上的处理数据的传输,例如在下面介绍的 8139CP 中断的处理过程中,目的就在于尽快把产生中断的设备挂在 poll_list,并且关闭接收中断,最后直接调用设备的POLL方法来处理数据包的接收,直到收到数据包收无可收,或者是到达一个时间片内的调度完成。 RTL8139C+ 的数据接收环形缓冲队列: RTL8139C+ 的接收方式是一种全新的缓冲方式,能显著的降低CPU接收数据造成的花费,适合大型的效劳器使用,适合 IP,TCP,UDP 等多种方式的数据下载,以及连接 IEEE802.1P,802.1Q,VLAN等网络形式;在 8139CP 中分别有 64 个连续的接收/发送描述符单元,对应三个不同的环形缓冲队列--一个是高优先级传输描述符队列,一个是普通优先级传输符描述队列,一个是接收符描述队列,每个环形缓冲队列右 64 个4个双字的连续描述符组成,每个描述符有 4 个连续的双字组成,每个描述符的开始地址在 256 个字节的位置对齐,接收数据之前,软件需要预先分配一个 DMA 缓冲区,一般对于传输而言,缓冲区最大为 8Kbyte 并且把物理地址链接在描述符的 DMA 地址描述单元,另外还有两个双字的单元表示对应的 DMA 缓冲区的接收状态。 在 /driver/net/8139CP.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\n", dev->name, status, cpr8(Cmd), cpr16(CpCmd)); /*去除NIC中断控制器的内容*/ cpw16(IntrStatus, status & ~cp_rx_intr_mask); spin_lock(&cp->lock); /*接收状态存放器表示有数据包到达*/ if (status & (RxOK | RxErr | RxEmpty | RxFIFOOvr)) { /*把当前的产生中断的NIC设备挂在softnet_data中的POLL队列上,等待网络上层上的应用程序处理*/ if (netif_rx_schedule_prep(dev)) { /*关闭接收中断使能*/ cpw16_f(IntrMask, cp_norx_intr_mask); __netif_rx_schedule(dev); } } /*发送中断的处理过程以及8139C+的专门软中断的处理过程,这里我们不关心*/ if (status & (TxOK | TxErr | TxEmpty | SWInt)) cp_tx(cp); /*如果发生链路变化的情况,需要检查介质无关接口〔MII〕的载波状态同样也发生变化, 否那么就要准备重新启动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=%04x, PCI status=%04x\n", dev->name, status, pci_status); /* TODO: reset hardware */ } spin_unlock(&cp->lock); return IRQ_HANDLED; } 把 NIC 挂在 POLL 队列(poll_list)上 在 8139CP 的中断程序可以看到 __netif_rx_schedule 的调用方式,它把 NIC 设备挂在softnet_data 结构中的 poll_list 队列上,以便及时的返回中断,让专门数据包处理 bottom-half局部来进行处理,我们先来看一下 __netif_rx_schedule 的内部工作流程。 static inline void __netif_rx_schedule(struct net_device *dev) { unsigned long flags; local_irq_save(flags); dev_hold(dev); /*把当前NIC设备挂在POLL〔poll_list〕队列中,等待唤醒软中断以后进行轮询*/ list_add_tail(&dev->poll_list, &__get_cpu_var(softnet_data).poll_list); /*确定当前该设备所要准备接收的包大小*/ if (dev->quota < 0) dev->quota += dev->weight; else dev->quota = dev->weight; /*启动软中断,在表示所有中断的状态字irq_cpustat_t中关于软中断字段__softirq_pending中, 把关于网络轮循接收软中断位置1,等待调度时机来临时候运行该中断的句柄net_rx_action。*/ __raise_softirq_irqoff(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(softnet_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 <= 0 || jiffies - start_time > 1) goto softnet_break; local_irq_enable(); /*从公共的 softnet_data 数据结构中的轮循队列上获得等待轮循的设备结构*/ dev = list_entry(queue->poll_list.next, struct net_device, poll_list); /*调用设备的POLL方法从NIC上的Ring Buffer中读入数据*/ if (dev->quota <= 0 || dev->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 < 0) dev->quota += dev->weight; else dev->quota = dev->weight; } else { /*发生了错误的数据接收状况,或者没有完成"规定"配额的数据接收,并且没有新的数据进来, 这个也可能表示已经完成了传输的过程,调用__netif_rx_complete把网络设备从POLL队列上去除 〔介绍POLL过程的时候详细介绍〕*/ dev_put(dev); local_irq_disable(); } } out: local_irq_enable(); preempt_enable(); return; softnet_break: __get_cpu_var(netdev_rx_stat).time_squeeze++; __raise_softirq_irqoff(NET_RX_SOFTIRQ); goto out; } 8139CP 驱动中的轮询方法 dev->poll 方法: 这个方法通常被网络层在向驱动的接收循环队列获取新的数据包时刻调用,而驱动的接收循环队列中可以向网络层交付的包数量那么在 dev->quota 字段中表示,我们来看 8139cp 中 POLL 的原型: static int cp_rx_poll (struct net_device *dev, int *budget) 参数 budget 的上层任务所需要底层传递的数据包的数量,这个数值不能超过netdev_max_backlog 的值。 总而言之,POLL 方法被网络层调用,只负责按照网络层的要求值〔"预算"值〕提交对应数量的数据包。8139CP 的 POLL 方法注册通常在设备驱动程序模块初始化(调用 probe)的时候进行,如下: static int cp_init_one (struct pci_dev *pdev, const struct pci_device_id *ent) { … … dev->poll = cp_rx_poll; … … } 设备的 POLL 方法正如前所说的是被网络层上的软中断 net_rx_action 调用,我们现在来看具体的流程: static int cp_rx_poll (struct net_device *dev, int *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_interrupt 中断句柄中中断关闭了,现在 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 的环形队列〔rx_ring〕上的最后的数据接收状态,是否有出现接收或者 FIFO 的错误,是否*/ status = le32_to_cpu(desc->opts1); if (status & DescOwn) break; len = (status & 0x1fff) - 4; mapping = cp->rx_skb[rx_tail].mapping; if ((status & (FirstFrag | LastFrag)) != (FirstFrag | LastFrag)) { /* we don't support incoming fragmented frames. * instead, we attempt to ensure that the * pre-allocated RX skbs are properly sized such * that RX fragments are never encountered */ cp_rx_err_acct(cp, rx_tail, status, 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 slot %d status 0x%x len %d\n", 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); /*把新建立的缓冲区的虚拟地址挂在接收缓冲区的队列中,在下一次访问rx_skb数组的这个结构时候, POLL方法会从这个虚拟地址读出接收到的数据包*/ cp->rx_skb[rx_tail].skb = new_skb; /*在cp_rx_skb调用netif_rx_skb,填充接收数据包队列,等待网络层在Bottom half队列中调用ip_rcv接收网络数据, 这个函数替代了以前使用的netif_rx*/ cp_rx_skb(cp, skb, desc); rx++; rx_next: /*把前面映射的物理地址挂在NIC设备的环行队列上〔也就是rx_ring上,它是在和NIC中物理存储区进行了DMA映射的, 而不是驱动在内存中动态建立的〕,准备提交给下层(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递减到0表示这次的POLL传输已经完成了使命, 就等待有数据到来的时候再次唤醒软中断执行POLL方法*/ dev->quota -= rx; *budget -= rx; /* if we did not reach work limit, then we're done with * this round of polling */ if (rx_work) { /*如果仍然有数据到达,那么返回POLL方法循环的开始,继续接收数据*/ if (cpr16(IntrStatus) & cp_rx_intr_mask) goto rx_status_loop; /*这里表示数据已经接收完毕,而且没有新的接收中断产生了,这个时候使能NIC的接收中断, 并且调用__netif_rx_complete把已经完成POLL的设备从poll_list上摘除,等待下一次中断产生的时候, 再次把设备挂上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 的驱动程序和 8139CP 大同小异,只是使用了网络层专门提供的 POLL 方法--proecess_backlog〔/net/dev.c〕,在 NIC 中断接收到了数据包后,调用网络层上的 netif_rx〔/net/dev.c〕将硬件中断中接收到数据帧存入 sk_buff 结构, 然后检查硬件帧头,识别帧类型, 放入接收队列〔softnet_data 结构中的 input_pkt_queue 队列上〕, 激活接收软中断作进一步处理. 软中断函数(net_rx_action)提取接收包,而 process_backlog(也就是 POLL 方法)向上层提交数据。 能让接收速度更快一些吗? 我们现在来思考一下如何提高 NAPI 效率的问题,在说到效率这个问题之前我们先看一下在linux 的文档中 NAPI_HOWTO.txt 中提供一个模型用来构造自己 NIC 的 POLL 方法,不过和 8139 有一些不一样,其中 NIC 设备描述中有一个 dirty_rx 字段是在 8139CP 中没有使用到的。 dirty_rx 就是已经开辟了 sk_buff 缓冲区指针和已经提交到 NIC 的 rx_ring 参与接收的缓冲,但是还没有完成传输的缓冲区和已经完成传输的缓冲区的数量总和,与之相类似的是 cur_rx 这个表示的是下一个参与传输的缓冲区指针,我们在 NAPI_HOWTO.txt 的举例中可以看到这个字段的一些具体使用方式: /*cur_rx为下一个需要参与传输的缓冲区指针, 如果cur_rx指针大于dirty_rx那么表示已经有在rx-ring中开辟的rx-ring中的每个传输缓冲已经被耗尽了, 这个时候需要调用refill_rx_ring 把一些已经向网络层提交了数据的rx-ring接收单元开辟新的缓冲区, 增加dirty_rx的数值,为下一次数据接收做准备,*/ 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之间相差不超过总的rx_ring接收单元的一半, 而且剩下的一半中间有空的传输单元,那么我们不必担忧了,因为还有足够的缓冲区可以使用〔凭经验推断的〕, 就可以退出当前的程序,等待下一次软中断调用POLL来处理在这之间收到的数据, 〔NAPI_HOWTO.txt中是重新启动时钟计数,这样做是在没有使能NIC中断处理的情况下〕*/ if (tp->rx_buffers[tp->dirty_rx % RX_RING_SIZE].skb == NULL) restart_timer(); /*如果执行到这里了,那表示有几种情况可能发生,第一当前的cur_rx和dirty_rx之间相差不超过总的rx_ring接收单元的一半, 调用refill_rx_ring后dirty_rx并未增加,〔也许在rx-ring中大量的单元收到数据没有得到网络层函数的处理〕, 结果dirty_rx没有增加,而且也没有空闲的单元来接收新到的数据,这样就要重新调用netif_rx_schedule 来唤醒软中断, 调用设备的POLL方法,采集在rx-ring的数据。*/ else netif_rx_schedule(dev); /* we are back on the poll list */ 在 RTL-8169 的驱动程序中就使用了 dirty_rx 这个字段,但是在 8139CP 中并未使用,其实这个并非 8139CP 驱动不成熟的表现,大家阅读 NAPI_HOWTO.t展开阅读全文
咨信网温馨提示:1、咨信平台为文档C2C交易模式,即用户上传的文档直接被用户下载,收益归上传人(含作者)所有;本站仅是提供信息存储空间和展示预览,仅对用户上传内容的表现方式做保护处理,对上载内容不做任何修改或编辑。所展示的作品文档包括内容和图片全部来源于网络用户和作者上传投稿,我们不确定上传用户享有完全著作权,根据《信息网络传播权保护条例》,如果侵犯了您的版权、权益或隐私,请联系我们,核实后会尽快下架及时删除,并可随时和客服了解处理情况,尊重保护知识产权我们共同努力。
2、文档的总页数、文档格式和文档大小以系统显示为准(内容中显示的页数不一定正确),网站客服只以系统显示的页数、文件格式、文档大小作为仲裁依据,个别因单元格分列造成显示页码不一将协商解决,平台无法对文档的真实性、完整性、权威性、准确性、专业性及其观点立场做任何保证或承诺,下载前须认真查看,确认无误后再购买,务必慎重购买;若有违法违纪将进行移交司法处理,若涉侵权平台将进行基本处罚并下架。
3、本站所有内容均由用户上传,付费前请自行鉴别,如您付费,意味着您已接受本站规则且自行承担风险,本站不进行额外附加服务,虚拟产品一经售出概不退款(未进行购买下载可退充值款),文档一经付费(服务费)、不意味着购买了该文档的版权,仅供个人/单位学习、研究之用,不得用于商业用途,未经授权,严禁复制、发行、汇编、翻译或者网络传播等,侵权必究。
4、如你看到网页展示的文档有www.zixin.com.cn水印,是因预览和防盗链等技术需要对页面进行转换压缩成图而已,我们并不对上传的文档进行任何编辑或修改,文档下载后都不会有水印标识(原文档上传前个别存留的除外),下载后原文更清晰;试题试卷类文档,如果标题没有明确说明有答案则都视为没有答案,请知晓;PPT和DOC文档可被视为“模板”,允许上传人保留章节、目录结构的情况下删减部份的内容;PDF文档不管是原文档转换或图片扫描而得,本站不作要求视为允许,下载前可先查看【教您几个在下载文档中可以更好的避免被坑】。
5、本文档所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用;网站提供的党政主题相关内容(国旗、国徽、党徽--等)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
6、文档遇到问题,请及时联系平台进行协调解决,联系【微信客服】、【QQ客服】,若有其他问题请点击或扫码反馈【服务填表】;文档侵犯商业秘密、侵犯著作权、侵犯人身权等,请点击“【版权申诉】”,意见反馈和侵权处理邮箱:1219186828@qq.com;也可以拔打客服电话:0574-28810668;投诉电话:18658249818。




NAPI-技术在-Linux-网络驱动上的应用和完善.docx



实名认证













自信AI助手
















微信客服
客服QQ
发送邮件
意见反馈



链接地址:https://www.zixin.com.cn/doc/4763965.html