papaya内核笔记 3 再谈网卡发包
本帖最后由 karma303 于 2016-07-05 05:18 编辑RTL8139驱动的发包部分原先写好了,也测过了,只是觉得脏,改来改去,今天才得到一个比较舒服的版本。 不过跟linux内核的实现已经相差甚远了。
先贴上一篇写的不错的入门文章m.blog.csdn.net/article/details?id=9191011美中不足的是,它有一段代码分析错了: static void rtl8139_interrupt (int irq, void *dev_instance, struct pt_regs *regs)
{
...
if((isr & TxOK) || (isr & TxErr))
{
while((tp->dirty_tx != tp->cur_tx) || netif_queue_stopped(dev))
{
unsigned int txstatus =
readl(ioaddr + TSD0 + tp->dirty_tx * sizeof(int));
if(!(txstatus & (TxStatOK | TxAborted | TxUnderrun)))
break; /* yet not transmitted */
if(txstatus & TxStatOK) {
LOG_MSG("Transmit OK interrupt\n");
tp->stats.tx_bytes += (txstatus & 0x1fff);
tp->stats.tx_packets++;
}
else {
LOG_MSG("Transmit Error interrupt\n");
tp->stats.tx_errors++;
}
tp->dirty_tx++;
tp->dirty_tx = tp->dirty_tx % NUM_TX_DESC;
if((tp->dirty_tx == tp->cur_tx) & netif_queue_stopped(dev))
{
LOG_MSG("waking up queue\n");
netif_wake_queue(dev);
}
}
}
.......
}
先不管那篇文章怎么分析的,我们自己来看看。
截取的这段代码,是RTL8139发包中断的handler,其中有一个while循环,它是在尽可能多的探测已经空闲(发包完成)的descriptor。
按理说,一个TOK中断,对应着一个descriptor重新可用,为什么要探测更多呢?
很简单,设想这样的情景:我们一口气写了4个descriptor,就是说,一口气启动了4个包的发送,过了一会儿,descriptor 1发包完成了,上报给CPU一个TOK中断,但是,CPU很忙(可能正陷在别的硬中断里),就在这一小会儿,descriptor2,3,4也完成了发包,他们的TOK接踵而至。 只有一个ISR,当然是前浪死在沙滩上,等CPU缓过神,转向网卡的TOK中断routine,好几个TOK中断已经丢失了。
但这不妨事,因为网卡上就4个descriptor,我们逐个检查一下它们各自TSR的某些位,若为1,则表明这个descriptor已经完成发包了。
这就是上面的while循环的目的。在文档里是有说明的。
再说那篇文章,他写的是,“那是因为8139不是在成功发送一个包后发出中断,而是将缓冲区上所有包发走后才发出中断的”。
我不是想接人家的短,只是看到他的错,觉得很欣慰。那个作者心里肯定对"8139为什么有4个descriptor”有一些猜想,所以分析问题时尝试往上靠。 我也一直没明白8139为什么有4个descriptor。
刚才说到文档,其实官网上我没找到,google到的两篇很好的,竟然来自于美国两所大学的网站。phy.duke.edu/~rgb/brahma/Resources/beowulf/linux/drivers/rtl8139.html
cs.usfca.edu/~cruse/cs326/RTL8139_ProgrammersGuide.pdf原先学init进程时,看到一篇国内大学网站上的文章,也很惊艳,是个老师写的。能在大学里学内核,或者教内核,都是挺幸福的。
回到正题。刚才说到那个while循环,手册上有提醒。而且手册上还提醒了第二点:
Case 2: No packet TOK need to be handled, but ISR routine called
就是提醒驱动编写者,有些个TOK中断送到CPU上时,中断例程其实无事可做
什么原因呢?想象这样一个情景:Descriptor A的TOK routine正在运行,此时Descriptor B的TOK发生了,但CPU听不到,因为还在Descriptor A的中断例程里,但是,就在这个例程里,我们能检测到Descriptor B已经可用了(你还记得那个while循环吗),并做相关的处理。等从这次中断routine出去,回立刻陷入Descriptor B的TOK的中断例程。但是,已经无事可做了。驱动编写者要考虑到这一点。
以上是本文的上半部分。
在papaya内核里,我主要是去掉了队列的"STOPPED"标志位,相应的,netif_start_queue, netif_stop_queue等一些函数也就删掉了。因为我想了很久,觉得没必要给tx_queue设置一个"running/stopped"的属性。
那网卡忙碌需要暂停发包时,该怎么通知内核的framework呢。我给start_xmit设计了一个返回值,用来反映当前网卡是否忙碌(是否还有发包的能力)。void nic_wake_queue(struct net_device *netdev){
cli();
if((netdev->flags & IN_WAKE_QUEUE) == 1) return sti();
if(!netdev->tx_queue.root) return sti();
if(netdev->tx_busy(netdev)) return sti();
netdev->flags |= IN_WAKE_QUEUE;
sti();
struct skb_queue *list = &netdev->tx_queue;
struct sk_buff * one;
int code = 0;
while(1){
oprintf(" %u done", netdev->tx_count);
one = list->root;
LL2_POP(list);
cli();
code = netdev->start_xmit(one, netdev);
if(code == -1 || list->root == 0){ //-1 indicates busy
netdev->flags &= ~IN_WAKE_QUEUE;
break;
}
sti();
}
sti();
}
因为不用维护任何全局的flags,代码很简单。
一开始三个if语句,分别是防止自身重入,检测队列空,检测网卡忙。
为什么nic_wake_queue不允许自身重入呢。我其实没有想“自身重入会带来哪些坏处”(我没有脑细胞去想了),因为这个函数没有任何重入的理由,他能在队列异步生长(因为中断尾随的bottom half)的同时很好的工作。
这个函数可以直接由用户调用(通过syscall),也可以在bottom half里由内核routine调用。BH与BH之间不存在重入的可能,因为papaya内核里,BH是严禁嵌套的,连不同channel上的BH都不嵌套。这根早期的linux很像。
重入发生的情形是:用户调用nic_wake_queue,运行期间被硬中断,其尾随的bottom half里,可能会调用nic_wake_queue。像比TOK中断。我们的if就是防止这个。
第二个话题,我在调用start_xmit的前后,用cli()/sti()把它包围起来,这个cli()来的似乎有些早,以后可以优化,但sti()一点儿也不早。start_xmit的返回值非常重要,必须保证它平安的返回。(即,在cli模式下顺利的ret回来)
接下来说说tx_queue的“运行”靠什么维持。
tx_queue会经常暂停,因为网卡忙碌,或者是自身空了。那再次启动靠什么呢?
我的设计是,提交skb到发包队列时,如果发现队列是空的,就手动调用一下nic_wake_queue()。否则,就不用管它,待会儿肯定有TOK中断发生。TOK中断是“驱动”发包队列的主力。
往发包队列里提交skb是个很常用的操作: void waiting_for_transmit(struct sk_buff *skb){
cli();
bool tx_queue_empty = !(bool)skb->dev->tx_queue.root;
LL2_A( &skb->dev->tx_queue, skb);
sti();
if(tx_queue_empty) nic_wake_queue(skb->dev);
}
这段代码不很优雅,就是队列为空,不代表待会儿就没有TOK中断发生了。所以应该再做进一步的检测,不到万不得已,不手动调用。
【关于tx_queue的”排队“】
很多人写了netif_wake_queue之后,大概都希望看到很多skb在tx_queue里排队的情形。实际上,8139发包的速度是很快的,不容易看到”排队“。
要想让包排队,要求是,在你依次写完1,2,3,4号descriptor之后,1号descriptor的包还没发出去。这时候,你再提交的包就只能排队等候了。
我今天是把发包的大小从60byte增加到1500byte,才第一次捕捉到”排队“:因为包大了,发送的相对就慢了。
我测试发送100个包,指定不同的发包长度,看分别有多少个包排队。发包长度(byte)1000 800 700670650 600
排队个数(个/100) 94 92 89 87 0 0以上仅限于真实机器,如果你在QEMU上测,估计永远是瞬间发出去,什么也看不到。我对QEMU是越来越失望了。在考虑是不是写一个e1000的驱动,这样可以回归bochs。
【关于OWN位】
写8139驱动的一个细节。如果你想检测一个descriptor的包是否发出去了,记住,不要检测TSR的OWN位,它置1,仅代表这个包从内存DMA到FIFO里了。并不一定到网线上了。
要检测的是 if(!TSR.TOK && !TSR.TUN && !TSR.TABT)。这三个bit分别代表”发包成功“,"FIFO exhausted", "发包终止"。 如果这三个bit都为0,那说明结果还没出来,包还没发出去。
【8139芯片的word aligned】
驱动提供给8139的内存buffer的起始地址,无论是用来接收,还是发送,都必须4字节对齐。 这是8139芯片要求的。
我开始只是在收包时注意它,发包时没管。
后来发现,我发出去的包,每间隔一个包就是坏包:mac头都是错的。
因为我申请4个发包缓冲区时,不是一个个申请的,而是:
private->tx_buf = kmalloc2( TX_BUF_SIZE * NUM_TX_DESC, __GFP_DMA);
for(int i = 0; i < NUM_TX_DESC; i++){
private->tx_bufs = private->tx_buf + i * TX_BUF_SIZE;
}而我把TX_BUF_SIZE定义成8139发送单包的体积上线:1762。
改成 #define TX_BUF_SIZE (1762 & ~3)就好了。
期待文章更新,咱们社区先加了精华鼓励奖,加油:victory: 现在大量的中断的话,都是轮询+中断,对于网卡来讲是NAPI 本帖最后由 mordorwww 于 2016-06-27 14:28 编辑
mordorwww 发表于 2016-06-27 14:14 static/image/common/back.gif
现在大量的中断的话,都是轮询+中断,对于网卡来讲是NAPI
另外内核里同一个中断是不会嵌套的 ,否则内核栈很容易溢出。
所以在网卡发送中断里,是不会处理下一个descriptor的发送中断的。
内核里同样类型的发送中断是串行处理的,是在软中断里排队串行处理各个descriptor的,此时并不需要中断,所以就没有中断, 也不是中断来同步CPU和网卡,而是由 ring buffer上各个descriptor上的标志位来同步CPU和网卡的。不知道对于其它的高速设备,例如sata驱动和sas驱动是不是也是这样同步的。
实际上,同一类型的硬中断也是串行处理的 allan cruse, 貌似有专门讲intel网卡编程的课。 While the chip is a bus master, it's not a descriptor-based bus master.
这句话怎么理解? The result is that all IP packets must be copied to an alignment buffer before being queued for transmit.
这个copy是软件行为还是硬件行为? 本帖最后由 karma303 于 2016-06-28 23:18 编辑
回复 6# nswcfd
While the chip is a bus master, it's not a descriptor-based bus master.
while表转折吧。
【bus master】
传统的PC体系中,CPU以外的设备是没有“主动权”的,即使它往内存DMA,也是由PC上的DMA控制器主导。
PCI总线标准允许挂在它上面的设备称为"bus master",这种芯片上有专门的电路用来控制DMA过程及对内存总线的征用。
【not descriptor based】
它大概是跟intel 8254x这样的网卡芯片比较,觉得RTL8139太low了。
其实RTL8139的tx部分是基于descriptor的。
回复 7# nswcfd
The result is that all IP packets must be copied to an alignment buffer before being queued for transmit.
整个这段话我都没看懂。只好以后看了。
页:
[1]