*说明:本系列博文源代码均来自dpdk17.02*
1.1内存初始化
1.1.1 hugepage技术
hugepage(2M/1G..)相对于普通的page(4K)来说有几个特点:
(1) hugepage 这种页面不受虚拟内存管理影响,不会被替换(swap)出内存,而普通的4kpage 如果物理内存不够可能会被虚拟内存管理模块替换到交换区。
(2) 同样的内存大小,hugepage产生的页表项数目远少于4kpage.
举一个例子,用户进程需要使用 4M 大小的内存,如果采用4Kpage, 需要1K的页表项存放虚拟地址到物理地址的映射关系,而采用hugepage 2M 只需要产生2条页表项,这样会带来两个后果,一是使用hugepage的内存产生的页表比较少,这对于数据库系统等动不动就需要映射非常大的数据到进程的应用来说,页表的开销是很可观的,所以很多数据库系统都采用hugepage技术。二是tlb冲突率大大减少,tlb 驻留在cpu的1级cache里,是芯片访问最快的缓存,一般只能容纳100多条页表项,如果采用hugepage,则可以极大减少
tlb miss 导致的开销:tlb命中,立即就获取到物理地址,如果不命中,需要查 rc3->进程页目录表pgd->进程页中间表pmd->进程页框->物理内存,如果这中间pmd或者页框被虚拟内存系统替换到交互区,则还需要交互区load回内存。。总之,tlb miss是性能大杀手,而采用hugepage可以有效降低tlb miss 。
linux 使用hugepage的方式比较简单,以2M的hugepage为例:
\1. /sys/kernel/mm/hugepages/hugepages-2048kB/ 通过修改这个目录下的文件可以修改hugepage页面的大小和总数目;
\2. mount -t hugetlbfs nodev /mnt/huge linux将hugepage实现为一种文件系统hugetlbfs,需要将该文件系统mount到某个文件;
\3. mmap /mnt/huge 在用户进程里通过mmap 映射hugetlbfs mount 的目标文件,这个mmap返回的地址就是大页面的了。
1.1.2 多进程共享
mmap 系统调用可以设置为共享的映射,dpdk的内存共享就依赖于此,在这多个进程中,分为两种角色,第一种是主进程(RTE_PROC_PRIMARY),第二种是从进程(RTE_PROC_SECONDARY)。主进程只有一个,必须在从进程之前启动,负责执行DPDK库环境的初始化,从进程attach到主进程初始化的DPDK上,主进程先mmap hugetlbfs 文件,构建内存管理相关结构将这些结构存入hugetlbfs 上的配置文件rte_config,然后其他进程mmap rte_config文件,获取内存管理结构,dpdk采用了一定的技巧,使得最终同样的共享物理内存在不同进程内部对应的虚拟地址是完全一样的,意味着一个进程内部的基于dpdk的共享数据和指向这些共享数据的指针,可以在不同进程间通用。
(免费订阅,永久学习)学习地址: Dpdk/网络协议栈/vpp/OvS/DDos/NFV/虚拟化/高性能专家-学习视频教程-腾讯课堂
更多DPDK相关学习资料有需要的可以自行报名学习,免费订阅,永久学习,或点击这里加qun免费
领取,关注我持续更新哦! !
1.1.3 相关数据结构
l rte_config
内存全局配置结构。
\1) rte_config 是每个程序私有的数据结构,这些东西都是每个程序的私有配置。
\2) lcore_role:这个DPDK程序使用-c参数设置的它同时跑在哪几个核上。
\3) master_lcore:DPDK的架构上,每个程序分配的lcore_role 有一个主核,对使用者来说影响不大。
\4) lcore_count:这个程序可以使用的核数。
\5) process_type:DPDK多进程:一个程序是主程序,否则初始化DPDK内存表,其他从程序使用这个表。RTE_PROC_PRIMARY/RTE_PROC_SECONDARY
\6) mem_config:指向设备各个DPDK程序共享的内存配置结构,这个结构被**mmap到文件/var/run/.rte_config,通过这个方式多进程实现对mem_config结构的共享**。
l hugepage_file
这个是struct hugepage数组,每个**struct hugepage_file** 都代表一个hugepage 页面,存储的每个页面的物理地址和程序的虚拟地址的映射关系。然后,把整个数组映射到文件/var/run /. rte_hugepage_info,同样这个文件也是设备共享的,主/从进程都能访问它。
\1) file_id: 每个文件在hugepage 文件系统中都有一个编号,就是数组1-N;
\2) filepath:%s/%smap_%file_id mount 的hugepage文件系统中的文件路径名;
\3) size: 这个hugepage页面的size,2M还是1G;
\4) socket_id:这个页面属于那个CPU socket 。
\5) Physaddr:这个hugepage 页面的物理地址
\6) orig_va:它和final_va一样都是指这个huagepage页面的虚拟地址。这个地址是主程序初始化huagepage用的,后来就没用了。
\7) final_va:这个最终这个页面映射到主/从程序中的虚拟地址。
首先因为整个数组都映射到文件里面,所有的程序之间都是共享的。主程序负责初始化这个数组,首先在它内部通过mmap把所有的hugepage物理页面都映射到虚存空间里面,然后把这种映射关系保存到这个文件里面。从程序启动的时候,读取这个文件,然后在它内存也创建和它一模一样的映射,这样的话,整个DPDK管理的内存在所有的程序里面都是可见,而且地址都一样。
在对各个页面的物理地址份配虚拟地址时,DPDK尽可能把物理地址连续的页面分配连续的虚存地址上,这个东西还是比较有用的,因为CPU/cache/内存控制器的等等看到的都是物理内存,我们在访问内存时,如果物理地址连续的话,性能会高一些。至于到底哪些地址是连续的,那些不是连续的,DPDK在这个结构之上又有一个新的结构rte_mem_config. memseg来管理。因为rte_mem_config也映射到文件里面,所有的程序都可见rte_mem_config. memseg结构。
l rte_mem_config
这个数据结构mmap 到文件/var/run /.rte_config中,主/从进程通过这个文件访问实现对这个数据结构的共享。在每个程序内,使用rte_config .mem_config 访问这个结构。
l rte_memseg
memseg 数组是维护物理地址的,在上面讲到struct hugepage结构对每个hugepage物理页面都存储了它在程序里面的虚存地址。memseg 数组的作用是将物理地址、虚拟地址都连续的hugepage**,并且都在同一个socket,pagesize** 也相同的hugepage**页面集合**,把它们都划在一个memseg结构里面,这样做的好处就是优化内存。
rte_memseg这个结构也很简单:
\1) phys_addr:这个memseg的包含的所有的hugepage页面的起始物理地址;
\2) addr:这些hugepage页面的起始的虚存地址;
\3) len:这个memseg的包含的空间size
\4) hugepage_sz; 这些页面的size 2M /1G?
这些信息都是从hugepage页表数组里面获得的。
1.2 dpdk 内存初始化源码解析
l rte_eal_init
这个函数是dpdk 运行环境初始化入口函数。
整个内存初始化的代码流程如上图所示,下面我们逐个分析。
l eal_hugepage_info_init
这个函数较为简单,主要是遍历系统的/sys/kernel/mm/hugepages目录建立对应的数据结构。系统支持的每种size的hugepage类型在/sys/kernel/mm/hugepages目录下都对应一个子目录。例如系统支持2M和1G的大页,就会有对应内目录如下图所示:
而其中每个目录就会对应一个**struct hugepage_info的结构**,其结构如下,记录着对应目录下的信息。那么目录下都有什么信息呢?如下图所示:
所以struct hugepage_info中也是记录的这些信息,包括当前size的hugepage页面总个数(nr_hugepages),已经还没有被分配的个数(free_hugepages)等。所有struct hugepage_info构成一个数组,保存在struct internal_config结构中。
源码如下:
-
int
-
eal_hugepage_info_init(void)
-
{
-
const char dirent_start_text[] = "hugepages-";
-
const size_t dirent_start_len = sizeof(dirent_start_text) - 1;
-
unsigned i, num_sizes = 0;
-
DIR *dir;
-
struct dirent *dirent;
-
``
-
dir = opendir(sys_dir_path); /* /sys/kernel/mm/hugepages */
-
if (dir == NULL)
-
rte_panic("Cannot open directory %s to read system hugepage "
-
"info\n", sys_dir_path);
-
``
-
for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir)) {
-
struct hugepage_info *hpi;
-
``
-
if (strncmp(dirent->d_name, dirent_start_text,
-
dirent_start_len) != 0)
-
continue;
-
``
-
if (num_sizes >= MAX_HUGEPAGE_SIZES)
-
break;
-
``
-
hpi = &internal_config.hugepage_info[num_sizes];
-
hpi->hugepage_sz =
-
rte_str_to_size(&dirent->d_name[dirent_start_len]);
-
hpi->hugedir = get_hugepage_dir(hpi->hugepage_sz);
-
``
-
/* first, check if we have a mountpoint */
-
if (hpi->hugedir == NULL) {
-
uint32_t num_pages;
-
``
-
num_pages = get_num_hugepages(dirent->d_name);
-
if (num_pages > 0)
-
RTE_LOG(NOTICE, EAL,
-
"%" PRIu32 " hugepages of size "
-
"%" PRIu64 " reserved, but no mounted "
-
"hugetlbfs found for that size\n",
-
num_pages, hpi->hugepage_sz);
-
continue;
-
}
-
``
-
/* try to obtain a writelock */
-
hpi->lock_descriptor = open(hpi->hugedir, O_RDONLY);
-
``
-
/* if blocking lock failed */
-
if (flock(hpi->lock_descriptor, LOCK_EX) == -1) {
-
RTE_LOG(CRIT, EAL,
-
"Failed to lock hugepage directory!\n");
-
break;
-
}
-
/* clear out the hugepages dir from unused pages */
-
if (clear_hugedir(hpi->hugedir) == -1)
-
break;
-
``
-
/* for now, put all pages into socket 0,
-
\* later they will be sorted */
-
/* 这里还没有按socket统计页数,将内存页数直接记录到hupage_info的num_pages[0]里面了 */
-
hpi->num_pages[0] = get_num_hugepages(dirent->d_name);
-
``
-
\#ifndef RTE_ARCH_64
-
/* for 32-bit systems, limit number of hugepages to
-
\* 1GB per page size */
-
hpi->num_pages[0] = RTE_MIN(hpi->num_pages[0],
-
RTE_PGSIZE_1G / hpi->hugepage_sz);
-
\#endif
-
``
-
num_sizes++;
-
}
-
closedir(dir);
-
``
-
/* something went wrong, and we broke from the for loop above */
-
if (dirent != NULL)
-
return -1;
-
``
-
internal_config.num_hugepage_sizes = num_sizes;
-
``
-
/* sort the page directory entries by size, largest to smallest */
-
qsort(&internal_config.hugepage_info[0], num_sizes,
-
sizeof(internal_config.hugepage_info[0]), compare_hpi);
-
``
-
/* now we have all info, check we have at least one valid size */
-
for (i = 0; i < num_sizes; i++)
-
if (internal_config.hugepage_info[i].hugedir != NULL &&
-
internal_config.hugepage_info[i].num_pages[0] > 0)
-
return 0;
-
``
-
/* no valid hugepage mounts available, return error */
-
return -1;
-
}
每一类所有内存页,也分处在哪个 socket上(不明白的查看NUMA相关知识补齐)的,hugepage_info中统计内存页数会按属于处在哪个socket上进行统计,但在这一步(eal_hugepage_info_init)中,还区分不了每个页处在哪个socket上,因此这里还没有按socket统计页数,所以就暂时将内存页数直接记录