开始写之前先记下几个与题目无关的知识吧,是在看代码的时候查到的:
首先是一个在内核代码中经常出现的宏:likely() and unlikely(), google了一下(看的资料为http://www.soidc.net/articles/1215484977397/20091220/1215945676298_1.html),明白了在内核代码中使用这两个宏,主要的目的是为了进行代码的优化,提高系统执行速度。
比如 :
if (likely(a>b)) { fun1();}if (unlikely(a<b)) { fun2();}
这里就是程序员可以确定 a>b 在程序执行过程中出现的可能相比较大,因此使用了likely()告诉编译器将fun1()函数的二进制代码紧跟在前面程序的后面,这样就cache在预取数据时就可以将fun1()函数的二进制代码拿到cache中。这样,也就增加了cache的命中率。
同样的,unlikely()的作用就是告诉编译器,a<b 的可能性很小所以这里在编译时,将fun2()的二进制代码尽量不要和前边的编译在一块。
另外一个是BUG_ON(摘自http://dev.firnow.com/course/6_system/linux/Linuxjs/20110109/552421.html), 它是一个函数接口,一般开发人员自己实现相关函数内容,一般用来判断内核是否出现问题,如果参数为真的话,证明内核出现了bug,打印BUG信息,然后调用PANIC函数,让系统panic。
还有一个就是我们在编内核的时候首先要先make menuconfig, 在menuconfig那个界面中有很多选项和说明,那么这些选项和说明是在哪里定义的呢?我们自己可不可以改或者加呢?我记得在《Linux那些事儿之我是PCI》一书中有看到相关的内容,这些内容是在每个文件夹下的Kconfig文件中定义的,把它们打开来看下也就一目了然了。对了,顺便再推荐下《Linux那些事儿之我是xxx》系列丛书吧,是fudanABC大牛写的,真的是写的太好了,那年看那个《..我是USB》看到实在是神魂颠倒,能把如此枯燥的驱动写成如小说一般实在是XXX啊。。。
好了,接下来是正题了,说实话,之前看代码看得真的是头都大了,特别是在netback里面,实在是搞不明白为什么在tx_submit()函数里面会有一个netif_rx()函数,而且之前完全没有看出backend里面是那里有把packet传出去,当时猜想rx这个函数把packet反正那个buffer上,然后由native的devicedriver进行轮询再发出去,不过当时也找不到真正的实现是怎么样的,由于知识的缺陷也不敢确定,也有很多迷惑的地方,之后实在没有办法了就发邮件给了netback.c的作者K.A.Fraser,没想到他如此之快就回复了,而且彻底让我搞懂了这是怎么回事,不愧是大牛啊!而且通过之后再查了一些资料,把其中Bridge-network这一点搞懂后,对于frontend和backend,以及backend和physicalinterface之间的联系终于差不多搞明白了,不知道是否完全正确,反正先记录下来,如果发现之后还有错误的地方再来回过来比较修改吧~~
按照我的看法,真实情况应该是这样子的:
首先要有一张在脑中的图(摘自AchievingHigh Throughput by Transparent Network Interface Virtualization on Multi-coreSystems.pdf):
也就是说netfront和netback之间是通过I/Oring、grant table和event-channel进行交互的,而netback和dom0中真实的devicedriver之间有一个bridge进行交互。
先从简单的开始,来看下右半边是如何实现的吧,我主要是想从代码角度来分析,可能比较枯燥,不过我想应该是最直观的了:
就从domU的一个数据包是如何发出去说起吧,首先在domU的xennet_netdev_ops中注册了一个函数:
.ndo_start_xmit = xennet_start_xmit,
上层应用需要发包的话会调用网络设备相对应的.ndo_start_xmit,即上面的这个函数:
static int xennet_start_xmit(struct sk_buff *skb, struct net_device *dev)
461 static int xennet_start_xmit(struct sk_buff *skb, struct net_device *dev) {462 unsigned short id;463 struct netfront_info *np = netdev_priv(dev);464 struct xen_netif_tx_request *tx;465 struct xen_netif_extra_info *extra;466 char *data = skb->data;467 RING_IDX i;468 grant_ref_t ref;469 unsigned long mfn;470 int notify;471 int frags = skb_shinfo(skb)->nr_frags;472 unsigned int offset = offset_in_page(data);473 unsigned int len = skb_headlen(skb);474475 frags += DIV_ROUND_UP(offset + len, PAGE_SIZE);476 if (unlikely(frags > MAX_SKB_FRAGS + 1)) {477 printk(KERN_ALERT "xennet: skb rides the rocket: %d frags/n",478 frags);479 dump_stack();480 goto drop;481 }482483 spin_lock_irq(&np->tx_lock);484485 if (unlikely(!netif_carrier_ok(dev) ||486 (frags > 1 && !xennet_can_sg(dev)) ||487 netif_needs_gso(dev, skb))) {488 spin_unlock_irq(&np->tx_lock);489 goto drop;490 }491492 i = np->tx.req_prod_pvt;493494 id = get_id_from_freelist(&np->tx_skb_freelist, np->tx_skbs);495 np->tx_skbs[id].skb = skb;496497 tx = RING_GET_REQUEST(&np->tx, i);498499 tx = RING_GET_REQUEST(&np->tx, i);500501 tx->id = id;502 ref = gnttab_claim_grant_reference(&np->gref_tx_head);503 BUG_ON((signed short)ref < 0);504 mfn = virt_to_mfn(data);505 gnttab_grant_foreign_access_ref(506 ref, np->xbdev->otherend_id, mfn, GNTMAP_readonly);507 tx->gref = np->grant_tx_ref[id] = ref;508 tx->offset = offset;509 tx->size = len;510 extra = NULL;511512 tx->flags = 0;513 if (skb->ip_summed == CHECKSUM_PARTIAL)514 /* local packet? */515 tx->flags |= NETTXF_csum_blank | NETTXF_data_validated;516 else if (skb->ip_summed == CHECKSUM_UNNECESSARY)517 /* remote but checksummed. */518 tx->flags |= NETTXF_data_validated;519520 if (skb_shinfo(skb)->gso_size) {521 struct xen_netif_extra_info *gso;522523 gso = (struct xen_netif_extra_info *)524 RING_GET_REQUEST(&np->tx, ++i);525526 if (extra)527 extra->flags |= XEN_NETIF_EXTRA_FLAG_MORE;528 else529 tx->flags |= NETTXF_extra_info;530531 gso->u.gso.size = skb_shinfo(skb)->gso_size;532 gso->u.gso.type = XEN_NETIF_GSO_TYPE_TCPV4;533 gso->u.gso.pad = 0;534 gso->u.gso.features = 0;
在该函数中,主要先是写一个tx_request,并将访问它的权限赋给dom0,然后对包进行下处理,包括他checksum的选项,和extrainfo,主要是GSO(Generic Segmentation Offload)选项,顺便提下,该选项是为了把segmentation offload让硬件来做,为了减小内存的拷贝,系统会讲一个大包传到网卡上,当由于ethernet层有MTU这种限制,包必须被分成几个小包,即segmentation。而以该信息相关的就是一个叫xen_netif_extra_info的结构,如果需要GSO的话,则会在这个extra_info中记录每个分开的fragment应该是多大等等信息,之后start_xmit()中还会调用一个函数,xennet_make_frags(),会将跨page的requests分成多个fragments,然后放入np->tx_skbs中,之后就调用了RING_PUSH_REQUESTS_AND_CHECK_NOTIFY这个宏,也就是将request放到I/O ring中,之后根据返回的notify变量调用notify_remote_via_irq(np->netdev->irq),这个时候就发了一个hypercall陷到xen里面,这个时候netfront的任务就结束了,这个时候就轮到netback出场了,在netback.c中定义了一个tasklet,即将net_tx_action()设为net_tx_tasklet,之后在每次 netif_schedule_work之后,都会调用到这个函数,1408 /* Called after netfront has transmitted */1409 static void net_tx_action(unsigned long unused)1410 {1411 unsigned nr_mops;1412 int ret;14131414 if (dealloc_cons != dealloc_prod) 1415 net_tx_action_dealloc();14161417 nr_mops = net_tx_build_mops();14181419 if (nr_mops == 0)1420 return;14211422 ret = HYPERVISOR_grant_table_op(GNTTABOP_map_grant_ref,1423 tx_map_ops, nr_mops);1424 BUG_ON(ret);14251426 net_tx_submit();1427 } 在tx_action里,首先调用到的是一个net_tx_action_dealloc,这个函数我现在还不是很了解是干什么的,我想因该是处理已经完成的request,然后调用一下make_tx_response,在I/O ring上写上response,但是在这之前应该得先把这个packet发出去啊,不过调用tx_action_dealloc这个函数是在dealloc_cons != dealloc_prod的条件下进入的,所以一开始是不会进去的,所以可以先来看下之后的函数,其实这之间主要就是繁杂的数据操作了,主要是netback通过I/O ring获得tx_request以及里面包含的数据在grant_table中的reference和相应的operations,通过hypercall对数据进行map,最后tx_action函数调用到了net_tx_submit()这个函数,这个函数中对数据进行了处理和分析,最后调用到了netif_rx()函数,按照Fraser的说法:The 'tx' and 'rx' naming scheme in netback is used from the point of view of the client VMs -- when a VM transmits a packet, netback processes it via net_tx_action, and injects it into domain-0 kernel's network stack for forwarding (usually via a software ethernet bridge to a physical network interface). Netif_Rx() is nothing more than the interface provided for network drivers to stuff packets up into the kernel's network stack.不过这里还有一个问题,既然netif_rx()都是将packet放入kernel network stack中,那么从外部来的和从domU来的都要放到这个栈上,那么又是如何将这两个区分开来的呢?同样的,Fraser也回答了:> one is when the package coming from outside network to the dom0(at this routine, the package just need to > spread up to be processed by CPU),Yes, the call to netif_rx() comes from within the physical networkinterface's device driver.> the other is from domU to dom0(where the dom0 need to send it out),
This is the netif_rx() call from within netback driver> so at what level that dom0 will recognize the different source of the package and handle it in two distinct ways? or > the two different routine just stuff the packages up to different stack?
It's all the same network stack. The stack will inspect packet headers to decide where to forward the packet. In the common scenario in which packets are forwarded by etherbridge, the etherbridge code will inspect the destination MAC address and compare against its forwarding table其他问题都搞明白了,就还剩下最后一个问题了,也是Fraser说的:How an Ethernet bridge works is something you can go read up on.http://wiki.xensource.com/xenwiki/XenNetworking#head-d7bb01c62c0cc1b20602dbeca666ec5be873e099这一篇xenwiki的文章说的很清楚,包括bridge,route等等机制具体是如何实现的,我想等有机会再用自己的话来描述一遍吧~今天先写到这,还有receive的部分,这是接下来的任务@@
版权声明:本文为博主原创文章,未经博主允许不得转载。 |