如何在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 缓冲区的接收状态。 54com.cn
在 /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)) { 中国网管论坛bbs.bitsCN.com
/*把当前的产生中断的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);
54ne.com
/* 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;/*表示队列的最大长度*/
/*锁定当前线程,多处理器的情况之下不能被其他处理器中断处理*/ 54com.cn
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缓冲区数目,这个参数很重要在高速处理的时候有需要慎重优化这个数值,在有大量数据接收的情况下,需要增加该数值)*/
中国网管联盟www_bitscn_com
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 的原型:
中国网管联盟www、bitsCN、com
static int cp_rx_poll (struct net_device *dev, int *budget)
参数 budget 的上层任务所需要底层传递的数据包的数量,这个数值不能超过netdev_max_backlog 的值 【转自】
阅读(752) | 评论(0) | 转发(0) |