高性能网络IO框架netmap源码分析.docx
《高性能网络IO框架netmap源码分析.docx》由会员分享,可在线阅读,更多相关《高性能网络IO框架netmap源码分析.docx(38页珍藏版)》请在冰豆网上搜索。
![高性能网络IO框架netmap源码分析.docx](https://file1.bdocx.com/fileroot1/2023-1/10/d1536c51-7cd4-4c25-b051-79e36c03c84c/d1536c51-7cd4-4c25-b051-79e36c03c84c1.gif)
高性能网络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