dpdk内存管理内存初始化
说明:本系列博文源代码均来自dpdk17。021。1内存初始化1。1。1hugepage技术
hugepage(2M1G。。)相对于普通的page(4K)来说有几个特点:
(1)hugepage这种页面不受虚拟内存管理影响,不会被替换(swap)出内存,而普通的4kpage如果物理内存不够可能会被虚拟内存管理模块替换到交换区。
(2)同样的内存大小,hugepage产生的页表项数目远少于4kpage。
举一个例子,用户进程需要使用4M大小的内存,如果采用4Kpage,需要1K的页表项存放虚拟地址到物理地址的映射关系,而采用hugepage2M只需要产生2条页表项,这样会带来两个后果,一是使用hugepage的内存产生的页表比较少,这对于数据库系统等动不动就需要映射非常大的数据到进程的应用来说,页表的开销是很可观的,所以很多数据库系统都采用hugepage技术。二是tlb冲突率大大减少,tlb驻留在cpu的1级cache里,是芯片访问最快的缓存,一般只能容纳100多条页表项,如果采用hugepage,则可以极大减少
tlbmiss导致的开销:tlb命中,立即就获取到物理地址,如果不命中,需要查rc3进程页目录表pgd进程页中间表pmd进程页框物理内存,如果这中间pmd或者页框被虚拟内存系统替换到交互区,则还需要交互区load回内存总之,tlbmiss是性能大杀手,而采用hugepage可以有效降低tlbmiss。
linux使用hugepage的方式比较简单,以2M的hugepage为例:
1。syskernelmmhugepageshugepages2048kB通过修改这个目录下的文件可以修改hugepage页面的大小和总数目;
2。mountthugetlbfsnodevmnthugelinux将hugepage实现为一种文件系统hugetlbfs,需要将该文件系统mount到某个文件;
3。mmapmnthuge在用户进程里通过mmap映射hugetlbfsmount的目标文件,这个mmap返回的地址就是大页面的了。
1。1。2多进程共享
mmap系统调用可以设置为共享的映射,dpdk的内存共享就依赖于此,在这多个进程中,分为两种角色,第一种是主进程(RTEPROCPRIMARY),第二种是从进程(RTEPROCSECONDARY)。主进程只有一个,必须在从进程之前启动,负责执行DPDK库环境的初始化,从进程attach到主进程初始化的DPDK上,主进程先mmaphugetlbfs文件,构建内存管理相关结构将这些结构存入hugetlbfs上的配置文件rteconfig,然后其他进程mmaprteconfig文件,获取内存管理结构,dpdk采用了一定的技巧,使得最终同样的共享物理内存在不同进程内部对应的虚拟地址是完全一样的,意味着一个进程内部的基于dpdk的共享数据和指向这些共享数据的指针,可以在不同进程间通用。
1。1。3相关数据结构
lrteconfig
内存全局配置结构。
1)rteconfig是每个程序私有的数据结构,这些东西都是每个程序的私有配置。
2)lcorerole:这个DPDK程序使用c参数设置的它同时跑在哪几个核上。
3)masterlcore:DPDK的架构上,每个程序分配的lcorerole有一个主核,对使用者来说影响不大。
4)lcorecount:这个程序可以使用的核数。
5)processtype:DPDK多进程:一个程序是主程序,否则初始化DPDK内存表,其他从程序使用这个表。RTEPROCPRIMARYRTEPROCSECONDARY
6)memconfig:指向设备各个DPDK程序共享的内存配置结构,这个结构被mmap到文件varrun。rteconfig,通过这个方式多进程实现对memconfig结构的共享。
lhugepagefile
这个是structhugepage数组,每个structhugepagefile都代表一个hugepage页面,存储的每个页面的物理地址和程序的虚拟地址的映射关系。然后,把整个数组映射到文件varrun。rtehugepageinfo,同样这个文件也是设备共享的,主从进程都能访问它。
1)fileid:每个文件在hugepage文件系统中都有一个编号,就是数组1N;
2)filepath:ssmapfileidmount的hugepage文件系统中的文件路径名;
3)size:这个hugepage页面的size,2M还是1G;
4)socketid:这个页面属于那个CPUsocket。
5)Physaddr:这个hugepage页面的物理地址
6)origva:它和finalva一样都是指这个huagepage页面的虚拟地址。这个地址是主程序初始化huagepage用的,后来就没用了。
7)finalva:这个最终这个页面映射到主从程序中的虚拟地址。
首先因为整个数组都映射到文件里面,所有的程序之间都是共享的。主程序负责初始化这个数组,首先在它内部通过mmap把所有的hugepage物理页面都映射到虚存空间里面,然后把这种映射关系保存到这个文件里面。从程序启动的时候,读取这个文件,然后在它内存也创建和它一模一样的映射,这样的话,整个DPDK管理的内存在所有的程序里面都是可见,而且地址都一样。
在对各个页面的物理地址份配虚拟地址时,DPDK尽可能把物理地址连续的页面分配连续的虚存地址上,这个东西还是比较有用的,因为CPUcache内存控制器的等等看到的都是物理内存,我们在访问内存时,如果物理地址连续的话,性能会高一些。至于到底哪些地址是连续的,那些不是连续的,DPDK在这个结构之上又有一个新的结构rtememconfig。memseg来管理。因为rtememconfig也映射到文件里面,所有的程序都可见rtememconfig。memseg结构。
lrtememconfig
这个数据结构mmap到文件varrun。rteconfig中,主从进程通过这个文件访问实现对这个数据结构的共享。在每个程序内,使用rteconfig。memconfig访问这个结构。
lrtememseg
memseg数组是维护物理地址的,在上面讲到structhugepage结构对每个hugepage物理页面都存储了它在程序里面的虚存地址。memseg数组的作用是将物理地址、虚拟地址都连续的hugepage,并且都在同一个socket,pagesize也相同的hugepage页面集合,把它们都划在一个memseg结构里面,这样做的好处就是优化内存。
rtememseg这个结构也很简单:
1)physaddr:这个memseg的包含的所有的hugepage页面的起始物理地址;
2)addr:这些hugepage页面的起始的虚存地址;
3)len:这个memseg的包含的空间size
4)hugepagesz;这些页面的size2M1G?
这些信息都是从hugepage页表数组里面获得的。1。2dpdk内存初始化源码解析
lrteealinit
这个函数是dpdk运行环境初始化入口函数。
整个内存初始化的代码流程如上图所示,下面我们逐个分析。
lealhugepageinfoinit
这个函数较为简单,主要是遍历系统的syskernelmmhugepages目录建立对应的数据结构。系统支持的每种size的hugepage类型在syskernelmmhugepages目录下都对应一个子目录。例如系统支持2M和1G的大页,就会有对应内目录如下图所示:
而其中每个目录就会对应一个structhugepageinfo的结构,其结构如下,记录着对应目录下的信息。那么目录下都有什么信息呢?如下图所示:
所以structhugepageinfo中也是记录的这些信息,包括当前size的hugepage页面总个数(nrhugepages),已经还没有被分配的个数(freehugepages)等。所有structhugepageinfo构成一个数组,保存在structinternalconfig结构中。
源码如下:intealhugepageinfoinit(void){constchardirentstarttext〔〕hugepages;constsizetdirentstartlensizeof(direntstarttext)1;unsignedi,numsizes0;DIRdir;structdirentdirent;diropendir(sysdirpath);syskernelmmhugepagesif(dirNULL)rtepanic(Cannotopendirectorystoreadsystemhugepageinfo,sysdirpath);for(direntreaddir(dir);dirent!NULL;direntreaddir(dir)){structhugepageinfohpi;if(strncmp(direntdname,direntstarttext,direntstartlen)!0)continue;if(numsizesMAXHUGEPAGESIZES)break;hpiinternalconfig。hugepageinfo〔numsizes〕;hpihugepageszrtestrtosize(direntdname〔direntstartlen〕);hpihugedirgethugepagedir(hpihugepagesz);first,checkifwehaveamountpointif(hpihugedirNULL){uint32tnumpages;numpagesgetnumhugepages(direntdname);if(numpages0)RTELOG(NOTICE,EAL,PRIu32hugepagesofsizePRIu64reserved,butnomountedhugetlbfsfoundforthatsize,numpages,hpihugepagesz);continue;}trytoobtainawritelockhpilockdescriptoropen(hpihugedir,ORDONLY);ifblockinglockfailedif(flock(hpilockdescriptor,LOCKEX)1){RTELOG(CRIT,EAL,Failedtolockhugepagedirectory!);break;}clearoutthehugepagesdirfromunusedpagesif(clearhugedir(hpihugedir)1)break;fornow,putallpagesintosocket0,latertheywillbesorted这里还没有按socket统计页数,将内存页数直接记录到hupageinfo的numpages〔0〕里面了hpinumpages〔0〕getnumhugepages(direntdname);ifndefRTEARCH64for32bitsystems,limitnumberofhugepagesto1GBperpagesizehpinumpages〔0〕RTEMIN(hpinumpages〔0〕,RTEPGSIZE1Ghpihugepagesz);endifnumsizes;}closedir(dir);somethingwentwrong,andwebrokefromtheforloopaboveif(dirent!NULL)return1;internalconfig。numhugepagesizesnumsizes;sortthepagedirectoryentriesbysize,largesttosmallestqsort(internalconfig。hugepageinfo〔0〕,numsizes,sizeof(internalconfig。hugepageinfo〔0〕),comparehpi);nowwehaveallinfo,checkwehaveatleastonevalidsizefor(i0;inumsizes;i)if(internalconfig。hugepageinfo〔i〕。hugedir!NULLinternalconfig。hugepageinfo〔i〕。numpages〔0〕0)return0;novalidhugepagemountsavailable,returnerrorreturn1;}
每一类所有内存页,也分处在哪个socket上(不明白的查看NUMA相关知识补齐)的,hugepageinfo中统计内存页数会按属于处在哪个socket上进行统计,但在这一步(ealhugepageinfoinit)中,还区分不了每个页处在哪个socket上,因此这里还没有按socket统计页数,所以就暂时将内存页数直接记录到hupageinfo的numpages〔0〕里面了。
这里有一个特别注意的点就是getnumhugepages获取的页面数量是怎么计算来的。这里就不将这个函数展开了,根据其内部实现,其返回的页面个数为freehugepagesresvhugepages。也就是说,这里获取的是整个系统的可用hugepage页面数。所以后面进行mmap时也是用的这个值,就是会对整个系统的可用页面数进行mmap。这就有个问题,我们知道dpdk进程启动会传入一个指定的内存大小参数,dpdk进程完全只需要分配及mmap这个内存大小就可以了,为什么还要mmap整个系统的的页面呢?这是为了在整个系统层面最大限度找到连续的物理内存,dpdk进程需要尽可能使用连续的内存来提高性能,当然多余的mmap内存会被dpdkunmmap掉,这个见后文分析。
lrteconfiginitstaticvoidrteconfiginit(void){rteconfig。processtypeinternalconfig。processtype;switch(rteconfig。processtype){caseRTEPROCPRIMARY:rteealconfigcreate();break;caseRTEPROCSECONDARY:rteealconfigattach();rteealmcfgwaitcomplete(rteconfig。memconfig);rteealconfigreattach();break;caseRTEPROCAUTO:caseRTEPROCINVALID:rtepanic(Invalidprocesstype);}}
DPDK多进程状态下,分为RTEPROCPRIMARY进程及RTEPROCSECONDARY进程,RTEPROCPRIMARY负责初始化内存,RTEPROCSECONDARY获取RTEPROCPRIMARY内存映射的信息,创建与RTEPROCPRIMARY一样的内存映射。这是DPDK多进程共享内存的方式。此处先不展开描述。随着流程的展开,自然会明白。
lrteealconfigcreate
创建structrtememconfig结构,并mmap到文件varrun。rteconfig中。staticvoidrteealconfigcreate(void){voidrtememcfgaddr;intretval;constcharpathnameealruntimeconfigpath();varrunif(internalconfig。noshconf)return;maptheconfigbeforehugepageaddresssothatwedontwasteapageif(internalconfig。basevirtaddr!0)rtememcfgaddr(void)RTEALIGNFLOOR(internalconfig。basevirtaddrsizeof(structrtememconfig),sysconf(SCPAGESIZE));elsertememcfgaddrNULL;if(memcfgfd0){memcfgfdopen(pathname,ORDWROCREAT,0660);if(memcfgfd0)rtepanic(Cannotopensforrtememconfig,pathname);}使用mmap分配内存,一般都是先open一个文件,然后调用ftruncate进行文件大小设置,最后进行mmapretvalftruncate(memcfgfd,sizeof(rteconfig。memconfig));if(retval0){close(memcfgfd);rtepanic(Cannotresizesforrtememconfig,pathname);}retvalfcntl(memcfgfd,FSETLK,wrlock);if(retval0){close(memcfgfd);rteexit(EXITFAILURE,Cannotcreatelockons。Isanotherprimaryprocessrunning?,pathname);}rtememcfgaddrmmap(rtememcfgaddr,sizeof(rteconfig。memconfig),PROTREADPROTWRITE,MAPSHARED,memcfgfd,0);if(rtememcfgaddrMAPFAILED){rtepanic(Cannotmmapmemoryforrteconfig);}memcpy(rtememcfgaddr,earlymemconfig,sizeof(earlymemconfig));rteconfig。memconfig(structrtememconfig)rtememcfgaddr;storeaddressoftheconfigintheconfigitselfsothatsecondaryprocessescouldlatermaptheconfigintothisexactlocationrteconfig。memconfigmemcfgaddr(uintptrt)rtememcfgaddr;}
lrteealmemoryinit
根据进程是否为RTEPROCPRIMARY分别调用rteealhugepageinit和rteealhugepageattach函数。
lrteealhugepageinit
这个函数是内存初始化的重点,整体流程如下:
我们分段分析。intrteealhugepageinit(void){structrtememconfigmcfg;structhugepagefilehugepageNULL,tmphpNULL;structhugepageinfousedhp〔MAXHUGEPAGESIZES〕;uint64tmemory〔RTEMAXNUMANODES〕;unsignedhpoffset;inti,j,newmemseg;intnrhugefiles,nrhugepages0;voidaddr;testprocpagemapreadable();memset(usedhp,0,sizeof(usedhp));获取rteconfigmemconfigmcfgrteealgetconfiguration()memconfig;hugetlbfscanbedisabledif(internalconfig。nohugetlbfs){addrmmap(NULL,internalconfig。memory,PROTREADPROTWRITE,MAPPRIVATEMAPANONYMOUS,0,0);if(addrMAPFAILED){RTELOG(ERR,EAL,s:mmap()failed:s,func,strerror(errno));return1;}mcfgmemseg〔0〕。physaddr(physaddrt)(uintptrt)addr;mcfgmemseg〔0〕。addraddr;mcfgmemseg〔0〕。hugepageszRTEPGSIZE4K;mcfgmemseg〔0〕。leninternalconfig。memory;mcfgmemseg〔0〕。socketid0;return0;}
函数一开始,将rteconfiginit函数获取的配置结构放到本地变量mcfg上,然后检查系统是否开启hugetlbfs,如果不开启,则直接通过系统的malloc函数申请配置需要的内存,然后跳出这个函数。calculatetotalnumberofhugepagesavailable。atthispointwehaventyetstartedsortingthemsotheyallareonsocket0for(i0;i(int)internalconfig。numhugepagesizes;i){meanwhile,alsoinitializeusedhphugepagesizesinusedhpusedhp〔i〕。hugepageszinternalconfig。hugepageinfo〔i〕。hugepagesz;nrhugepagesinternalconfig。hugepageinfo〔i〕。numpages〔0〕;}allocateamemoryareaforhugepagetable。thisisntsharedmemoryyet。duetothefactthatweneedsomeprocessingdoneonthesepages,sharedmemorywillbecreatedatalaterstage。注意这里分配内存使用的不是hugepage,后面会将这些结构拷贝到hugepage,这里内存会被释放tmphpmalloc(nrhugepagessizeof(structhugepagefile));if(tmphpNULL)gotofail;memset(tmphp,0,nrhugepagessizeof(structhugepagefile));hpoffset0;wherewestartthecurrentpagesizeentrieshugeregistersigbus();
计算系统中的hugepage个数,存放在nrhugepages中。然后分配structhugepagefile的数组,每个结构对应一个hugepage页面信息。注意这个数组的内存还不在hugepage的共享内存中,而是普通的进程私有内存。
点击(此处)折叠或打开mapallhugepagesandsortthem遍历每种size的hugepage,如1G,2M的一页for(i0;i(int)internalconfig。numhugepagesizes;i){unsignedpagesold,pagesnew;structhugepageinfohpi;wedontyetmarkhugepagesasusedatthisstage,sowejustmapallhugepagesavailabletothesystemallhugepagesarestilllocatedonsocket0hpiinternalconfig。hugepageinfo〔i〕;if(hpinumpages〔0〕0)如果这种size的hugepage的个数为0,则跳过continue;mapallhugepagesavailablepagesoldhpinumpages〔0〕;为每个hugepage创建文件并mmap,相关hugepage信息保存在tmphp〔hpoffset〕开启的数组pagesnewmapallhugepages(tmphp〔hpoffset〕,hpi,1);if(pagesnewpagesold){说明有写页面没有mmap成功,可能中间有其他进程占用了RTELOG(DEBUG,EAL,dnotdhugepagesofsizeuMBallocated,pagesnew,pagesold,(unsigned)(hpihugepagesz0x100000));intpagespagesoldpagesnew;nrhugepagespages;hpinumpages〔0〕pagesnew;更新可用页面为mmap成功的页面if(pagesnew0)continue;}查询每个hugepage起始的物理地址,记录在hugepagefile。physaddrfindphysicaladdressesandsocketsforeachhugepageif(findphysaddrs(tmphp〔hpoffset〕,hpi)0){RTELOG(DEBUG,EAL,FailedtofindphysaddrforuMBpages,(unsigned)(hpihugepagesz0x100000));gotofail;}查询每个hugepage所在的socket,记录在hugepagefile。socketidif(findnumasocket(tmphp〔hpoffset〕,hpi)0){RTELOG(DEBUG,EAL,FailedtofindNUMAsocketforuMBpages,(unsigned)(hpihugepagesz0x100000));gotofail;}根据物理地址对tmphp中每个hugepage进行排序qsort(tmphp〔hpoffset〕,hpinumpages〔0〕,sizeof(structhugepagefile),cmpphysaddr);remapallhugepages对排序好的hugepage再次进行mmapif(mapallhugepages(tmphp〔hpoffset〕,hpi,0)!hpinumpages〔0〕){RTELOG(ERR,EAL,FailedtoremapuMBpages,(unsigned)(hpihugepagesz0x100000));gotofail;}解除第一次mmap关系unmaporiginalmappingsif(unmapallhugepagesorig(tmphp〔hpoffset〕,hpi)0)gotofail;wehaveprocessedanumofhugepagesofthissize,soincoffsethpoffsethpinumpages〔0〕;更新hpoffset,每次循环处理一个size的所有hugepage}
构建hugepage结构数组分下面几步:
(1)循环遍历系统所有的hugetlbfs文件系统,一般来说,一个系统只会使用一种hugetlbfs,所以这一层的循环可以认为没有作用,一种hugetlbfs文件系统对应的基础数据包括:页面大小,比如2M,页面数目,比如2K个页面;
(2)其次,将特定的hugetlbfs的全部页面映射到本进程,放到本进程的hugepage数组管理,这个过程主要由mapallhugepages函数完成,第一次映射的虚拟地址存放在hugepage结构的origva变量;
(3)遍历hugepage数组,找到每个虚拟地址对应的物理地址和所属的物理cpu,将这些信息也记入hugepage数组,物理地址记录在hugepage结构的phyaddr变量,物理cpu号记录在hugepage结构的socketid变量;
(4)跟据物理地址大小对hugepage数组做排序;
(5)根据排序结果重新映射,这个也是由函数mapallhugepages完成,重新映射后的虚拟地址存放在hugepage结构的finalva变量;
(6)将第一次映射关系解除,即将origva变量对应的虚拟地址空间返回给内核。
下面看mapallhugepages的实现过程。
lmapallhugepagesstaticunsignedmapallhugepages(structhugepagefilehugepgtbl,structhugepageinfohpi,intorig){intfd;unsignedi;voidvirtaddr;voidvmaaddrNULL;sizetvmalen0;遍历每个hugepage页面for(i0;ihpinumpages〔0〕;i){uint64thugepageszhpihugepagesz;if(orig){如果是第一次调用这个函数hugepgtbl〔i〕。fileidi;hugepage页面的编号hugepgtbl〔i〕。sizehugepagesz;ealgethugefilepath(hugepgtbl〔i〕。filepath,sizeof(hugepgtbl〔i〕。filepath),hpihugedir,hugepgtbl〔i〕。fileid);构造hugepage对应的磁盘文件名称,如:mnthugeretmap0hugepgtbl〔i〕。filepath〔sizeof(hugepgtbl〔i〕。filepath)1〕;}elseif(vmalen0){第二次映射调用,且第一次进入循环unsignedj,numpages;reserveavirtualareafornextcontiguousphysicalblock:countthenumberofcontiguousphysicalpages。遍历hugepage页面,找物理内存最大的连续区间for(ji1;jhpinumpages〔0〕;j){if(hugepgtbl〔j〕。physaddr!hugepgtbl〔j1〕。physaddrhugepagesz)break;}所有的已分配物理页未必连续,这里只是找最大的连续物理内存区间numpagesji;连续页面的个数vmalennumpageshugepagesz;连续页面的大小getthebiggestvirtualmemoryareauptovmalen。Ifitfails,vmaaddrisNULL,soletthekernelprovidetheaddress。vmaaddrgetvirtualarea(vmalen,hpihugepagesz);申请和连续物理内存同样大小的连续虚拟地址空间if(vmaaddrNULL)vmalenhugepagesz;}trytocreatehugepagefilefdopen(hugepgtbl〔i〕。filepath,OCREATORDWR,0600);if(fd0){RTELOG(DEBUG,EAL,s():openfailed:s,func,strerror(errno));returni;}mapthesegment,andpopulatepagetables,thekernelfillsthissegmentwithzeros第一次mmap时vmaaddr为NULL,内核会自动选取mmap虚拟地址,第二次vmaaddr是计算出来的virtaddrmmap(vmaaddr,hugepagesz,PROTREADPROTWRITE,MAPSHAREDMAPPOPULATE,fd,0);if(virtaddrMAPFAILED){RTELOG(DEBUG,EAL,s():mmapfailed:s,func,strerror(errno));close(fd);returni;}if(orig){如果是第一次映射,映射虚拟地址保存在origvahugepgtbl〔i〕。origvavirtaddr;}else{第二次映射,映射虚拟地址保存在finalvahugepgtbl〔i〕。finalvavirtaddr;}if(orig){Inlinux,hugetlblimitations,likecgroup,areenforcedatfaulttimeinsteadofmmap(),evenwiththeoptionofMAPPOPULATE。KernelwillsendaSIGBUSsignal。Toavoidtobekilled,savestackenvironmenthere,ifSIGBUShappens,wecanjumpbackhere。if(hugewrapsigsetjmp()){RTELOG(DEBUG,EAL,SIGBUS:CannotmmapmorehugepagesofsizeuMB,(unsigned)(hugepagesz0x100000));munmap(virtaddr,hugepagesz);close(fd);unlink(hugepgtbl〔i〕。filepath);returni;}(int)virtaddr0;}setsharedflockonthefile。if(flock(fd,LOCKSHLOCKNB)1){RTELOG(DEBUG,EAL,s():Lockingfilefailed:s,func,strerror(errno));close(fd);returni;}close(fd);vmaaddr(char)vmaaddrhugepagesz;vmalenhugepagesz;}returni;}
这个函数是复用的,共有两次调用。对于第一次调用,就是根据hugetlbfs文件系统的页面数m,构造m个文件名称并创建文件,每个文件对应一个大页面,然后通过mmap系统调用映射到进程的一块虚拟地址空间,并将虚拟地址存放在hugepage结构的origva地址上。如果该hugetlbfs有1K个页面,最终会在hugetlbfs挂载的目录上生成1K个文件,这1K个文件mmap到进程的虚拟地址由进程内部的hugepage数组维护对于第二次调用,由于hugepage数组已经基于物理地址排序,这些有序的物理地址可能有2种情况,一种是连续的,另一种是不连续的,这时候的调用会遍历这个hugepage数组,然后统计连续物理地址的最大内存,这个统计有什么好处?因为第二次的映射需要保证物理内存连续的其虚拟内存也是连续的,在获取了最大连续物理内存大小后,比如是100个页面大小,会调用getvirtualarea函数向内涵申请100个页面大小的虚拟空间,如果成功,说明虚拟地址可以满足,然后循环100次,每次映射mmap的首个参数就是getvirtualarea函数返回的虚拟地址i页面大小,这样,这100个页面的虚拟地址和物理地址都是连续的,虚拟地址存放到finalva变量上。
那么究竟是如何找到连续的虚拟地址空间呢?staticvoidgetvirtualarea(sizetsize,sizethugepagesz){voidaddr;intfd;longalignedaddr;if(internalconfig。basevirtaddr!0){addr(void)(uintptrt)(internalconfig。basevirtaddrbaseaddroffset);}elseaddrNULL;RTELOG(DEBUG,EAL,Askavirtualareaof0xzxbytes,size);fdopen(devzero,ORDONLY);if(fd0){RTELOG(ERR,EAL,Cannotopendevzero);returnNULL;}do{注意这里使用的私有映射addrmmap(addr,(size)hugepagesz,PROTREAD,MAPPRIVATE,fd,0);if(addrMAPFAILED)sizehugepagesz;}while(addrMAPFAILEDsize0);未必有这么大的连续虚拟空间,所以如果失败需要减少虚拟空间大小if(addrMAPFAILED){close(fd);RTELOG(ERR,EAL,Cannotgetavirtualarea:s,strerror(errno));returnNULL;}munmap(addr,(size)hugepagesz);close(fd);alignaddrtoahugepagesizeboundaryalignedaddr(long)addr;alignedaddr(hugepagesz1);alignedaddr((hugepagesz1));addr(void)(alignedaddr);RTELOG(DEBUG,EAL,Virtualareafoundatp(size0xzx),addr,size);incrementoffsetbaseaddroffsetsize;returnaddr;}
下面看findphysaddr的实现过程。
lfindphysaddr
这个函数的作用就是找到hugepage数组里每个虚拟地址对应的物理地址,并存放到phyaddr变量上,最终实现由函数rtememvirt2phy(constvoidvirt)函数实现,其原理相当于页表查找,主要是通过linux的页表文件procselfpagemap实现。procselfpagemap页表文件记录了本进程的页表,即本进程虚拟地址到物理地址的映射关系,主要是通过虚拟地址的前面若干位定位到物理页框,然后物理页框虚拟地址偏移构成物理地址,其实现如下physaddrtrtememvirt2phy(constvoidvirtaddr){intfd,retval;uint64tpage,physaddr;unsignedlongvirtpfn;intpagesize;offtoffset;whenusingdom0,procselfpagemapalwaysreturns0,checkindpdkmemorybybrowsingthememsegsif(rtexendom0supported()){structrtememconfigmcfg;structrtememsegmemseg;unsignedi;mcfgrteealgetconfiguration()memconfig;for(i0;iRTEMAXMEMSEG;i){memsegmcfgmemseg〔i〕;if(memsegaddrNULL)break;if(virtaddrmemsegaddrvirtaddrRTEPTRADD(memsegaddr,memseglen)){returnmemsegphysaddrRTEPTRDIFF(virtaddr,memsegaddr);}}returnRTEBADPHYSADDR;}Cannotparseprocselfpagemap,noneedtologerrorseverywhereif(!procpagemapreadable)returnRTEBADPHYSADDR;standardpagesizepagesizegetpagesize();fdopen(procselfpagemap,ORDONLY);if(fd0){RTELOG(ERR,EAL,s():cannotopenprocselfpagemap:s,func,strerror(errno));returnRTEBADPHYSADDR;}virtpfn(unsignedlong)virtaddrpagesize;offsetsizeof(uint64t)virtpfn;if(lseek(fd,offset,SEEKSET)(offt)1){RTELOG(ERR,EAL,s():seekerrorinprocselfpagemap:s,func,strerror(errno));close(fd);returnRTEBADPHYSADDR;}retvalread(fd,page,PFNMASKSIZE);close(fd);if(retval0){RTELOG(ERR,EAL,s():cannotreadprocselfpagemap:s,func,strerror(errno));returnRTEBADPHYSADDR;}elseif(retval!PFNMASKSIZE){RTELOG(ERR,EAL,s():readdbytesfromprocselfpagemapbutexpectedd:,func,retval,PFNMASKSIZE);returnRTEBADPHYSADDR;}thepfn(pageframenumber)arebits054(seepagemap。txtinlinuxDocumentation)physaddr((page0x7fffffffffffffULL)pagesize)((unsignedlong)virtaddrpagesize);returnphysaddr;}
lfindnumasocket
下面看findnumasocket的实现过程这个函数的作用是找到hugepage数组里每个虚拟地址对应的物理cpu号,基本原理是通过linux提供的procselfnumamaps文件,
procselfnumamaps文件记录了本进程的虚拟地址与物理cpu号(多核系统)的对应关系,在遍历的时候将非hugepage的虚拟地址过滤掉,剩下的虚拟地址与hugepage数组里的origva比较,实现如下:staticintfindnumasocket(structhugepagefilehugepgtbl,structhugepageinfohpi){intsocketid;charend,nodestr;unsignedi,hpcount0;uint64tvirtaddr;charbuf〔BUFSIZ〕;charhugedirstr〔PATHMAX〕;FILEf;ffopen(procselfnumamaps,r);if(fNULL){RTELOG(NOTICE,EAL,cannotopenprocselfnumamaps,considerthatallmemoryisinsocketid0);return0;}snprintf(hugedirstr,sizeof(hugedirstr),ss,hpihugedir,internalconfig。hugefileprefix);parsenumamapwhile(fgets(buf,sizeof(buf),f)!NULL){ignorenonhugepageif(strstr(buf,huge)NULLstrstr(buf,hugedirstr)NULL)continue;getzoneaddrvirtaddrstrtoull(buf,end,16);if(virtaddr0endbuf){RTELOG(ERR,EAL,s():errorinnumamapsparsing,func);gotoerror;}getnodeid(socketid)nodestrstrstr(buf,N);if(nodestrNULL){RTELOG(ERR,EAL,s():errorinnumamapsparsing,func);gotoerror;}nodestr2;endstrstr(nodestr,);if(endNULL){RTELOG(ERR,EAL,s():errorinnumamapsparsing,func);gotoerror;}end〔0〕;endNULL;socketidstrtoul(nodestr,end,0);if((nodestr〔0〕)(endNULL)(end!)){RTELOG(ERR,EAL,s():errorinnumamapsparsing,func);gotoerror;}ifwefindthispageinourmappings,setsocketidfor(i0;ihpinumpages〔0〕;i){voidva(void)(unsignedlong)virtaddr;if(hugepgtbl〔i〕。origvava){hugepgtbl〔i〕。socketidsocketid;hpcount;}}}if(hpcounthpinumpages〔0〕)gotoerror;fclose(f);return0;error:fclose(f);return1;}
sortbyphysaddr根据hugepage结构的phyaddr排序,比较简单unmapallhugepagesorig调用mumap系统调用将hugepage结构的origva虚拟地址返回给内核。
上面几步就完成了hugepage数组的构造,现在这个数组对应了某个hugetlbfs系统的大页面,数组的每一个节点是一个hugepage结构,该结构的phyaddr存放着该页面的物理内存地址,finalva存放着phyaddr映射到进程空间的虚拟地址,socketid存放着物理cpu号,如果多个hugepage结构的finalva虚拟地址是连续的,则其phyaddr物理地址也是连续的。
下面是rteealhugepageinit函数的余下部分,我们知道之前的进程是对整个系统的可用页面进行mmap,但是我们进程实际并不需要这么多内存,所以需要对多余的内存进行释放,接下来的一段代码就是在做这个工作。if(internalconfig。memory0internalconfig。forcesockets0)internalconfig。memoryealgethugepagememsize();nrhugefilesnrhugepages;cleanoutthenumbersofpages清除hugepageinfo中的page数量信息,因为之前将所有hugepage记录在了socket0上for(i0;i(int)internalconfig。numhugepagesizes;i)for(j0;jRTEMAXNUMANODES;j)internalconfig。hugepageinfo〔i〕。numpages〔j〕0;根据之前查找的每个page的socket信息,重新更新每个socket上的hugepage计数gethugepagesforeachsocketfor(i0;inrhugefiles;i){intsockettmphp〔i〕。socketid;findahugepageinfowithrightsizeandincrementnumpagesconstintnbhpsizesRTEMIN(MAXHUGEPAGESIZES,(int)internalconfig。numhugepagesizes);for(j0;jnbhpsizes;j){if(tmphp〔i〕。sizeinternalconfig。hugepageinfo〔j〕。hugepagesz){internalconfig。hugepageinfo〔j〕。numpages〔socket〕;}}}memory〔i〕记录着当前socket所需要申请的内存数量,这是通过参数指定的makeacopyofsocketmem,neededfornumberofpagescalculationfor(i0;iRTEMAXNUMANODES;i)memory〔i〕internalconfig。socketmem〔i〕;calculatefinalnumberofpages这个函数会根据当前进程指定所需要的实际内存大小计算出所需要实际的hugepage页面数量,之前我们mmap了系统的所有free的页面,但程序实际并不需要这么多,所以我们需要计算出实际需要的页面数量,而usedhp记录了实际需要内存大小的hugepage信息nrhugepagescalcnumpagespersocket(memory,internalconfig。hugepageinfo,usedhp,internalconfig。numhugepagesizes);errorifnotenoughmemoryavailableif(nrhugepages0)gotofail;reportingfor(i0;i(int)internalconfig。numhugepagesizes;i){for(j0;jRTEMAXNUMANODES;j){if(usedhp〔i〕。numpages〔j〕0){RTELOG(DEBUG,EAL,RequestingupagesofsizeuMBfromsocketi,usedhp〔i〕。numpages〔j〕,(unsigned)(usedhp〔i〕。hugepagesz0x100000),j);}}}创建存放hugepagefile结构的共享内存文件,注意这里nrhugefiles是系统所有可用的页面数,而不仅是程序所需要的页面数createsharedmemoryhugepagecreatesharedmemory(ealhugepageinfopath(),nrhugefilessizeof(structhugepagefile));if(hugepageNULL){RTELOG(ERR,EAL,Failedtocreatesharedmemory!);gotofail;}memset(hugepage,0,nrhugefilessizeof(structhugepagefile));unmappagesthatwewontneed(looksatusedhp)。also,setsfinalvatoNULLonpagesthatwereunmapped。对之前多mmap的页面进行unmmap,毕竟我们不需要那么多内存if(unmapunneededhugepages(tmphp,usedhp,internalconfig。numhugepagesizes)0){RTELOG(ERR,EAL,Unmappingandlockinghugepagesfailed!);gotofail;}copystufffrommallocdhugepagetotheactualsharedmemory。thisprocedureonlycopiesthosehugepagesthathavefinalvanotNULL。hasoverflowprotection。将hugepagefile数组拷贝到共享内存文件,注意只拷贝finalva不为NULL的结构,而前面我们已经将不需要的页面设置为NULL,所以这里只拷贝的是程序实际所需要的页面结构if(copyhugepagestosharedmem(hugepage,nrhugefiles,tmphp,nrhugefiles)0){RTELOG(ERR,EAL,Copyingtablestosharedmemoryfailed!);gotofail;}如果设置了internalconfig。hugepageunlink,则将程序使用的页面mmap文件进行unlink,即删除磁盘文件,注意这并不影响程序对内存的使用freethehugepagebackingfilesif(internalconfig。hugepageunlinkunlinkhugepagefiles(tmphp,internalconfig。numhugepagesizes)0){RTELOG(ERR,EAL,Unlinkinghugepagefilesfailed!);gotofail;}freethetemporaryhugepagetablefree(tmphp);tmphpNULL;
这一步之后,就会创建磁盘文件。rtehugepageinfo,其中存放着当前进程实际使用的的hugepage信息。并释放hugepage数组,其他进程通过映射hugepageinfo文件就可以获取hugepage数组,从而管理hugepage共享内存。
下面是rteealhugepageinit函数的最后一部分。主要分两个方面,一是将hugepage数组里属于同一个物理cpu,物理内存连续的多个hugepage用一层memseg结构管理起来。一个memseg结构维护的内存必然是同一个物理cpu上的,虚拟地址和物理地址都连续的内存,最终的memzone接口是通过操作memseg实现的;2是将hugepage数组和memseg数组的信息记录到共享文件里,方便从进程获取;firstmemsegindexshallbe0afterincrementingitbelowj1;for(i0;inrhugefiles;i){newmemseg0;ifthisisanewsection,createanewmemsegif(i0)newmemseg1;elseif(hugepage〔i〕。socketid!hugepage〔i1〕。socketid)newmemseg1;elseif(hugepage〔i〕。size!hugepage〔i1〕。size)newmemseg1;elseif((hugepage〔i〕。physaddrhugepage〔i1〕。physaddr)!hugepage〔i〕。size)newmemseg1;elseif(((unsignedlong)hugepage〔i〕。finalva(unsignedlong)hugepage〔i1〕。finalva)!hugepage〔i〕。size)newmemseg1;if(newmemseg){新建的memseg,用首个hugepage作为初始值j1;if(jRTEMAXMEMSEG)break;mcfgmemseg〔j〕。physaddrhugepage〔i〕。physaddr;mcfgmemseg〔j〕。addrhugepage〔i〕。finalva;mcfgmemseg〔j〕。lenhugepage〔i〕。size;mcfgmemseg〔j〕。socketidhugepage〔i〕。socketid;mcfgmemseg〔j〕。hugepageszhugepage〔i〕。size;}continuationofpreviousmemsegelse{非新建的memsegmcfgmemseg〔j〕。lenmcfgmemseg〔j〕。hugepagesz;}hugepage〔i〕。memsegidj;将memsegid放在hugepage中}
这个函数后,整个内存状态如下所示。
lrteealmemzoneinit
最后我们看rteealmemzoneinit函数,这个函数内部主要调用了rteealmallocheapinit,我们直接看这个函数。intrteealmallocheapinit(void){structrtememconfigmcfgrteealgetconfiguration()memconfig;unsignedmscnt;structrtememsegms;if(mcfgNULL)return1;遍历所有memseg,对每个memseg调用mallocheapaddmemsegfor(msmcfgmemseg〔0〕,mscnt0;(mscntRTEMAXMEMSEG)(mslen0);mscnt,ms){mallocheapaddmemseg(mcfgmallocheaps〔mssocketid〕,ms);}return0;}
而mallocheapaddmemseg主要是为这个memseg创建相应的内存管理结构。
lmallocheapaddmemsegExpandtheheapwithamemseg。Thisreservesthezoneandsetsadummymallocelemheaderattheendtopreventoverflow。Therestofthezoneisaddedtofreelistasasinglelargefreeblockstaticvoidmallocheapaddmemseg(structmallocheapheap,structrtememsegms){allocatethememoryblockheaders,oneatend,oneatstartstartelem位于这个memseg(一段连续物理内存)的首部structmallocelemstartelem(structmallocelem)msaddr;endelem位于这个memseg(一段连续物理内存)的尾部(还留了MALLOCELEMOVERHEAD的空间防止越界)structmallocelemendelemRTEPTRADD(msaddr,mslenMALLOCELEMOVERHEAD);endelemRTEPTRALIGNFLOOR(endelem,RTECACHELINESIZE);elemsize为这段连续内存的大小constsizetelemsize(uintptrt)endelem(uintptrt)startelem;初始化startelem成员,其state设置为ELEMFREEmalloceleminit(startelem,heap,ms,elemsize);初始化endelem,其state设置为ELEMBUSY,其pre指向startelemmallocelemmkend(endelem,startelem);根据elemsize从freeheadfreehead中找到合适的idx指向startelemmallocelemfreelistinsert(startelem);heaptotalsizeelemsize;}
这段代码执行后,数据结构及内存关系就如下图所示。
下面说下如何根据elemsize(也就是memseg对应的连续内存大小)从freeheadfreehead中找到合适的idx的。freeheadfreehead按照连续内存大小,划分问若干个链表,如下所示:
Exampleelementsizerangesforaheapwithfivefreelists:
heapfreehead〔0〕(0,28〕
heapfreehead〔1〕(28,210〕
heapfreehead〔2〕(210,212〕
heapfreehead〔3〕(212,214〕
heapfreehead〔4〕(214,MAXSIZE〕
原文链接:http:blog。chinaunix。netuid28541347id5763610。html原文作者:lvyilong316
妇女节,聆听他们对她的告白春风十里不如她三八妇女节,聆听他们对她的告白世界上若没有女人,这世界至少要失去十分之五的真,十分之六的善,十分之七的美。冰心正是三月春光美,春水初生,春……
想吃油条不用买!掌握这个做法和配方,保证你做一次成功大家好,我是小阿亮美食,今天跟大家分享一道美食,炸油条。碗中倒入350克的面粉,打入一颗鸡蛋、一勺盐、3克的酵母粉、2克的小苏打,再加入2克的五粒泡打粉,将所有的食材搅拌混合,……
一张豆腐皮半斤五花肉,在家做好吃的豆皮肉卷,给年夜饭加菜豆腐皮肉卷是大家都很喜欢吃的一种美食,在外面买太贵不划算,今天我来分享一下家常做法,足不出户就能吃到美味可口的豆皮肉卷。家常版豆皮肉卷怎么做来看下制作清单,1。主料……
西安美食丨西安人平时都吃什么1。肉夹馍肉夹馍是陕西著名小吃,来西安必吃的美食之一。馍外观焦黄,条纹清晰,内部呈层状,饼体发胀,皮酥里嫩,肉是用腊汁煮出来的肉,味道非常棒。没有哪个西安本地人没吃……
青海最值得推荐的九大美食青海人的饮食口味比较具有民族特色,色香味形都有,所以很多青海美食都遵循了乡俗,有机会去到青海一定要吃地道的青海美食。这九大美食尤其不错。1。青海酿皮酿皮在青海是很有……
知青的白鱼岭之恋(79获得生活的激情)我们经常在一起谈论我们的国家,与西方相比落后了半个世纪。据说我们从日本进口一台机器,工程师拆下来研究一阵就再也装不上去,就算好不容易安装好,但是加工的精密度降低了。日本人知道了……
我的孩子犯了错,那就是你老师没教好!终身成长词典已上线17213000词条今天是精读君陪伴你终身成长的第2906天家庭教育是人生整个教育的基础和起点。郝滨啪啪啪伴随着玻璃杯摔碎的声音,我吃惊地望……
推特公司安插堂兄弟,谁批评我封你号,马斯克我的地盘我做主今年7月份,《福布斯》评选出全球13位科技界领袖人物,马斯克位列其中,毫无悬念。马斯克拥有9家公司,马斯克的科技帝国涵盖汽车、航天航空、光伏发电、卫星系统、脑机接口、人工……
推荐5000毫安电池的四部手机,让你告别充电焦虑大家也知道电池容量越小,使用的时间越短,大家也是都喜欢电池容量大的手机,这样使用的时间也长,这样就不用一天三充了,为此各家厂商也做出了许多努力,小编给大家盘点几部5000毫安电……
被鲸鱼吞大肚子活下来的可能性有多大?长见识了!被鲸鱼吞下肚子活下来的可能性有多大?长见识了。鲸鱼是一种体型巨大的动物,食量也非常大,一口能吞下不少鱼虾。那么,如果鲸鱼一口把一个人吞下去,这个人活着的几率有多大呢?鲸鱼……
这届年轻人,都说没年味了年味是什么?没有花招,没有噱头,人们却愿意赋予它美好的词汇。它在每一个细节里,闪动着对生活对家一种执着的微光,年味一来,就连空气中都蕴含着喜庆的味道。它有滋有……
烟熏妆就是田小娟美貌的开关吧?编辑:小颜最近(G)IDLE回归了,新歌水平再一次起飞,可以说这一次的舞台非常讨小颜我的喜欢!并且全员的造型也都很出彩,团内的成员我们写过不少,这一次我……