高性能网络IO框架netmap源码分析.docx

上传人:b****6 文档编号:6796508 上传时间:2023-01-10 格式:DOCX 页数:38 大小:37.04KB
下载 相关 举报
高性能网络IO框架netmap源码分析.docx_第1页
第1页 / 共38页
高性能网络IO框架netmap源码分析.docx_第2页
第2页 / 共38页
高性能网络IO框架netmap源码分析.docx_第3页
第3页 / 共38页
高性能网络IO框架netmap源码分析.docx_第4页
第4页 / 共38页
高性能网络IO框架netmap源码分析.docx_第5页
第5页 / 共38页
点击查看更多>>
下载资源
资源描述

高性能网络IO框架netmap源码分析.docx

《高性能网络IO框架netmap源码分析.docx》由会员分享,可在线阅读,更多相关《高性能网络IO框架netmap源码分析.docx(38页珍藏版)》请在冰豆网上搜索。

高性能网络IO框架netmap源码分析.docx

高性能网络IO框架netmap源码分析

高性能网络I/O框架-netmap源码分析

(1)

作者:

gfree.wind@

博客:

blog.focus-

微博:

QQ技术群:

4367710

前几天听一个朋友提到这个netmap,看了它的介绍和设计,确实是个好东西。

其设计思想与业界不谋而合——因为为了提高性能,几个性能瓶颈放在那里,解决方法自然也是类似的。

由于保密性,我是不可能拿公司的设计来和大家讨论和分享的。

而netmap的出现,提供了这么一个好机会。

它既实现了一个高性能的网络I/O框架,代码量又不算大,非常适合学习和研究。

netmap简单介绍

首先要感谢netmap的作者,创造出了netmap并无私的分享了他的设计和代码。

netmap的文档写得很不错,这里我简单说明一下为什么netmap可以达到高性能。

1.利用mmap,将网卡驱动的ring内存空间映射到用户空间。

这样用户态可以直接访问到原始的数据包,避免了内核和用户态的两次拷贝;——前两天我还想写这么一个东西呢。

2.利用预先分配的固定大小的buff来保存数据包。

这样减少了内核原有的动态分配;——对于网络设备来说,固定大小的内存池比buddy要有效的多。

之前我跟Bean_lee也提过此事呵。

3.批量处理数据包。

这样就减少了系统调用;

更具体的内容,大家直接去netmap的官方网站上看吧,写得很详细。

虽然英文,大家还是耐着性子好好看看,收获良多。

netmap的源码分析

从上面netmap的简单介绍中可以看到,netmap不可避免的要修改网卡驱动。

不过这个修改量很小。

驱动的修改

下面我以e1000.c为例来分析。

由于netmap最早是在FreeBSD上实现的,为了在linux达到最小的修改,使用了大量的宏,这给代码的阅读带来了一些困难。

e1000_probe的修改

俺不是写驱动的。

e1000_probe里面很多代码看不明白,但是不影响我们对netmap的分析。

通过netmap的patch,知道是在e1000完成一系列硬件初始化以后,并注册成功,这时调用e1000_netmap_attach

@@-1175,6+1183,10@@staticint__devinite1000_probe(struct

if(err)

gotoerr_register;

+#ifdefDEV_NETMAP

+e1000_netmap_attach(adapter);

+#endif/*DEV_NETMAP*/

+

/*printbustype/speed/widthinfo*/

e_info(probe,"(PCI%s:

%dMHz:

%d-bit)%pM\n",

((hw->bus_type==e1000_bus_type_pcix)?

"-X":

""),

下面是e1000_netmap_attach的代码

staticvoid

e1000_netmap_attach(structSOFTC_T*adapter)

{

structnetmap_adapterna;

bzero(&na,sizeof(na));

na.ifp=adapter->netdev;

na.separate_locks=0;

na.num_tx_desc=adapter->tx_ring[0].count;

na.num_rx_desc=adapter->rx_ring[0].count;

na.nm_register=e1000_netmap_reg;

na.nm_txsync=e1000_netmap_txsync;

na.nm_rxsync=e1000_netmap_rxsync;

netmap_attach(&na,1);

}

SOFTC_T是一个宏定义,对于e1000,实际上是e1000_adapter,即e1000网卡驱动对应的privatedata。

下面是structnetmap_adapter的定义

/*

*Thisstructextendsthe'structadapter'(or

*equivalent)devicedescriptor.Itcontainsallfieldsneededto

*supportnetmapoperation.

*/

structnetmap_adapter{

/*

*Onlinuxwedonothaveagoodwaytotellifaninterface

*isnetmap-capable.Soweusethefollowingtrick:

*NA(ifp)pointshere,andthefirstentry(whichhopefully

*alwaysexistsandisatleast32bits)containsamagic

*valuewhichwecanusetodetectthattheinterfaceisgood.

*/

uint32_tmagic;

uint32_tna_flags;/*futureplaceforIFCAP_NETMAP*/

intrefcount;/*numberofuser-spacedescriptorsusingthis

interface,whichisequaltothenumberof

structnetmap_ifobjsinthemappedregion.*/

/*

*Theselwakeupintheinterruptthreadcanuseper-ring

*and/orglobalwaitqueues.Wetrackhowmanyclients

*ofeachtypewehavesowecanoptimizethedrivers,

*andespeciallyavoidhugecontentiononthelocks.

*/

intna_single;/*threadsattachedtoasinglehwqueue*/

intna_multi;/*threadsattachedtomultiplehwqueues*/

intseparate_locks;/*setiftheinterfacesuportsdifferent

locksforrx,txandcore.*/

u_intnum_rx_rings;/*numberofadapterreceiverings*/

u_intnum_tx_rings;/*numberofadaptertransmitrings*/

u_intnum_tx_desc;/*numberofdescriptorineachqueue*/

u_intnum_rx_desc;

 

/*tx_ringsandrx_ringsareprivatebutallocated

*asacontiguouschunkofmemory.Eacharrayhas

*N+1entries,fortheadapterqueuesandforthehostqueue.

*/

structnetmap_kring*tx_rings;/*arrayofTXrings.*/

structnetmap_kring*rx_rings;/*arrayofRXrings.*/

NM_SELINFO_Ttx_si,rx_si;/*globalwaitqueues*/

/*copyofif_qflushandif_transmitpointers,tointercept

*packetsfromthenetworkstackwhennetmapisactive.

*/

int(*if_transmit)(structifnet*,structmbuf*);

/*referencestotheifnetanddeviceroutines,usedby

*thegenericnetmapfunctions.

*/

structifnet*ifp;/*adapterisifp->if_softc*/

NM_LOCK_Tcore_lock;/*usedifnodevicelockavailable*/

int(*nm_register)(structifnet*,intonoff);

void(*nm_lock)(structifnet*,intwhat,u_intringid);

int(*nm_txsync)(structifnet*,u_intring,intlock);

int(*nm_rxsync)(structifnet*,u_intring,intlock);

intbdg_port;

#ifdeflinux

structnet_device_opsnm_ndo;

intif_refcount;//XXXadditionsforbridge

#endif/*linux*/

};

从structnetmap_adapter可以看出,netmap的注释是相当详细。

所以后面,我不再列出netmap的结构体定义,大家可以自己查看,免得满篇全是代码。

————这样的注释,有几个公司能够做到?

e1000_netmap_attach完成简单的初始化工作以后,调用netmap_attach执行真正的attach工作。

前者是完成与具体驱动相关的attach工作或者说是准备工作,而后者则是真正的attach。

int

netmap_attach(structnetmap_adapter*na,intnum_queues)

{

intn,size;

void*buf;

/*这里ifnet又是一个宏,linux下ifnet实际上是net_device*/

structifnet*ifp=na->ifp;

if(ifp==NULL){

D("ifpnotset,givingup");

returnEINVAL;

}

/*clearotherfields?

*/

na->refcount=0;

/*初始化接收和发送ring*/

if(na->num_tx_rings==0)

na->num_tx_rings=num_queues;

na->num_rx_rings=num_queues;

/*oneachdirectionwehaveN+1resources

*0..n-1arethehardwarerings

*nistheringattachedtothestack.

*/

/*

这么详细的注释。

还用得着我说吗?

0到n-1的ring是用于转发的ring,而n是本机协议栈的队列

n+1为哨兵位置

*/

n=na->num_rx_rings+na->num_tx_rings+2;

/*netmap_adapter与其ring统一申请内存*/

size=sizeof(*na)+n*sizeof(structnetmap_kring);

/*

这里的malloc,实际上为kmalloc。

这里还有一个小trick。

M_DEVBUF,M_NOWAIT和M_ZERO都是FreeBSD的定义。

那么在linux下怎么使用呢?

我开始以为其被定义为linux对应的flag,如GFP_ATOMIC和__GFP_ZERO,于是grep了M_NOWAIT,也没有找到任何的宏定义。

正在奇怪的时候,想到一种情况。

让我们看看malloc的宏定义

 

/*usevolatiletofixaprobablecompilererroron2.6.25*/

#definemalloc(_size,type,flags)\

({volatileint_v=_size;kmalloc(_v,GFP_ATOMIC|__GFP_ZERO);})

这里type和flags完全没有任何引用的地方。

所以在linux下,上面的M_DEVBUG实际上直接被忽略掉了。

*/

buf=malloc(size,M_DEVBUF,M_NOWAIT|M_ZERO);

if(buf){

/*Linux下重用了structnet_device->ax25_ptr,用其保存buf的地址*/

WNA(ifp)=buf;

/*初始化tx_rings和rx_rings,tx_rings和rx_rings之间用了一个额外的ring分隔,目前不知道这个ring是哨兵呢,还是本主机的ring*/

na->tx_rings=(void*)((char*)buf+sizeof(*na));

na->rx_rings=na->tx_rings+na->num_tx_rings+1;

/*复制netmap_device并设置对应的标志位,用于表示其为netmap_device*/

bcopy(na,buf,sizeof(*na));

NETMAP_SET_CAPABLE(ifp);

na=buf;

/*Corelockinitializedhere.Othersareinitializedafter

*netmap_if_new.

*/

mtx_init(&na->core_lock,"netmapcorelock",MTX_NETWORK_LOCK,

MTX_DEF);

if(na->nm_lock==NULL){

ND("usingdefaultlocksfor%s",ifp->if_xname);

na->nm_lock=netmap_lock_wrapper;

}

}

/*这几行Linux才用的上的代码,是为linux网卡的驱动框架准备的。

未来有用处*/

#ifdeflinux

if(ifp->netdev_ops){

D("netdev_ops%p",ifp->netdev_ops);

/*prepareacloneofthenetdevops*/

na->nm_ndo=*ifp->netdev_ops;

}

na->nm_ndo.ndo_start_xmit=linux_netmap_start;

#endif

D("%sfor%s",buf?

"ok":

"failed",ifp->if_xname);

return(buf?

0:

ENOMEM);

}

完成了netmap_attach,e1000的probe函数e1000_probe即执行完毕。

前面e1000_probe的分析,按照Linux驱动框架,接下来就该e1000_open。

netmap并没有对e1000_open进行任何修改,而是改动了e1000_configure,其会被e1000_open及e1000_up调用。

e1000_configure的修改

按照惯例,还是先看diff文件

@@-393,6+397,10@@staticvoide1000_configure(structe1000

e1000_configure_tx(adapter);

e1000_setup_rctl(adapter);

e1000_configure_rx(adapter);

+#ifdefDEV_NETMAP

+if(e1000_netmap_init_buffers(adapter))

+return;

+#endif/*DEV_NETMAP*/

/*callE1000_DESC_UNUSEDwhichalwaysleaves

*atleast1descriptorunusedtomakesure

*next_to_use!

=next_to_clean*/

从diff文件可以看出,netmap替代了原有的e1000申请ringbuffer的代码。

如果e1000_netmap_init_buffers成功返回,e1000_configure就直接退出了。

接下来进入e1000_netmap_init_buffers:

/*

*Makethetxandrxringspointtothenetmapbuffers.

*/

staticinte1000_netmap_init_buffers(structSOFTC_T*adapter)

{

structe1000_hw*hw=&adapter->hw;

structifnet*ifp=adapter->netdev;

structnetmap_adapter*na=NA(ifp);

structnetmap_slot*slot;

structe1000_tx_ring*txr=&adapter->tx_ring[0];

unsignedinti,r,si;

uint64_tpaddr;

/*

还记得前面的netmap_attach吗?

所谓的attach,即申请了netmap_adapter,并将net_device->ax25_ptr保存了指针,并设置了NETMAP_SET_CAPABLE。

因此这里做一个sanitycheck,以免影响正常的网卡驱动

*/

if(!

na||!

(na->ifp->if_capenable&IFCAP_NETMAP))

return0;

/*e1000_no_rx_alloc如其名,为一个不该调用的函数,只输出一行错误日志*/

adapter->alloc_rx_buf=e1000_no_rx_alloc;

for(r=0;rnum_rx_rings;r++){

structe1000_rx_ring*rxr;

/*初始化对应的netmap对应的ring*/

slot=netmap_reset(na,NR_RX,r,0);

if(!

slot){

D("strange,nullnetmapring%d",r);

return0;

}

/*得到e1000对应的ring*/

rxr=&adapter->rx_ring[r];

for(i=0;icount;i++){

//XXXtheskbcheckandcleanupcangoaway

structe1000_buffer*bi=&rxr->buffer_info[i];

/*将当前的buff索引转换为netmap的buff索引*/

si=netmap_idx_n2k(&na->rx_rings[r],i);

/*获得netmap的buff的物理地址*/

PNMB(slot+si,&paddr);

if(bi->skb)

D("rxbuf%dwasset",i);

bi->skb=NULL;

//netmap_load_map(...)

/*现在网卡的这个buffer已经指向了netmap申请的buff地址了*/

E1000_RX_DESC(*rxr,i)->buffer_addr=htole64(paddr);

}

rxr->next_to_use=0;

/*

下面这几行代码没看明白怎么回事。

有明白的同学指点一下,多谢。

*/

/*preservebuffersalreadymadeavailabletoclients*/

i=rxr->count-1-na->rx_rings[0].nr_hwavail;

if(i<0)

i+=rxr->count;

D("inowis%d",i);

wmb();/*Forcememorywritestocomplete*/

writel(i,hw->hw_addr+rxr->rdt);

}

/*

初始化发送ring,与接收类似.

区别在于没有考虑发送多队列。

难道是因为e1000只可能是接收多队列,发送只可能是一个队列?

这个问题不影响后面的代码阅读。

咱们可以暂时将其假设为e1000只有一个发送队列

*/

/*nowinitializethetxring(s)*/

slot=netmap_reset(na,NR_TX,0,0);

for(i=0;inum_tx_desc;i++){

si=netmap_idx_n2k(&na->tx_rings[0],i);

PNMB(slot+si,&paddr);

//netmap_load_map(...)

E1000_TX_DESC(*txr,i)->buffer_addr=htole64(paddr);

}

return1;

}

e1000cleanrx_irq的修改

@@-3952,6+3973,11@@staticboole1000_clean_rx_irq(structe1

boolcleaned=false;

unsignedinttotal_rx_bytes=0,total_rx_packets=0;

+#ifdefDEV_NETMAP

+ND("callingnetmap_rx_irq");

+if(netmap_rx_irq(netdev,0,work_done))

+return1;/*seemstobeignored*/

+#endif/*DEV_NETMA

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1