DPDK关键技术详解Word文档下载推荐.docx
《DPDK关键技术详解Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《DPDK关键技术详解Word文档下载推荐.docx(41页珍藏版)》请在冰豆网上搜索。
提供应用程序创建和释放用于存储报文信息的缓存块的接口,这些MBUF存储在一内存池中。
提供两种类型的MBUF,一种用于存储一般信息,一种用于存储报文数据。
6、定时器组件:
提供一些异步周期执行的接口(也可以只执行一次),可以指定某个函数在规定的时间异步的执行,就像LIBC中的timer定时器,但是这里的定时器需要应用程序在主循环中周期调用rte_timer_manage来使定时器得到执行,使用起来没有那么方便。
定时器组件的时间参考来自EAL层提供的时间接口。
除了以上六个核心组件外,DPDK还提供以下功能:
1、以太网轮询模式驱动(PMD)架构:
把以太网驱动从内核移到应用层,采用同步轮询机制而不是内核态的异步中断机制来提高报文的接收和发送效率。
2、报文转发算法支持:
Hash库和LPM库为报文转发算法提供支持。
3、网络协议定义和相关宏定义:
基于FreeBSDIP协议栈的相关定义如:
TCP、UDP、SCTP等协议头定义。
4、报文QOS调度库:
支持随机早检测、流量整形、严格优先级和加权随机循环优先级调度等相关QOS功能。
5、内核网络接口库(KNI):
提供一种DPDK应用程序与内核协议栈的通信的方法,类似普通LINUX的TUN/TAP接口,但比TUN/TAP接口效率高。
每个物理网口可以虚拟出多个KNI接口。
以下分章节对各个组件单元进行详细分析。
日志系统篇:
1、全局日志变量rte_logs
structrte_logsrte_logs={
.type=~0,
.level=RTE_LOG_DEBUG,
.file=NULL,
};
该变量用于存储日志文件的的FILE指针、日志打印级别、要记录的日志类型。
2、日志类型:
/*系统内部日志类型*/
#defineRTE_LOGTYPE_EAL0x00000001/**<
Logrelatedtoeal.*/
#defineRTE_LOGTYPE_MALLOC0x00000002/**<
Logrelatedtomalloc.*/
#defineRTE_LOGTYPE_RING0x00000004/**<
Logrelatedtoring.*/
#defineRTE_LOGTYPE_MEMPOOL0x00000008/**<
Logrelatedtomempool.*/
#defineRTE_LOGTYPE_TIMER0x00000010/**<
Logrelatedtotimers.*/
#defineRTE_LOGTYPE_PMD0x00000020/**<
Logrelatedtopollmodedriver.*/
#defineRTE_LOGTYPE_HASH0x00000040/**<
Logrelatedtohashtable.*/
#defineRTE_LOGTYPE_LPM0x00000080/**<
LogrelatedtoLPM.*/
#defineRTE_LOGTYPE_KNI0x00000100/**<
LogrelatedtoKNI.*/
#defineRTE_LOGTYPE_ACL0x00000200/**<
LogrelatedtoACL.*/
#defineRTE_LOGTYPE_POWER0x00000400/**<
Logrelatedtopower.*/
#defineRTE_LOGTYPE_METER0x00000800/**<
LogrelatedtoQoSmeter.*/
#defineRTE_LOGTYPE_SCHED0x00001000/**<
LogrelatedtoQoSportscheduler.*/
/*用户可自定义的日志类型*/
#defineRTE_LOGTYPE_USER10x01000000/**<
User-definedlogtype1.*/
#defineRTE_LOGTYPE_USER20x02000000/**<
User-definedlogtype2.*/
#defineRTE_LOGTYPE_USER30x04000000/**<
User-definedlogtype3.*/
#defineRTE_LOGTYPE_USER40x08000000/**<
User-definedlogtype4.*/
#defineRTE_LOGTYPE_USER50x10000000/**<
User-definedlogtype5.*/
#defineRTE_LOGTYPE_USER60x20000000/**<
User-definedlogtype6.*/
#defineRTE_LOGTYPE_USER70x40000000/**<
User-definedlogtype7.*/
#defineRTE_LOGTYPE_USER80x80000000/**<
User-definedlogtype8.*/
3、日志级别
/*:
Can'
tuse0,asitgivescompilerwarnings*/
#defineRTE_LOG_EMERG1U/**<
Systemisunusable.*/
#defineRTE_LOG_ALERT2U/**<
Actionmustbetakenimmediately.*/
#defineRTE_LOG_CRIT3U/**<
Criticalconditions.*/
#defineRTE_LOG_ERR4U/**<
Errorconditions.*/
#defineRTE_LOG_WARNING5U/**<
Warningconditions.*/
#defineRTE_LOG_NOTICE6U/**<
Normalbutsignificantcondition.*/
#defineRTE_LOG_INFO7U/**<
Informational.*/
#defineRTE_LOG_DEBUG8U/**<
Debug-levelmessages.*/
4、改写系统日志文件
/**
*@paramf
*文件流指针,可以是NULL,如果是NULL,系统使用默认日志文件流,如串口或syslog.
rte_eal_log_init(constchar*id,intfacility)初始化时,已经把默认日专文件流设置为syslog
*@return
*-0onsuccess.
*-Negativeonerror.
*/
intrte_openlog_stream(FILE*f);
5、设计日志打印级别
voidrte_set_log_level(uint32_tlevel);
6、使能某个日志类型,使能之后,可以记录该类型的日志信息。
voidrte_set_log_type(uint32_ttype,intenable);
7、取得当前核中刚刚处理的日志消息类型和级别
每个核处理日志消息时,会记录本该消息的类型和级别到一个变量中。
intrte_log_cur_msg_loglevel(void);
intrte_log_cur_msg_logtype(void);
8、使能LOG存储记录
voidrte_log_set_history(intenable);
9、存储或显示历史记录,这个接口,日志是在标准输出中显示的
intrte_log_add_in_history(constchar*buf,size_tsize);
voidrte_log_dump_history(void);
10、打印一条日志,这里会根据类型和级别,判断是否打印,如果打印,则打印到初始化时所设定的文件中。
#defineRTE_LOG(l,t,...)\
(void)(((RTE_LOG_##l<
=RTE_LOG_LEVEL)&
&
\
(RTE_LOG_##l<
=rte_logs.level)&
(RTE_LOGTYPE_##t&
rte_logs.type))?
\
rte_log(RTE_LOG_##l,\
RTE_LOGTYPE_##t,#t"
:
"
__VA_ARGS__):
\
0)
11、日志打印机制:
上面调用先判断类型和级别,看是否需要记录,如果不需要,就什么都不做,如果需要就调用:
rte_log(uint32_tlevel,uint32_tlogtype,constchar*format,...)-----
rte_vlog(__attribute__((unused))uint32_tlevel,__attribute__((unused))uint32_tlogtype,constchar*format,va_listap)
{
intret;
FILE*f=rte_logs.file;
unsignedlcore_id;
/*记录正在打印的日志的类型和级别*/
lcore_id=rte_lcore_id();
log_cur_msg[lcore_id].loglevel=level;
log_cur_msg[lcore_id].logtype=logtype;
ret=vfprintf(f,format,ap);
/*把日志输出到初始化时定义的文件流中*/
fflush(f);
returnret;
}
初始化时定义的文件流设置如下(发):
/*
*setthelogtodefaultfunction,calledduringealinitprocess,
*oncememzonesareavailable.
int
rte_eal_log_init(constchar*id,intfacility)
FILE*log_stream;
log_stream=fopencookie(NULL,"
w+"
console_log_func);
if(log_stream==NULL)
return-1;
openlog(id,LOG_NDELAY|LOG_PID,facility);
/*这里把默认的LOG文件流定义为fopencookie所设置的文件流*/
if(rte_eal_common_log_init(log_stream)<
return0;
Fopencookie文件流的操作接口定义如下(lib\librte_eal\linuxapp\ealeal_log.c):
staticcookie_io_functions_tconsole_log_func={
.read=console_log_read,
.write=console_log_write,/*这就是vfprintf调用时,真正的写操作接口*/
.seek=console_log_seek,
.close=console_log_close
下面就是写日志时的真正操作了:
staticssize_t
console_log_write(__attribute__((unused))void*c,constchar*buf,size_tsize)
charcopybuf[BUFSIZ+1];
ssize_tret;
uint32_tloglevel;
/*先把日志志信息记录在历史记录中*/
rte_log_add_in_history(buf,size);
/*再把日志输出到标准输出*/
ret=fwrite(buf,1,size,stdout);
fflush(stdout);
/*truncatemessageiftoobig(shouldnothappen)*/
if(size>
BUFSIZ)
size=BUFSIZ;
/*Syslogerrorlevelsarefrom0to7,sosubtract1toconvert*/
loglevel=rte_log_cur_msg_loglevel()-1;
memcpy(copybuf,buf,size);
copybuf[size]='
\0'
;
/*最后,还把信息输出到系统日志中*/
syslog(loglevel,"
%s"
copybuf);
HUGPAGE的使用:
为什么内存要分页?
实践过程中,有这样的问题:
程序需要使用4G内存空间,而物理内存又小于4G,导致程序不得不降低内存占用。
为解决些问题,引入MMU,MMU核心思想是利用虚拟地址代替物理地址,即CPU使用虚拟地址,MMU负责把虚拟地址转成物理地址
内存分页是基于MMU的一种内存管理机制,这种机制,从结构上保证了访问内存的高效,使OS能支持非连续内存的分配。
为了访问更快,硬件上引入TLB(页表寄存器缓冲),但TLB有限,为了减少MISS,引入大页
大页的设置,
echo128>
/sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
mount-thugetlbfs–opagesize=2Mnodev/mnt/hugepages-fs
内存初始化:
1、映射文件总结
DPDK中,有两个核间共享的全局变量,分别是总内存配置信息和页表数组信息,系统中所有核都可以对这两个全局变量进行访问,为了使每个核访问相同变量时的变量地址是一致的,采用映射相同文件的方式来访问相同的变量地址。
总内存配置信息映射文件/var/run/.rte_config
rte_config.mem_config指向总内存配置信息,每个核通过映射/var/run/.rte_config文件来使mem_config指向相同的物理内存。
用structrte_mem_config结构来存储总内存配置信息,其主要包括以下信息:
内存分段信息:
总共可存储256个连续的内存段,保存在structrte_memsegmemseg[RTE_MAX_MEMSEG];
数组中。
这个数组在运行过程中不会变化。
空闲内存段信息free_memseg:
这个一开始就是内存分段的信息,随着运行过程中内存的使用情况,这个信息会做相应的调整。
内存域数组memzone:
这个存储了系统中已分配的内存域,内存域从free_memseg中分配得到,并按分配的先后顺序保存在这个数组中,每一个内存域有一个唯一的名字与其对应。
总共可存储2560个域。
rte_memzone是DPDK内存管理最终向客户程序提供的基础接口,通过ret_memzone_reverse可以获取基于DPDKHUGEPAGE的属于同一个物理CPU的物理内存连续且虚拟内存也连续的一块地址。
rte_ring/rte_malloc/rte_mempool等组件就是依赖于rte_memzone组件实现的。
页表数组信息映射文件/var/run/.rte_hugepage_info:
DPDK中,使用到的内存所对应的页用一个Structhugepage表示,系统中会使用很多内存页,把所有内存页的信息放到一个Structhugepage结构数组进行管理,这里暂时把这个数据叫做页表项数组吧!
保存页表项数据的内存也是采用共享内存形式,通过映射文件/var/run/.rte_hugepage_info来保证各核所访问的内存是相同的。
页表项信息记录了每一页所对应的物理地址、虚拟地址、该页的大小、该页所属的socket_id、该页所挂载到的文件名称,等等。
页表项信息结构如下:
structhugepage{
void*orig_va;
/**<
virtualaddroffirstmmap()orig=1时放在这里*/
void*final_va;
virtualaddrof2ndmmap()虚拟开始地址*/
uint64_tphysaddr;
physicaladdr物理开始地址*/
size_tsize;
thepagesize该页的大小*/
intsocket_id;
NUMAsocketID*/
intfile_id;
the'
%d'
inHUGEFILE_FMT在map_all_hugepages时的顺序*/
/*该页所属的内存段,一个内存段包含的页是相连续的*/
intmemseg_id;
thememorysegmenttowhichpagebelongs*/
/*该大页缓存所挂载到的目录下的文件*/
charfilepath[MAX_HUGEPAGE_PATH];
/*rtemap%s*<
pathtobackingfileonfilesystem*/
其中orig_va在初始化完内存之后全为NULL,不再使用。
页挂载路径/mn/huge/rtesmp_%fileid
由上述页表项信息结构知道,每一个页都挂载到hugepage文件系统中的一个文件中上,文件路径名格式是:
/mn/huge/rtesmp_%fileid
各结构关系图
2、共享内存子系统的初始化
DPDK的共享内存是使用系统中的大页内存来实现的,对共享内存的管理,其实就是管理这些大页内存,所谓共享内存初始化的主要工作,就是把系统中的大页内存的信息(如内存大小,相应的物理地址等)采集并存储在DPDK自已所设计的一系例结构体变量上,通过这些结构体变量,DPDK可以按照自已的方式来管理系统中的内存分配和申请。
DPDK通过以下结构来管理共享内存:
系统大页文件系统信息结构:
用于存储采集到的系统中的各种大页文件系统的页面大小和页面数。
系统中可能有不同页面大小的大页文件系统,如页面大小为2M(32位机)或1G(64位机),路径/sys/kernel/mm/hugepages/保存了系统中所有大页面文件系统的信息。
DPDK用数组internal_config.hugepage_info[MAX_HUGEPAGE_SIZES]来保存所有不同页面大小的HUGETLBFS。
structhugepage_info{
size_thugepage_sz;
页面大小*/
constchar*hugedir;
目录/MNT/HUGE//dirwherehugetlbfsismounted*/
//一个NUMANODE包含多个socket,一个socket包含多个核,可以把一个socket看成
//是一个物理CPU,同一个socket中的内存是共享的,
//这里表示不同物理CPU上的页数
uint32_tnum_pages[RTE_MAX_NUMA_NODES];
/**<
numberofhugepagesofthatsizeoneachsocket*/
intlock_descriptor;
filedescriptorforhugepagedir大页目录的文件锁*/
页信息结构:
用于保存页面的物理地址、映射到进程中的虚拟地址、页面大小、页面所属物理CPU、页面对应的MMAP文件等信息。
DPDK用这个结构体数组来保存DPDK使用到的所有页面的信息。
我们在这里把这个结构体数组称为页表,每一个表项表示一个页。
DPDK把页表保存到了共享内存映射文件/var/run/.rte_hugepage_info,不同的进程通过访问这个文件,都可以得到这个全局的页表。
virtualaddroffirstmmap()只在页面信息建立过程中使用,*/
void*f