<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從原理到實(shí)踐:掌握DPDK內(nèi)存池技術(shù)

          共 44219字,需瀏覽 89分鐘

           ·

          2024-04-29 08:00


          前言:本文整理下之前的學(xué)習(xí)筆記,基于DPDK17.11版本源碼分析。主要分析一下內(nèi)存管理部分代碼。

          一、概述

          內(nèi)存管理是數(shù)據(jù)面開(kāi)發(fā)套件(DPDK)的一個(gè)核心部分,以此為基礎(chǔ),DPDK的其他部分和用戶應(yīng)用得以發(fā)揮其最佳性能。本系列文章將詳細(xì)介紹DPDK提供的各種內(nèi)存管理的功能。

          但在此之前,有必要先談一談為何DPDK中內(nèi)存管理要以現(xiàn)有的方式運(yùn)作,它背后又有怎樣的原理,再進(jìn)一步探討DPDK具體能夠提供哪些與內(nèi)存相關(guān)的功能。本文將先介紹DPDK內(nèi)存的基本原理,并解釋它們是如何幫助DPDK實(shí)現(xiàn)高性能的。

          請(qǐng)注意,雖然DPDK支持FreeBSD,而且也會(huì)有正在運(yùn)行的Windows端口,但目前大多數(shù)與內(nèi)存相關(guān)的功能僅適用于Linux。

          先看一下下面的圖片,其中左邊部分為DPDK內(nèi)存層級(jí)結(jié)構(gòu),下面三層在rte_eal_init初始化時(shí)完成,上面三層由用戶調(diào)用API生成。右邊為每層內(nèi)存結(jié)構(gòu)提供的API,供上層或者APP使用。



          下圖為內(nèi)存管理相關(guān)的數(shù)據(jù)結(jié)構(gòu),其中rte_config->mem_config指向共享內(nèi)存文件 /var/run/.rte_config,對(duì)應(yīng)的結(jié)構(gòu)體為struct rte_mem_config,其中mem_cfg_addr保存了映射文件/var/run/.rte_config后的虛擬地址,從進(jìn)程使用此值進(jìn)行映射,保證主從進(jìn)程可以使用相同的虛擬地址訪問(wèn)結(jié)構(gòu)體rte_mem_config。



          此共享內(nèi)存struct rte_mem_config還保存了如下幾個(gè)重要的結(jié)構(gòu)體:

          a. memseg: 將同時(shí)滿足下面四個(gè)條件的大頁(yè)放在一個(gè)memseg中

          1. 同socket
          2. hugepage 大小相同
          3. 物理地址連續(xù)
          4. 虛擬地址連續(xù)

          b. malloc_heap: 將相同socket的memseg掛在同一個(gè)malloc_heap上,對(duì)外提供API的最底層實(shí)現(xiàn)
          c. memzone: 用來(lái)申請(qǐng)整塊內(nèi)存
          d. tailq_head: 共享隊(duì)列實(shí)現(xiàn),主從進(jìn)程可以同時(shí)訪問(wèn)。

          內(nèi)存初始化流程如下,后面會(huì)詳細(xì)看每個(gè)函數(shù)的實(shí)現(xiàn)。

          rte_eal_init
          //internal_config為本進(jìn)程全局變量,用來(lái)保存參數(shù)等信息
          eal_reset_internal_config(&internal_config);

          //解析參數(shù),保存到 internal_config
          eal_parse_args(argc, argv);

          //收集系統(tǒng)上可用的大頁(yè)內(nèi)存,保存到 internal_config->hugepage_info[3]
          eal_hugepage_info_init();

          //初始化全局變量rte_config,并將 rte_config->mem_config 進(jìn)行映射
          rte_config_init();

          //大頁(yè)內(nèi)存映射,并保存到 rte_config->mem_config->memseg[]
          rte_eal_memory_init();

          //將 memseg 插入 rte_config->mem_config->malloc_heaps[]
          rte_eal_memzone_init();

          //主進(jìn)程初始化完成
          rte_eal_mcfg_complete();
          /* ALL shared mem_config related INIT DONE */
          //主進(jìn)程完成了所有共享內(nèi)存的初始化,設(shè)置RTE_MAGIC。
          //從進(jìn)程在等待magic變成RTE_MAGIC后才能繼續(xù)下去。
          if (rte_config.process_type == RTE_PROC_PRIMARY)
          rte_config.mem_config->magic = RTE_MAGIC;

          二、標(biāo)準(zhǔn)大頁(yè)

          現(xiàn)代CPU架構(gòu)中,內(nèi)存管理并不以單個(gè)字節(jié)進(jìn)行,而是以頁(yè)為單位,即虛擬和物理連續(xù)的內(nèi)存塊。這些內(nèi)存塊通常(但不是必須) 存儲(chǔ)在RAM中。在英特爾?64和IA-32架構(gòu)上,標(biāo)準(zhǔn)系統(tǒng)的頁(yè)面大小為4KB。

          基于安全性和通用性的考慮,軟件的應(yīng)用程序訪問(wèn)的內(nèi)存位置使用的是操作系統(tǒng)分配的虛擬地址。運(yùn)行代碼時(shí),該虛擬地址需要被轉(zhuǎn)換為硬件使用的物理地址。這種轉(zhuǎn)換是操作系統(tǒng)通過(guò)頁(yè)表轉(zhuǎn)換來(lái)完成的,頁(yè)表在分頁(yè)粒度級(jí)別上(即4KB一個(gè)粒度)將虛擬地址映射到物理地址。為了提高性能,最近一次使用的若干頁(yè)面地址被保存在一個(gè)稱為轉(zhuǎn)換檢測(cè)緩沖區(qū)(TLB)的高速緩存中。每一分頁(yè)都占有TLB的一個(gè)條目。如果用戶的代碼訪問(wèn)(或最近訪問(wèn)過(guò))16 KB的內(nèi)存,即4頁(yè),這些頁(yè)面很有可能會(huì)在TLB緩存中。

          如果其中一個(gè)頁(yè)面不在TLB緩存中,嘗試訪問(wèn)該頁(yè)面中包含的地址將導(dǎo)致TLB查詢失敗;也就是說(shuō),操作系統(tǒng)寫(xiě)入TLB的頁(yè)地址必須是在它的全局頁(yè)表中進(jìn)行查詢操作獲取的。因此,TLB查詢失敗的代價(jià)也相對(duì)較高(某些情況下代價(jià)會(huì)非常高),所以最好將當(dāng)前活動(dòng)的所有頁(yè)面都置于TLB中以盡可能減少TLB查詢失敗。

          然而,TLB的大小有限,而且實(shí)際上非常小,和DPDK通常處理的數(shù)據(jù)量(有時(shí)高達(dá)幾十GB)比起來(lái),在任一給定的時(shí)刻,4KB 標(biāo)準(zhǔn)頁(yè)面大小的TLB所覆蓋的內(nèi)存量(幾MB)微不足道。這意味著,如果DPDK采用常規(guī)內(nèi)存,使用DPDK的應(yīng)用會(huì)因?yàn)門(mén)LB頻繁的查詢失敗在性能上大打折扣。

          為解決這個(gè)問(wèn)題,DPDK依賴于標(biāo)準(zhǔn)大頁(yè)。從名字中很容易猜到,標(biāo)準(zhǔn)大頁(yè)類似于普通的頁(yè)面,只是會(huì)更大。有多大呢?在英特爾?64和1A-32架構(gòu)上,目前可用的兩種大頁(yè)大小為2MB和1GB。也就是說(shuō),單個(gè)頁(yè)面可以覆蓋2 MB或1 GB大小的整個(gè)物理和虛擬連續(xù)的存儲(chǔ)區(qū)域。


          TLB內(nèi)存覆蓋量比較


          這兩種頁(yè)面大小DPDK都可以支持。有了這樣的頁(yè)面大小,就可以更容易覆蓋大內(nèi)存區(qū)域,也同時(shí)避免(同樣多的)TLB查詢失敗。反過(guò)來(lái),在處理大內(nèi)存區(qū)域時(shí),更少的TLB查詢失敗也會(huì)使性能得到提升,DPDK的用例通常如此。

          2.1eal_hugepage_info_init 收集可用大頁(yè)內(nèi)存

          遍歷/sys/kernel/mm/hugepages下面的目錄收集可用的大頁(yè)。

          /*
          * when we initialize the hugepage info, everything goes
          * to socket 0 by default. it will later get sorted by memory
          * initialization procedure.
          */
          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;
          //打開(kāi)目錄 /sys/kernel/mm/hugepages
          dir = opendir(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]);

          //打開(kāi)文件 /proc/mounts 遍歷所有掛載點(diǎn),找到 hugetlbfs
          //相關(guān)的掛載點(diǎn),再找到和hpi->hugepage_sz相等的掛載
          //點(diǎn),返回其掛載目錄。
          hpi->hugedir = get_hugepage_dir(hpi->hugepage_sz);

          //如果對(duì)應(yīng)hugepage_sz的掛載點(diǎn),則跳過(guò)此種類型大頁(yè)
          /* 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 */
          flock(hpi->lock_descriptor, LOCK_EX);

          //刪除掛載點(diǎn)目錄下以"map_"開(kāi)頭的文件
          /* clear out the hugepages dir from unused pages */
          clear_hugedir(hpi->hugedir);

          //獲取空閑的大頁(yè)數(shù)量,free_hugepages減去
          //resv_hugepages。此時(shí)還不知道大頁(yè)位于哪個(gè)socket,
          //所以先將大頁(yè)數(shù)量統(tǒng)一放到socket0上
          /* for now, put all pages into socket 0,
          * later they will be sorted */
          hpi->num_pages[0] = get_num_hugepages(dirent->d_name);

          //大頁(yè)種類加一
          num_sizes++;
          }
          closedir(dir);

          //保存大頁(yè)種類: 2M, 1G等
          internal_config.num_hugepage_sizes = num_sizes;

          //按照大頁(yè)大小排序 hugepage_info,從大到小
          /* 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);

          //如果有可用的大頁(yè),返回0,否則返回-1
          /* 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;
          }

          rte_config_init

          映射文件 /var/run/.rte_config,用來(lái)保存結(jié)構(gòu)體 rte_config->mem_config 的內(nèi)容。主進(jìn)程映射此文件,將映射后的虛擬地址保存到 rte_config->mem_config->mem_cfg_addr,從進(jìn)程需要映射兩次此文件,第一次是為了獲取主進(jìn)程使用的虛擬地址mem_cfg_addr,然后再使用此虛擬地址進(jìn)行映射,這樣保存主從進(jìn)程可以使用相同的虛擬地方訪問(wèn) rte_config->mem_config指向的共享內(nèi)存。

          /* Sets up rte_config structure with the pointer to shared memory config.*/
          static void
          rte_config_init(void)
          {
          rte_config.process_type = internal_config.process_type;

          switch (rte_config.process_type){
          case RTE_PROC_PRIMARY:
          //主進(jìn)程初始化
          rte_eal_config_create();
          break;
          case RTE_PROC_SECONDARY:
          //從進(jìn)程attach
          rte_eal_config_attach();
          //等待主進(jìn)程初始化完成
          rte_eal_mcfg_wait_complete(rte_config.mem_config);
          //再次attach
          rte_eal_config_reattach();
          break;
          case RTE_PROC_AUTO:
          case RTE_PROC_INVALID:
          rte_panic("Invalid process type\n");
          }
          }

          主進(jìn)程映射文件 /var/run/.rte_config

          /* create memory configuration in shared/mmap memory. Take out
          * a write lock on the memsegs, so we can auto-detect primary/secondary.
          * This means we never close the file while running (auto-close on exit).
          * We also don't lock the whole file, so that in future we can use read-locks
          * on other parts, e.g. memzones, to detect if there are running secondary
          * processes. */
          static void
          rte_eal_config_create(void)
          {
          void *rte_mem_cfg_addr;
          int retval;

          //獲取config所在路徑 /var/run/.rte_config
          const char *pathname = eal_runtime_config_path();

          if (internal_config.no_shconf)
          return;

          /* map the config before hugepage address so that we don't waste a page */
          if (internal_config.base_virtaddr != 0)
          rte_mem_cfg_addr = (void *)
          RTE_ALIGN_FLOOR(internal_config.base_virtaddr -
          sizeof(struct rte_mem_config), sysconf(_SC_PAGE_SIZE));
          else
          rte_mem_cfg_addr = NULL;

          //打開(kāi)config文件路徑 /var/run/.rte_config
          if (mem_cfg_fd < 0){
          mem_cfg_fd = open(pathname, O_RDWR | O_CREAT, 0660);
          if (mem_cfg_fd < 0)
          rte_panic("Cannot open '%s' for rte_mem_config\n", pathname);
          }

          //將文件大小修改為 mem_config 的長(zhǎng)度
          retval = ftruncate(mem_cfg_fd, sizeof(*rte_config.mem_config));
          if (retval < 0){
          close(mem_cfg_fd);
          rte_panic("Cannot resize '%s' for rte_mem_config\n", pathname);
          }

          //給文件mem_cfg_fd加上鎖,可以調(diào)用eal_proc_type_detect檢測(cè)是主進(jìn)程還是從進(jìn)程。
          //只有主進(jìn)程能lock成功。
          static struct flock wr_lock = {
          .l_type = F_WRLCK,
          .l_whence = SEEK_SET,
          .l_start = offsetof(struct rte_mem_config, memseg),
          .l_len = sizeof(early_mem_config.memseg),
          };
          fcntl(mem_cfg_fd, F_SETLK, &wr_lock);

          //映射config文件
          rte_mem_cfg_addr = mmap(rte_mem_cfg_addr, sizeof(*rte_config.mem_config),
          PROT_READ | PROT_WRITE, MAP_SHARED, mem_cfg_fd, 0);

          //將early_mem_config內(nèi)容保存到共享內(nèi)存 rte_mem_cfg_addr
          memcpy(rte_mem_cfg_addr, &early_mem_config, sizeof(early_mem_config));

          //將映射后的虛擬地址保存到 rte_config.mem_config
          rte_config.mem_config = rte_mem_cfg_addr;

          //最后將映射后的虛擬地址 rte_mem_cfg_addr 保存到共享內(nèi)存 mem_config->mem_cfg_addr,
          //以便從進(jìn)程獲取后映射相同的虛擬地址。
          /* store address of the config in the config itself so that secondary
          * processes could later map the config into this exact location */
          rte_config.mem_config->mem_cfg_addr = (uintptr_t) rte_mem_cfg_addr;
          }

          因?yàn)橹挥兄鬟M(jìn)程能lock,所以可以使用函數(shù)eal_proc_type_detect檢查當(dāng)前進(jìn)程是主進(jìn)程還是從進(jìn)程。

          /* Detect if we are a primary or a secondary process */
          enum rte_proc_type_t
          eal_proc_type_detect(void)
          {
          enum rte_proc_type_t ptype = RTE_PROC_PRIMARY;
          const char *pathname = eal_runtime_config_path();

          /* if we can open the file but not get a write-lock we are a secondary
          * process. NOTE: if we get a file handle back, we keep that open
          * and don't close it to prevent a race condition between multiple opens */
          if (((mem_cfg_fd = open(pathname, O_RDWR)) >= 0) &&
          (fcntl(mem_cfg_fd, F_SETLK, &wr_lock) < 0))
          ptype = RTE_PROC_SECONDARY;

          RTE_LOG(INFO, EAL, "Auto-detected process type: %s\n",
          ptype == RTE_PROC_PRIMARY ? "PRIMARY" : "SECONDARY");

          return ptype;
          }

          從進(jìn)程第一此映射/var/run/.rte_config,可能在主進(jìn)程之前就映射了,映射后主從進(jìn)程都可以操作這塊內(nèi)存,但主要目的還是為了獲取主進(jìn)程的虛擬地址 mem_cfg_addr。

          /* attach to an existing shared memory config */
          static void
          rte_eal_config_attach(void)
          {
          struct rte_mem_config *mem_config;

          //獲取config所在路徑 /var/run/.rte_config
          const char *pathname = eal_runtime_config_path();

          if (internal_config.no_shconf)
          return;

          //打開(kāi)文件 /var/run/.rte_config
          if (mem_cfg_fd < 0){
          mem_cfg_fd = open(pathname, O_RDWR);
          if (mem_cfg_fd < 0)
          rte_panic("Cannot open '%s' for rte_mem_config\n", pathname);
          }

          //映射config文件,只讀模式。而且第一個(gè)參數(shù)為NULL,即不知道虛擬地址
          /* map it as read-only first */
          mem_config = (struct rte_mem_config *) mmap(NULL, sizeof(*mem_config),
          PROT_READ, MAP_SHARED, mem_cfg_fd, 0);
          if (mem_config == MAP_FAILED)
          rte_panic("Cannot mmap memory for rte_config! error %i (%s)\n",
          errno, strerror(errno));

          rte_config.mem_config = mem_config;
          }

          等待主進(jìn)程初始化完成。主進(jìn)程初始化完成后會(huì)調(diào)用rte_eal_mcfg_complete將mcfg->magic設(shè)置為 RTE_MAGIC。

          inline static void
          rte_eal_mcfg_wait_complete(struct rte_mem_config* mcfg)
          {
          /* wait until shared mem_config finish initialising */
          while(mcfg->magic != RTE_MAGIC)
          rte_pause();
          }

          主進(jìn)程初始化成功后,從進(jìn)程就可以使用主進(jìn)程映射的虛擬地址 mem_cfg_addr進(jìn)行第二次映射

          /* reattach the shared config at exact memory location primary process has it */
          static void
          rte_eal_config_reattach(void)
          {
          struct rte_mem_config *mem_config;
          void *rte_mem_cfg_addr;

          if (internal_config.no_shconf)
          return;

          //從共享配置文件獲取主進(jìn)程映射的虛擬地址 mem_cfg_addr
          /* save the address primary process has mapped shared config to */
          rte_mem_cfg_addr = (void *) (uintptr_t) rte_config.mem_config->mem_cfg_addr;

          //去除從進(jìn)程之前的對(duì)mem_config的映射
          /* unmap original config */
          munmap(rte_config.mem_config, sizeof(struct rte_mem_config));

          //指定主進(jìn)程映射的虛擬地址 mem_cfg_addr重新映射,獲取和主進(jìn)程一樣的虛擬地址
          /* remap the config at proper address */
          mem_config = (struct rte_mem_config *) mmap(rte_mem_cfg_addr,
          sizeof(*mem_config), PROT_READ | PROT_WRITE, MAP_SHARED,
          mem_cfg_fd, 0);
          if (mem_config == MAP_FAILED || mem_config != rte_mem_cfg_addr) {
          if (mem_config != MAP_FAILED)
          /* errno is stale, don't use */
          rte_panic("Cannot mmap memory for rte_config at [%p], got [%p]"
          " - please use '--base-virtaddr' option\n",
          rte_mem_cfg_addr, mem_config);
          else
          rte_panic("Cannot mmap memory for rte_config! error %i (%s)\n",
          errno, strerror(errno));
          }
          close(mem_cfg_fd);

          //將映射后的mem_config保存到從進(jìn)程本地變量 rte_config.mem_config
          rte_config.mem_config = mem_config;
          }

          這樣主從進(jìn)程都能使用相同的虛擬地址訪問(wèn)共享配置 rte_config.mem_config。

          2.2rte_eal_memory_init映射大頁(yè)內(nèi)存

          主進(jìn)程先映射,將映射后的虛擬地址保存到文件/var/run/.rte_hugepage_info中,從進(jìn)程讀取此文件,以相同的虛擬地址進(jìn)行映射,保證主從進(jìn)程以相同的地址訪問(wèn)大頁(yè)內(nèi)存,這也是實(shí)現(xiàn)進(jìn)程間傳遞報(bào)文零拷貝的關(guān)鍵。

          /* init memory subsystem */
          int
          rte_eal_memory_init(void)
          {
          RTE_LOG(DEBUG, EAL, "Setting up physically contiguous memory...\n");

          const int retval = rte_eal_process_type() == RTE_PROC_PRIMARY ?
          rte_eal_hugepage_init() :
          rte_eal_hugepage_attach();
          if (retval < 0)
          return -1;

          if (internal_config.no_shconf == 0 && rte_eal_memdevice_init() < 0)
          return -1;

          return 0;
          }

          主進(jìn)程調(diào)用 rte_eal_hugepage_init 進(jìn)行大頁(yè)映射,并根據(jù)分類保存到memseg中。

          /*
          * Prepare physical memory mapping: fill configuration structure with
          * these infos, return 0 on success.
          * 1. map N huge pages in separate files in hugetlbfs
          * 2. find associated physical addr
          * 3. find associated NUMA socket ID
          * 4. sort all huge pages by physical address
          * 5. remap these N huge pages in the correct order
          * 6. unmap the first mapping
          * 7. fill memsegs in configuration with contiguous zones
          */
          int
          rte_eal_hugepage_init(void)
          {
          struct rte_mem_config *mcfg;
          struct hugepage_file *hugepage = NULL, *tmp_hp = NULL;
          struct hugepage_info used_hp[MAX_HUGEPAGE_SIZES];

          uint64_t memory[RTE_MAX_NUMA_NODES];

          unsigned hp_offset;
          int i, j, new_memseg;
          int nr_hugefiles, nr_hugepages = 0;
          void *addr;

          //測(cè)試物理地址是否可用,如果參數(shù)指定no_hugetlbfs不用大頁(yè)或者
          //通過(guò)虛擬地址不能獲取物理地址,則認(rèn)為物理地址不可用,則設(shè)置phys_addrs_available為false。
          test_phys_addrs_available();

          memset(used_hp, 0, sizeof(used_hp));

          //獲取全局共享配置 mem_config
          /* get pointer to global configuration */
          mcfg = rte_eal_get_configuration()->mem_config;

          //遍歷 num_hugepage_sizes 中大頁(yè)內(nèi)存
          /* calculate total number of hugepages available. at this point we haven't
          * yet started sorting them so they all are on socket 0 */
          for (i = 0; i < (int) internal_config.num_hugepage_sizes; i++) {
          /* meanwhile, also initialize used_hp hugepage sizes in used_hp */
          used_hp[i].hugepage_sz = internal_config.hugepage_info[i].hugepage_sz;

          //獲取所有大頁(yè)的個(gè)數(shù)
          nr_hugepages += internal_config.hugepage_info[i].num_pages[0];
          }

          /*
          * allocate a memory area for hugepage table.
          * this isn't shared memory yet. due to the fact that we need some
          * processing done on these pages, shared memory will be created
          * at a later stage.
          */
          //分配整塊內(nèi)存,用于保存 nr_hugepages 個(gè) struct hugepage_file
          tmp_hp = malloc(nr_hugepages * sizeof(struct hugepage_file));
          if (tmp_hp == NULL)
          goto fail;

          memset(tmp_hp, 0, nr_hugepages * sizeof(struct hugepage_file));

          hp_offset = 0; /* where we start the current page size entries */

          //internal_config.socket_mem[i] 保存的是參數(shù) --socket-mem 1024,1024 指定的每個(gè)socket上的內(nèi)存。
          //復(fù)制一份到局部變量 memory 中
          /* make a copy of socket_mem, needed for balanced allocation. */
          for (i = 0; i < RTE_MAX_NUMA_NODES; i++)
          memory[i] = internal_config.socket_mem[i];

          //開(kāi)始映射大頁(yè)內(nèi)存,主要工作為函數(shù)前面注釋的前6條。
          /* map all hugepages and sort them */
          //遍歷當(dāng)前系統(tǒng)上配置的幾種大頁(yè)內(nèi)存
          for (i = 0; i < (int)internal_config.num_hugepage_sizes; i ++){
          unsigned pages_old, pages_new;
          struct hugepage_info *hpi;

          /*
          * we don't yet mark hugepages as used at this stage, so
          * we just map all hugepages available to the system
          * all hugepages are still located on socket 0
          */
          hpi = &internal_config.hugepage_info[i];

          //如果為0,說(shuō)明此種大頁(yè)沒(méi)有free可用的,跳過(guò)
          if (hpi->num_pages[0] == 0)
          continue;

          /* map all hugepages available */
          //先獲取當(dāng)前此種類型的大頁(yè)個(gè)數(shù)
          pages_old = hpi->num_pages[0];
          //映射大頁(yè),返回實(shí)際成功應(yīng)該的個(gè)數(shù)。
          //map_all_hugepages最后一個(gè)參數(shù)為1,表示第一次映射,將映射后的虛擬地址保存到 hugepg_tbl[i].orig_va,
          //后面再單獨(dú)分析此函數(shù)
          pages_new = map_all_hugepages(&tmp_hp[hp_offset], hpi, memory, 1);
          if (pages_new < pages_old) {
          RTE_LOG(DEBUG, EAL,
          "%d not %d hugepages of size %u MB allocated\n",
          pages_new, pages_old,
          (unsigned)(hpi->hugepage_sz / 0x100000));

          //獲取未映射成功的大頁(yè)個(gè)數(shù)
          int pages = pages_old - pages_new;

          //更新總大頁(yè)個(gè)數(shù)
          nr_hugepages -= pages;
          //保存映射成功的大頁(yè)
          hpi->num_pages[0] = pages_new;
          //如果映射成功的大頁(yè)個(gè)數(shù)為0,直接跳過(guò),開(kāi)始下一種大頁(yè)的映射
          if (pages_new == 0)
          continue;
          }

          //物理地址可用,則調(diào)用 find_physaddrs 獲取映射后的虛擬地址對(duì)應(yīng)的物理地址,
          //并保存到 hugepg_tbl[i].physaddr
          if (phys_addrs_available) {
          /* find physical addresses for each hugepage */
          if (find_physaddrs(&tmp_hp[hp_offset], hpi) < 0) {
          RTE_LOG(DEBUG, EAL, "Failed to find phys addr "
          "for %u MB pages\n",
          (unsigned int)(hpi->hugepage_sz / 0x100000));
          goto fail;
          }
          } else {
          /* set physical addresses for each hugepage */
          if (set_physaddrs(&tmp_hp[hp_offset], hpi) < 0) {
          RTE_LOG(DEBUG, EAL, "Failed to set phys addr "
          "for %u MB pages\n",
          (unsigned int)(hpi->hugepage_sz / 0x100000));
          goto fail;
          }
          }

          //根據(jù)映射后的huge文件(rte_map0等)獲取socket,并保存到hugepg_tbl[i].socket_id
          if (find_numasocket(&tmp_hp[hp_offset], hpi) < 0){
          RTE_LOG(DEBUG, EAL, "Failed to find NUMA socket for %u MB pages\n",
          (unsigned)(hpi->hugepage_sz / 0x100000));
          goto fail;
          }

          //根據(jù)物理地址從小到大排序 tmp_hp
          qsort(&tmp_hp[hp_offset], hpi->num_pages[0],
          sizeof(struct hugepage_file), cmp_physaddr);

          //重新映射大頁(yè)內(nèi)存,這次最后一個(gè)參數(shù)為0,會(huì)將映射后的虛擬地址保存到 hugepg_tbl[i].final_va,
          //這也是最終的虛擬地址。
          //重新映射的目的是為了盡量找到物理地址和虛擬地址都連續(xù)的大頁(yè)內(nèi)存。
          /* remap all hugepages */
          if (map_all_hugepages(&tmp_hp[hp_offset], hpi, NULL, 0) !=
          hpi->num_pages[0]) {
          RTE_LOG(ERR, EAL, "Failed to remap %u MB pages\n",
          (unsigned)(hpi->hugepage_sz / 0x100000));
          goto fail;
          }

          //解除第一次映射的虛擬地址 hugepg_tbl[i].orig_va
          /* unmap original mappings */
          if (unmap_all_hugepages_orig(&tmp_hp[hp_offset], hpi) < 0)
          goto fail;

          //偏移,遍歷下一種大頁(yè)
          /* we have processed a num of hugepages of this size, so inc offset */
          hp_offset += hpi->num_pages[0];
          }

          //如果沒(méi)有通過(guò)參數(shù) -m 或者 --socket-mem 指定內(nèi)存,則獲取當(dāng)前所有大頁(yè)內(nèi)存總和
          if (internal_config.memory == 0 && internal_config.force_sockets == 0)
          internal_config.memory = eal_get_hugepage_mem_size();

          nr_hugefiles = nr_hugepages;

          //清空hugepage_info的大頁(yè)個(gè)數(shù)
          /* clean out the numbers of pages */
          for (i = 0; i < (int) internal_config.num_hugepage_sizes; i++)
          for (j = 0; j < RTE_MAX_NUMA_NODES; j++)
          internal_config.hugepage_info[i].num_pages[j] = 0;

          //前面獲取了大頁(yè)所在socket,這里計(jì)算每種大頁(yè)在各個(gè)socket上的大頁(yè)個(gè)數(shù)
          /* get hugepages for each socket */
          for (i = 0; i < nr_hugefiles; i++) {
          int socket = tmp_hp[i].socket_id;

          /* find a hugepage info with right size and increment num_pages */
          const int nb_hpsizes = RTE_MIN(MAX_HUGEPAGE_SIZES,
          (int)internal_config.num_hugepage_sizes);
          for (j = 0; j < nb_hpsizes; j++) {
          if (tmp_hp[i].size ==
          internal_config.hugepage_info[j].hugepage_sz) {
          internal_config.hugepage_info[j].num_pages[socket]++;
          }
          }
          }

          //復(fù)制參數(shù)指定的socket內(nèi)存到memory
          /* make a copy of socket_mem, needed for number of pages calculation */
          for (i = 0; i < RTE_MAX_NUMA_NODES; i++)
          memory[i] = internal_config.socket_mem[i];

          //計(jì)算最后需要的大頁(yè)個(gè)數(shù)。
          //前面映射的當(dāng)前系統(tǒng)上所有可用的大頁(yè),但是如果參數(shù)指定了內(nèi)存大小,就有可能用不到所有的
          //大頁(yè)。所以此函數(shù)時(shí)根據(jù)實(shí)際需要返回大頁(yè)個(gè)數(shù)。
          //如果參數(shù)申請(qǐng)的內(nèi)存大于當(dāng)前可用的內(nèi)存,直接返回-1
          /* calculate final number of pages */
          nr_hugepages = calc_num_pages_per_socket(memory,
          internal_config.hugepage_info, used_hp,
          internal_config.num_hugepage_sizes);

          //沒(méi)有足夠內(nèi)存
          /* error if not enough memory available */
          if (nr_hugepages < 0)
          goto fail;

          //創(chuàng)建文件 /var/run/.rte_hugepage_info,文件大小為nr_hugefiles * sizeof(struct hugepage_file),
          //用來(lái)保存實(shí)際使用的大頁(yè)信息,并將此文件進(jìn)行mmap映射。
          /* create shared memory */
          hugepage = create_shared_memory(eal_hugepage_info_path(),
          nr_hugefiles * sizeof(struct hugepage_file));

          memset(hugepage, 0, nr_hugefiles * sizeof(struct hugepage_file));

          /*
          * unmap pages that we won't need (looks at used_hp).
          * also, sets final_va to NULL on pages that were unmapped.
          */
          //刪除前面映射的但是不需要的大頁(yè)文件。
          //比如當(dāng)前系統(tǒng)有10個(gè)1G可用大頁(yè)內(nèi)存,進(jìn)行映射后會(huì)生成10個(gè)1G的文件,
          //但是參數(shù) --socket-mem 只指定了1G內(nèi)存,則需要?jiǎng)h除9個(gè)1G的文件。
          if (unmap_unneeded_hugepages(tmp_hp, used_hp,
          internal_config.num_hugepage_sizes) < 0) {
          RTE_LOG(ERR, EAL, "Unmapping and locking hugepages failed!\n");
          goto fail;
          }

          /*
          * copy stuff from malloc'd hugepage* to the actual shared memory.
          * this procedure only copies those hugepages that have final_va
          * not NULL. has overflow protection.
          */
          //這里只將實(shí)際需要的大頁(yè)信息保存到 hugepage
          if (copy_hugepages_to_shared_mem(hugepage, nr_hugefiles,
          tmp_hp, nr_hugefiles) < 0) {
          RTE_LOG(ERR, EAL, "Copying tables to shared memory failed!\n");
          goto fail;
          }

          //如果參數(shù)指定了 unlink,則要將映射后大頁(yè)文件unlink掉。unlink會(huì)將文件刪除(ls 看不到文件了),
          //但是前面打開(kāi)了這些文件,實(shí)際上不會(huì)真正刪除(lsof 可以看到)。
          /* free the hugepage backing files */
          if (internal_config.hugepage_unlink &&
          unlink_hugepage_files(tmp_hp, internal_config.num_hugepage_sizes) < 0) {
          RTE_LOG(ERR, EAL, "Unlinking hugepage files failed!\n");
          goto fail;
          }

          /* free the temporary hugepage table */
          free(tmp_hp);
          tmp_hp = NULL;

          //前面完成了大頁(yè)內(nèi)存的映射,這里要將他們分別保存到 memseg 中。
          //同時(shí)滿足下面四個(gè)條件的hugepage 放在同一個(gè)memseg中.
          //1. 同socket
          //2. hugepage 大小相同
          //3. 物理地址連續(xù)
          //4. 虛擬地址連續(xù)
          /* first memseg index shall be 0 after incrementing it below */
          j = -1;
          for (i = 0; i < nr_hugefiles; i++) {
          new_memseg = 0;

          /* if this is a new section, create a new memseg */
          //第一個(gè)大頁(yè)內(nèi)存,肯定是新memseg
          if (i == 0)
          new_memseg = 1;
          //和前一個(gè)大頁(yè)的socket不一樣,認(rèn)為是新的memseg
          else if (hugepage[i].socket_id != hugepage[i-1].socket_id)
          new_memseg = 1;
          //和前一個(gè)大頁(yè)的大小不一樣,認(rèn)為是新的memseg
          else if (hugepage[i].size != hugepage[i-1].size)
          new_memseg = 1;
          //和前一個(gè)大頁(yè)的物理地址不連續(xù),認(rèn)為是新的memseg
          else if ((hugepage[i].physaddr - hugepage[i-1].physaddr) !=
          hugepage[i].size)
          new_memseg = 1;
          //和前一個(gè)大頁(yè)的虛擬地址不連續(xù),認(rèn)為是新的memseg
          else if (((unsigned long)hugepage[i].final_va -
          (unsigned long)hugepage[i-1].final_va) != hugepage[i].size)
          new_memseg = 1;

          //將大頁(yè)信息保存到 memseg中。
          //最壞情況下,每個(gè)大頁(yè)使用有一個(gè)memseg。
          if (new_memseg) {
          j += 1;
          if (j == RTE_MAX_MEMSEG)
          break;

          mcfg->memseg[j].iova = hugepage[i].physaddr;
          mcfg->memseg[j].addr = hugepage[i].final_va;
          mcfg->memseg[j].len = hugepage[i].size;
          mcfg->memseg[j].socket_id = hugepage[i].socket_id;
          mcfg->memseg[j].hugepage_sz = hugepage[i].size;
          }
          /* continuation of previous memseg */
          else {
          mcfg->memseg[j].len += mcfg->memseg[j].hugepage_sz;
          }
          hugepage[i].memseg_id = j;
          }

          if (i < nr_hugefiles) {
          RTE_LOG(ERR, EAL, "Can only reserve %d pages "
          "from %d requested\n"
          "Current %s=%d is not enough\n"
          "Please either increase it or request less amount "
          "of memory.\n",
          i, nr_hugefiles, RTE_STR(CONFIG_RTE_MAX_MEMSEG),
          RTE_MAX_MEMSEG);
          goto fail;
          }

          //將大頁(yè)信息保存到文件 /var/run/.rte_hugepage_info 后,就可以將其解除映射,
          //等待從進(jìn)程讀取此文件即可。
          munmap(hugepage, nr_hugefiles * sizeof(struct hugepage_file));

          return 0;
          }


          /*
          * Mmap all hugepages of hugepage table: it first open a file in
          * hugetlbfs, then mmap() hugepage_sz data in it. If orig is set, the
          * virtual address is stored in hugepg_tbl[i].orig_va, else it is stored
          * in hugepg_tbl[i].final_va. The second mapping (when orig is 0) tries to
          * map contiguous physical blocks in contiguous virtual blocks.
          */
          static unsigned
          map_all_hugepages(struct hugepage_file *hugepg_tbl, struct hugepage_info *hpi,
          uint64_t *essential_memory __rte_unused, int orig)
          {
          int fd;
          unsigned i;
          void *virtaddr;
          void *vma_addr = NULL;
          size_t vma_len = 0;

          //遍歷大頁(yè)
          for (i = 0; i < hpi->num_pages[0]; i++) {
          uint64_t hugepage_sz = hpi->hugepage_sz;
          //如果是第一次映射,保存大頁(yè)索引,獲取大頁(yè)所在路徑
          if (orig) {
          hugepg_tbl[i].file_id = i;
          hugepg_tbl[i].size = hugepage_sz;
          //大頁(yè)文件所在路徑 /mnt/huge/rte_mapx
          eal_get_hugefile_path(hugepg_tbl[i].filepath,
          sizeof(hugepg_tbl[i].filepath), hpi->hugedir,
          hugepg_tbl[i].file_id);
          hugepg_tbl[i].filepath[sizeof(hugepg_tbl[i].filepath) - 1] = '\0';
          }
          else if (vma_len == 0) {
          unsigned j, num_pages;

          /* reserve a virtual area for next contiguous
          * physical block: count the number of
          * contiguous physical pages. */
          //找到和當(dāng)前大頁(yè)物理地址連續(xù)的大頁(yè)
          for (j = i+1; j < hpi->num_pages[0] ; j++) {
          //前一個(gè)大頁(yè)的物理地址加上大頁(yè)大小不等于當(dāng)前頁(yè)的物理地址,
          //說(shuō)明這兩個(gè)大頁(yè)物理地址不連續(xù)。
          if (hugepg_tbl[j].physaddr !=
          hugepg_tbl[j-1].physaddr + hugepage_sz)
          break;
          }

          num_pages = j - i;
          vma_len = num_pages * hugepage_sz;

          /* get the biggest virtual memory area up to
          * vma_len. If it fails, vma_addr is NULL, so
          * let the kernel provide the address. */
          //如果物理地址連續(xù),則判斷虛擬地址是否也可以連續(xù)
          vma_addr = get_virtual_area(&vma_len, hpi->hugepage_sz);
          if (vma_addr == NULL)
          vma_len = hugepage_sz;
          }

          //打開(kāi)大頁(yè)文件
          /* try to create hugepage file */
          fd = open(hugepg_tbl[i].filepath, O_CREAT | O_RDWR, 0600);
          if (fd < 0) {
          RTE_LOG(DEBUG, EAL, "%s(): open failed: %s\n", __func__,
          strerror(errno));
          goto out;
          }

          //第一次映射,vma_addr為NULL,讓kernel返回合適的虛擬地址
          /* map the segment, and populate page tables,
          * the kernel fills this segment with zeros */
          virtaddr = mmap(vma_addr, hugepage_sz, PROT_READ | PROT_WRITE,
          MAP_SHARED | MAP_POPULATE, fd, 0);
          if (virtaddr == MAP_FAILED) {
          RTE_LOG(DEBUG, EAL, "%s(): mmap failed: %s\n", __func__,
          strerror(errno));
          close(fd);
          goto out;
          }

          //第一次映射,將返回的虛擬地址保存到 orig_va
          if (orig) {
          hugepg_tbl[i].orig_va = virtaddr;
          }
          else {//第二次映射,將返回的虛擬地址保存到 final_va
          hugepg_tbl[i].final_va = virtaddr;
          }

          /* set shared flock on the file. */
          if (flock(fd, LOCK_SH | LOCK_NB) == -1) {
          RTE_LOG(DEBUG, EAL, "%s(): Locking file failed:%s \n",
          __func__, strerror(errno));
          close(fd);
          goto out;
          }

          close(fd);

          //下一個(gè)大頁(yè)映射的虛擬地址為當(dāng)前虛擬地址加大頁(yè)大小,保證
          //所有大頁(yè)的虛擬地址連續(xù)
          vma_addr = (char *)vma_addr + hugepage_sz;
          vma_len -= hugepage_sz;
          }

          out:
          return i;
          }

          從進(jìn)程調(diào)用 rte_eal_hugepage_attach 映射和主進(jìn)程相同的虛擬地址。

          /*
          * This creates the memory mappings in the secondary process to match that of
          * the server process. It goes through each memory segment in the DPDK runtime
          * configuration and finds the hugepages which form that segment, mapping them
          * in order to form a contiguous block in the virtual memory space
          */
          int
          rte_eal_hugepage_attach(void)
          {
          //獲取全局共享內(nèi)存配置
          const struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
          struct hugepage_file *hp = NULL;
          unsigned num_hp = 0;
          unsigned i, s = 0; /* s used to track the segment number */
          unsigned max_seg = RTE_MAX_MEMSEG;
          off_t size = 0;
          int fd, fd_zero = -1, fd_hugepage = -1;

          if (aslr_enabled() > 0) {
          RTE_LOG(WARNING, EAL, "WARNING: Address Space Layout Randomization "
          "(ASLR) is enabled in the kernel.\n");
          RTE_LOG(WARNING, EAL, " This may cause issues with mapping memory "
          "into secondary processes\n");
          }

          test_phys_addrs_available();

          //打開(kāi) /dev/zero,用來(lái)測(cè)試虛擬地址是否可用
          fd_zero = open("/dev/zero", O_RDONLY);
          if (fd_zero < 0) {
          RTE_LOG(ERR, EAL, "Could not open /dev/zero\n");
          goto error;
          }
          //打開(kāi)文件 /var/run/.rte_hugepage_info
          fd_hugepage = open(eal_hugepage_info_path(), O_RDONLY);
          if (fd_hugepage < 0) {
          RTE_LOG(ERR, EAL, "Could not open %s\n", eal_hugepage_info_path());
          goto error;
          }

          //主進(jìn)程已經(jīng)將需要的大頁(yè)進(jìn)行映射,并保存到了 mem_config->memseg[]中,
          //遍歷memseg,將主進(jìn)程保存到memseg的虛擬地址在從進(jìn)程映射,查看是否能
          //映射成功,如果不能成功,則報(bào)錯(cuò)返回,說(shuō)明不能和主進(jìn)程使用相同的地址。
          /* map all segments into memory to make sure we get the addrs */
          for (s = 0; s < RTE_MAX_MEMSEG; ++s) {
          void *base_addr;

          /*
          * the first memory segment with len==0 is the one that
          * follows the last valid segment.
          */
          if (mcfg->memseg[s].len == 0)
          break;

          /*
          * fdzero is mmapped to get a contiguous block of virtual
          * addresses of the appropriate memseg size.
          * use mmap to get identical addresses as the primary process.
          */
          base_addr = mmap(mcfg->memseg[s].addr, mcfg->memseg[s].len,
          PROT_READ,
          MAP_PRIVATE,
          fd_zero, 0);
          if (base_addr == MAP_FAILED ||
          base_addr != mcfg->memseg[s].addr) {
          max_seg = s;
          if (base_addr != MAP_FAILED) {
          /* errno is stale, don't use */
          RTE_LOG(ERR, EAL, "Could not mmap %llu bytes "
          "in /dev/zero at [%p], got [%p] - "
          "please use '--base-virtaddr' option\n",
          (unsigned long long)mcfg->memseg[s].len,
          mcfg->memseg[s].addr, base_addr);
          munmap(base_addr, mcfg->memseg[s].len);
          } else {
          RTE_LOG(ERR, EAL, "Could not mmap %llu bytes "
          "in /dev/zero at [%p]: '%s'\n",
          (unsigned long long)mcfg->memseg[s].len,
          mcfg->memseg[s].addr, strerror(errno));
          }
          if (aslr_enabled() > 0) {
          RTE_LOG(ERR, EAL, "It is recommended to "
          "disable ASLR in the kernel "
          "and retry running both primary "
          "and secondary processes\n");
          }
          goto error;
          }
          }

          //獲取文件 /var/run/.rte_hugepage_info 的實(shí)際大小
          size = getFileSize(fd_hugepage);
          //映射此文件
          hp = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd_hugepage, 0);
          if (hp == MAP_FAILED) {
          RTE_LOG(ERR, EAL, "Could not mmap %s\n", eal_hugepage_info_path());
          goto error;
          }

          //計(jì)算保存的大頁(yè)個(gè)數(shù)
          num_hp = size / sizeof(struct hugepage_file);
          RTE_LOG(DEBUG, EAL, "Analysing %u files\n", num_hp);

          //再次遍歷 memseg
          s = 0;
          while (s < RTE_MAX_MEMSEG && mcfg->memseg[s].len > 0){
          void *addr, *base_addr;
          uintptr_t offset = 0;
          size_t mapping_size;
          /*
          * free previously mapped memory so we can map the
          * hugepages into the space
          */
          //解除到 /dev/zero 的映射
          base_addr = mcfg->memseg[s].addr;
          munmap(base_addr, mcfg->memseg[s].len);

          //找到memseg中的大頁(yè)進(jìn)行映射
          /* find the hugepages for this segment and map them
          * we don't need to worry about order, as the server sorted the
          * entries before it did the second mmap of them */
          for (i = 0; i < num_hp && offset < mcfg->memseg[s].len; i++) {
          if (hp[i].memseg_id == (int)s){
          fd = open(hp[i].filepath, O_RDWR);
          if (fd < 0) {
          RTE_LOG(ERR, EAL, "Could not open %s\n",
          hp[i].filepath);
          goto error;
          }
          mapping_size = hp[i].size;
          addr = mmap(RTE_PTR_ADD(base_addr, offset),
          mapping_size, PROT_READ | PROT_WRITE,
          MAP_SHARED, fd, 0);
          close(fd); /* close file both on success and on failure */
          if (addr == MAP_FAILED ||
          addr != RTE_PTR_ADD(base_addr, offset)) {
          RTE_LOG(ERR, EAL, "Could not mmap %s\n",
          hp[i].filepath);
          goto error;
          }
          offset+=mapping_size;
          }
          }
          RTE_LOG(DEBUG, EAL, "Mapped segment %u of size 0x%llx\n", s,
          (unsigned long long)mcfg->memseg[s].len);
          s++;
          }
          /* unmap the hugepage config file, since we are done using it */
          munmap(hp, size);
          close(fd_zero);
          close(fd_hugepage);
          return 0;
          }

          rte_eal_memzone_init

          雖然函數(shù)名字是memzone初始化,但更多的是初始化 malloc_heap。

          /*
          * Init the memzone subsystem
          */
          int
          rte_eal_memzone_init(void)
          {
          struct rte_mem_config *mcfg;
          const struct rte_memseg *memseg;

          /* get pointer to global configuration */
          mcfg = rte_eal_get_configuration()->mem_config;

          //從進(jìn)程不用執(zhí)行
          /* secondary processes don't need to initialise anything */
          if (rte_eal_process_type() == RTE_PROC_SECONDARY)
          return 0;

          memseg = rte_eal_get_physmem_layout();
          if (memseg == NULL) {
          RTE_LOG(ERR, EAL, "%s(): Cannot get physical layout\n", __func__);
          return -1;
          }

          rte_rwlock_write_lock(&mcfg->mlock);

          //清空 memzone 個(gè)數(shù)
          /* delete all zones */
          mcfg->memzone_cnt = 0;
          memset(mcfg->memzone, 0, sizeof(mcfg->memzone));

          rte_rwlock_write_unlock(&mcfg->mlock);

          //初始化 heap
          return rte_eal_malloc_heap_init();
          }

          int
          rte_eal_malloc_heap_init(void)
          {
          struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
          unsigned ms_cnt;
          struct rte_memseg *ms;

          if (mcfg == NULL)
          return -1;

          //遍歷 memseg,按照memseg中的socket插入malloc_heaps中
          for (ms = &mcfg->memseg[0], ms_cnt = 0;
          (ms_cnt < RTE_MAX_MEMSEG) && (ms->len > 0);
          ms_cnt++, ms++) {
          malloc_heap_add_memseg(&mcfg->malloc_heaps[ms->socket_id], ms);
          }

          return 0;
          }

          將memseg放入 malloc_heap,首尾各分配一個(gè) malloc_elem,后者指向前者,將前者插入free_head

          /*
          * Expand the heap with a memseg.
          * This reserves the zone and sets a dummy malloc_elem header at the end
          * to prevent overflow. The rest of the zone is added to free list as a single
          * large free block
          */
          static void
          malloc_heap_add_memseg(struct malloc_heap *heap, struct rte_memseg *ms)
          {
          /* allocate the memory block headers, one at end, one at start */
          //memseg的首地址作為第一個(gè) malloc_elem
          struct malloc_elem *start_elem = (struct malloc_elem *)ms->addr;
          //memseg的尾地址減去malloc_elem大小作為最后一個(gè) malloc_elem
          struct malloc_elem *end_elem = RTE_PTR_ADD(ms->addr, ms->len - MALLOC_ELEM_OVERHEAD);
          end_elem = RTE_PTR_ALIGN_FLOOR(end_elem, RTE_CACHE_LINE_SIZE);
          //首尾malloc_elem相減得出第一個(gè)elem的大小
          const size_t elem_size = (uintptr_t)end_elem - (uintptr_t)start_elem;

          //初始化第一個(gè) elem,狀態(tài)為free,表示可以被分配
          malloc_elem_init(start_elem, heap, ms, elem_size);
          elem->heap = heap;
          elem->ms = ms;
          elem->prev = NULL;
          memset(&elem->free_list, 0, sizeof(elem->free_list));
          elem->state = ELEM_FREE;
          elem->size = size;
          elem->pad = 0;
          set_header(elem);
          set_trailer(elem);

          //初始化最后一個(gè) elem,并指向前一個(gè)elem,狀態(tài)為busy,表示永遠(yuǎn)不會(huì)被分配走
          malloc_elem_mkend(end_elem, start_elem);
          malloc_elem_init(elem, prev->heap, prev->ms, 0);
          elem->prev = prev;
          elem->state = ELEM_BUSY; /* mark busy so its never merged */

          //根據(jù)第一個(gè)elem的大小插入對(duì)應(yīng)的free_head
          malloc_elem_free_list_insert(start_elem);
          size_t idx;
          //根據(jù)size計(jì)算idx
          idx = malloc_elem_free_list_index(elem->size - MALLOC_ELEM_HEADER_LEN);
          elem->state = ELEM_FREE;
          LIST_INSERT_HEAD(&elem->heap->free_head[idx], elem, free_list);

          //保存此heap可分配的總內(nèi)存大小
          heap->total_size += elem_size;
          }

          三、內(nèi)存分配

          3.1將內(nèi)存固定到NUMA節(jié)點(diǎn)

          當(dāng)分配常規(guī)內(nèi)存時(shí),理論上,它可以被分配到RAM中的任何位置。這在單CPU系統(tǒng)上沒(méi)有什么問(wèn)題,但是許多DPDK用戶是在支持非統(tǒng)一內(nèi)存訪問(wèn) (NUMA) 的多CPU系統(tǒng)上運(yùn)行應(yīng)用的。對(duì)于NUMA來(lái)說(shuō),所有內(nèi)存都是不同的:某一個(gè)CPU對(duì)一些內(nèi)存的訪問(wèn)(如不在該CPU所屬NUMA NODE上的內(nèi)存)將比其他內(nèi)存訪問(wèn)花費(fèi)更長(zhǎng)的時(shí)間,這是由于它們相對(duì)于執(zhí)行所述內(nèi)存訪問(wèn)的CPU所在的物理位置不同。

          進(jìn)行常規(guī)內(nèi)存分配時(shí),通常無(wú)法控制該內(nèi)存分配到哪里,因此如果DPDK在這樣的系統(tǒng)上使用常規(guī)內(nèi)存,就可能會(huì)導(dǎo)致以下的情況:在一個(gè)CPU上執(zhí)行的線程卻在無(wú)意中訪問(wèn)屬于非本地NUMA節(jié)點(diǎn)的內(nèi)存。


          理想的NUMA節(jié)點(diǎn)分配


          雖然這種跨NUMA節(jié)點(diǎn)訪問(wèn)在所有現(xiàn)代操作系統(tǒng)上都比較少有,因?yàn)檫@樣的訪問(wèn)都是都是NUMA感知的,而且即使沒(méi)有DPDK還是有方法能對(duì)內(nèi)存實(shí)施NUMA定位。但是DPDK帶來(lái)的不僅僅是NUMA感知,事實(shí)上,整個(gè)DPDK API的構(gòu)建都旨在為每個(gè)操作提供明確的NUMA感知。如果不明確請(qǐng)求NUMA節(jié)點(diǎn)訪問(wèn)(其中所述結(jié)構(gòu)必須位于內(nèi)存中),通常無(wú)法分配給定的DPDK數(shù)據(jù)結(jié)構(gòu)。

          DPDK API提供的這種明確的NUMA感知有助于確保用戶應(yīng)用在每個(gè)操作中都能考慮到NUMA感知;換句話說(shuō),DPDK API可以減少寫(xiě)出編寫(xiě)性能差的代碼的可能性。

          硬件、物理地址和直接內(nèi)存存取(DMA)

          DPDK被認(rèn)為是一組用戶態(tài)的網(wǎng)絡(luò)包輸入/輸出庫(kù),到目前為止,它基本上保持了最初的任務(wù)聲明。但是,電腦上的硬件不能處理用戶空間的虛擬地址,因?yàn)樗荒芨兄魏斡脩魬B(tài)的進(jìn)程和其所分配到的用戶空間虛擬地址。相反,它只能訪問(wèn)真實(shí)的物理地址上的內(nèi)存,也就是CPU、RAM和系統(tǒng)所有其他的部分用來(lái)相互通信的地址。

          出于對(duì)效率的考量,現(xiàn)代硬件幾乎總是使用直接內(nèi)存存取(DMA)事務(wù)。通常,為了執(zhí)行一個(gè)DMA事務(wù),內(nèi)核需要參與創(chuàng)建一個(gè)支持DMA的存儲(chǔ)區(qū)域,將進(jìn)程內(nèi)虛擬地址轉(zhuǎn)換成硬件能夠理解的真實(shí)物理地址,并啟動(dòng)DMA事務(wù)。這是大多數(shù)現(xiàn)代操作系統(tǒng)中輸入輸出的工作方式;然而,這是一個(gè)耗時(shí)的過(guò)程,需要上下文切換、轉(zhuǎn)換和查找操作,這不利于高性能輸入/輸出。

          DPDK的內(nèi)存管理以一種簡(jiǎn)單的方式解決了這個(gè)問(wèn)題。每當(dāng)一個(gè)內(nèi)存區(qū)域可供DPDK使用時(shí),DPDK就通過(guò)詢問(wèn)內(nèi)核來(lái)計(jì)算它的物理地址。由于DPDK使用鎖定內(nèi)存,通常以大頁(yè)的形式,底層內(nèi)存區(qū)域的物理地址預(yù)計(jì)不會(huì)改變,因此硬件可以依賴這些物理地址始終有效,即使內(nèi)存本身有一段時(shí)間沒(méi)有使用。然后,DPDK會(huì)在準(zhǔn)備由硬件完成的輸入/輸出事務(wù)時(shí)使用這些物理地址,并以允許硬件自己?jiǎn)?dòng)DMA事務(wù)的方式配置硬件。這使DPDK避免不必要的開(kāi)銷,并且完全從用戶空間執(zhí)行輸入/輸出。

          IOMMU和IOVA

          默認(rèn)情況下,任何硬件都可以訪問(wèn)整個(gè)系統(tǒng),因此它可以在任何地方執(zhí)行DMA 事務(wù)。這有許多安全隱患。例如,流氓和/或不可信進(jìn)程(包括在VM (虛擬機(jī))內(nèi)運(yùn)行的進(jìn)程)可能使用硬件設(shè)備來(lái)讀寫(xiě)內(nèi)核空間,和幾乎其他任何存儲(chǔ)位置。為了解決這個(gè)問(wèn)題,現(xiàn)代系統(tǒng)配備了輸入輸出內(nèi)存管理單元(IOMMU)。這是一種硬件設(shè)備,提供DMA地址轉(zhuǎn)換和設(shè)備隔離功能,因此只允許特定設(shè)備執(zhí)行進(jìn)出特定內(nèi)存區(qū)域(由IOMMU指定)的DMA 事務(wù),而不能訪問(wèn)系統(tǒng)內(nèi)存地址空間的其余部分。

          由于IOMMU的參與,硬件使用的物理地址可能不是真實(shí)的物理地址,而是IOMMU分配給硬件的(完全任意的)輸入輸出虛擬地址(IOVA)。一般來(lái)說(shuō),DPDK社區(qū)可以互換使用物理地址和IOVA這兩個(gè)術(shù)語(yǔ),但是根據(jù)上下文,這兩者之間的區(qū)別可能很重要。例如,DPDK 17.11和更新的DPDK長(zhǎng)期支持(LTS)版本在某些情況下可能根本不使用實(shí)際的物理地址,而是使用用戶空間虛擬地址(甚至完全任意的地址)來(lái)實(shí)現(xiàn)DMA。IOMMU負(fù)責(zé)地址轉(zhuǎn)換,因此硬件永遠(yuǎn)不會(huì)注意到兩者之間的差異。


          IOMMU將物理地址重新映射到IOVA地址的示例


          根據(jù)DPDK的初始化方式,IOVA地址可能代表也可能不代表實(shí)際的物理地址,但有一點(diǎn)始終是正確的:DPDK知道底層內(nèi)存布局,因此可以利用這一點(diǎn)。例如,它可以以創(chuàng)建IOVA連續(xù)虛擬區(qū)域的方式映射頁(yè)面,或者甚至利用IOMMU來(lái)重新排列內(nèi)存映射,以使內(nèi)存看起來(lái)IOVA連續(xù),即使底層物理內(nèi)存可能不連續(xù)。

          因此,這種對(duì)底層物理內(nèi)存區(qū)域的感知是DPDK工具包中的又一個(gè)利器。大多數(shù)數(shù)據(jù)結(jié)構(gòu)不關(guān)心IOVA地址,但當(dāng)它們關(guān)心時(shí),DPDK為軟件和硬件提供了利用物理內(nèi)存布局的工具,并針對(duì)不同的用例進(jìn)行優(yōu)化

          請(qǐng)注意,IOMMU不會(huì)自行設(shè)置任何映射。相反,平臺(tái)、硬件和操作系統(tǒng)必須進(jìn)行配置,來(lái)使用IOMMU。這種配置說(shuō)明超出了本系列文章的范圍,但是在DPDK文檔和其他地方有相關(guān)說(shuō)明。一旦系統(tǒng)和硬件設(shè)置為使IOMMU,DPDK就可以使用IOMMU為DPDK分配的任何內(nèi)存區(qū)域設(shè)置DMA映射。使用IOMMU是運(yùn)行DPDK的推薦方法,因?yàn)檫@樣做更安全,并且它提供了可用性優(yōu)勢(shì)。

          3.2內(nèi)存分配和管理

          DPDK不使用常規(guī)內(nèi)存分配函數(shù),如malloc()。相反,DPDK管理自己的內(nèi)存。更具體地說(shuō),DPDK分配大頁(yè)并在此內(nèi)存中創(chuàng)建一個(gè)堆(heap)并將其提供給用戶應(yīng)用程序并用于存取應(yīng)用程序內(nèi)部的數(shù)據(jù)結(jié)構(gòu)。

          使用自定義內(nèi)存分配器有許多優(yōu)點(diǎn)。最明顯的一個(gè)是終端應(yīng)用程序的性能優(yōu)勢(shì):DPDK創(chuàng)建應(yīng)用程序要使用的內(nèi)存區(qū)域,并且應(yīng)用程序可以原生支持大頁(yè)、NUMA節(jié)點(diǎn)親和性、對(duì)DMA地址的訪問(wèn)、IOVA連續(xù)性等等性能優(yōu)勢(shì),而無(wú)需任何額外的開(kāi)發(fā)。

          DPDK內(nèi)存分配總是在CPU高速緩存行(cache line)的邊界上對(duì)齊,每個(gè)分配的起始地址將是系統(tǒng)高速緩存行大小的倍數(shù)。這種方法防止了許多常見(jiàn)的性能問(wèn)題,例如未對(duì)齊的訪問(wèn)和錯(cuò)誤的數(shù)據(jù)共享,其中單個(gè)高速緩存行無(wú)意中包含(可能不相關(guān)的)多個(gè)內(nèi)核同時(shí)訪問(wèn)的數(shù)據(jù)。對(duì)于需要這種對(duì)齊的用例(例如,分配硬件環(huán)結(jié)構(gòu)),也支持任何其他二次冪值 (當(dāng)然> =高速緩存行大小)。

          DPDK中的任何內(nèi)存分配也是線程安全的。這意味著在任何CPU核心上發(fā)生的任何分配都是原子的,不會(huì)干擾任何其他分配。這可能看起來(lái)很無(wú)足輕重 (畢竟,常規(guī)glibc內(nèi)存分配例程通常也是線程安全的),但是一旦在多處理環(huán)境中考慮,它的重要性就會(huì)變得更加清晰。

          DPDK支持特定風(fēng)格的協(xié)同多處理,其中主進(jìn)程管理所有DPDK資源,多個(gè)輔助進(jìn)程可以連接到主進(jìn)程,并共享由主進(jìn)程管理的資源的訪問(wèn)。

          DPDK的共享內(nèi)存實(shí)現(xiàn)不僅通過(guò)映射不同進(jìn)程中的相同資源 (類似于shmget () 機(jī)制) 來(lái)實(shí)現(xiàn),還通過(guò)復(fù)制另一個(gè)進(jìn)程中主進(jìn)程的地址空間來(lái)實(shí)現(xiàn)。因此,由于兩個(gè)進(jìn)程中的所有內(nèi)容都位于相同的地址,指向DPDK內(nèi)存對(duì)象的任何指針都將跨進(jìn)程工作,無(wú)需任何地址轉(zhuǎn)換。這對(duì)于跨進(jìn)程傳遞數(shù)據(jù)時(shí)的性能非常重要。


          操作系統(tǒng)和DPDK分配器的比較



          malloc_heap_alloc

          對(duì)外提供API的最底層實(shí)現(xiàn)是malloc_heap,它提供了函數(shù)malloc_heap_alloc用來(lái)從heap中分配內(nèi)存。

          /*
          * Main function to allocate a block of memory from the heap.
          * It locks the free list, scans it, and adds a new memseg if the
          * scan fails. Once the new memseg is added, it re-scans and should return
          * the new element after releasing the lock.
          */
          void *
          malloc_heap_alloc(struct malloc_heap *heap,
          const char *type __attribute__((unused)), size_t size, unsigned flags,
          size_t align, size_t bound)
          {
          struct malloc_elem *elem;

          size = RTE_CACHE_LINE_ROUNDUP(size);
          align = RTE_CACHE_LINE_ROUNDUP(align);

          //分配內(nèi)存都要先加鎖
          rte_spinlock_lock(&heap->lock);

          //先根據(jù)請(qǐng)求的內(nèi)存大小判斷是否有這么多可用內(nèi)存
          elem = find_suitable_element(heap, size, flags, align, bound);
          if (elem != NULL) {
          //有可用內(nèi)存,則將memseg進(jìn)行分割
          elem = malloc_elem_alloc(elem, size, align, bound);
          /* increase heap's count of allocated elements */
          heap->alloc_count++;
          }
          rte_spinlock_unlock(&heap->lock);

          return elem == NULL ? NULL : (void *)(&elem[1]);
          }

          rte_memzone_reserve

          rte_memzone_reserve用來(lái)從heap中分配一個(gè)內(nèi)存,可用指定長(zhǎng)度和socket。、

          /*
          * Return a pointer to a correctly filled memzone descriptor. If the
          * allocation cannot be done, return NULL.
          */
          const struct rte_memzone *
          rte_memzone_reserve(const char *name, size_t len, int socket_id,
          unsigned flags)
          {
          return rte_memzone_reserve_thread_safe(name, len, socket_id,
          flags, RTE_CACHE_LINE_SIZE, 0);
          }

          rte_mempool_create

          rte_mempool_create用來(lái)申請(qǐng)內(nèi)存保存固定大小的對(duì)象,會(huì)申請(qǐng)多個(gè)memzone,其中一個(gè)用于存放struct rte_mempool,其余的一個(gè)或者多個(gè)memzone存放固定大小的對(duì)象。后面介紹的mbuf會(huì)作為固定大小的對(duì)象存儲(chǔ)在mempool中。

          rte_pktmbuf_pool_create

          rte_mbuf用來(lái)存放報(bào)文,它是在應(yīng)用啟動(dòng)前調(diào)用rte_pktmbuf_pool_create申請(qǐng)好的內(nèi)存,后面申請(qǐng)和釋放只是指針的操作。
          name: mempool的名字。
          n: mempool中存放obj的個(gè)數(shù)。
          cache_size: 每個(gè)cpu緩存obj的最大個(gè)數(shù)。
          priv_size: mbuf結(jié)構(gòu)后面的內(nèi)存,可用來(lái)存放應(yīng)用的私有數(shù)據(jù)。
          data_room_size: mbuf中存放報(bào)文的空間大小。

          /* helper to create a mbuf pool */
          struct rte_mempool *
          rte_pktmbuf_pool_create(const char *name, unsigned n,
          unsigned cache_size, uint16_t priv_size, uint16_t data_room_size,
          int socket_id)
          {
          struct rte_mempool *mp;
          struct rte_pktmbuf_pool_private mbp_priv;
          const char *mp_ops_name;
          unsigned elt_size;
          int ret;

          if (RTE_ALIGN(priv_size, RTE_MBUF_PRIV_ALIGN) != priv_size) {
          RTE_LOG(ERR, MBUF, "mbuf priv_size=%u is not aligned\n",
          priv_size);
          rte_errno = EINVAL;
          return NULL;
          }

          //mempool中一個(gè)對(duì)象的大小
          elt_size = sizeof(struct rte_mbuf) + (unsigned)priv_size +
          (unsigned)data_room_size;
          mbp_priv.mbuf_data_room_size = data_room_size;
          mbp_priv.mbuf_priv_size = priv_size;

          //創(chuàng)建mempool結(jié)構(gòu),并插入共享鏈表rte_mempool_tailq
          mp = rte_mempool_create_empty(name, n, elt_size, cache_size,
          sizeof(struct rte_pktmbuf_pool_private), socket_id, 0);
          if (mp == NULL)
          return NULL;

          mp_ops_name = rte_eal_mbuf_default_mempool_ops();
          ret = rte_mempool_set_ops_byname(mp, mp_ops_name, NULL);
          if (ret != 0) {
          RTE_LOG(ERR, MBUF, "error setting mempool handler\n");
          rte_mempool_free(mp);
          rte_errno = -ret;
          return NULL;
          }
          rte_pktmbuf_pool_init(mp, &mbp_priv);

          //申請(qǐng)n個(gè)obj所占內(nèi)存,如果一個(gè)memzone不滿足,會(huì)申請(qǐng)多個(gè)memzone。將申請(qǐng)的memzone信息保存到rte_mempool_memhdr中,并插入mp->mem_list。同時(shí)將每個(gè)memzone按obj大小分成n份(每份就相當(dāng)于是一個(gè)mbuf),將每份的地址又保存到rte_mempool_objhdr,并插入mp->elt_list,然后將每份的地址入隊(duì)到mp->pool_data(rte_ring)。
          ret = rte_mempool_populate_default(mp);
          if (ret < 0) {
          rte_mempool_free(mp);
          rte_errno = -ret;
          return NULL;
          }

          //調(diào)用rte_pktmbuf_init初始化mbuf
          rte_mempool_obj_iter(mp, rte_pktmbuf_init, NULL);

          return mp;
          }

          初始化后,mempool內(nèi)存結(jié)構(gòu)如下、



          其中rte_mbuf的內(nèi)存結(jié)構(gòu)如下



          rte_pktmbuf_alloc

          rte_pktmbuf_alloc用來(lái)從指定的mempool中獲取一個(gè)mbuf,優(yōu)先從當(dāng)前cpu的cache里取,如果cache中沒(méi)了再?gòu)膒ool里取。

          static inline struct rte_mbuf *rte_pktmbuf_alloc(struct rte_mempool *mp)
          {
          struct rte_mbuf *m;
          if ((m = rte_mbuf_raw_alloc(mp)) != NULL)
          rte_pktmbuf_reset(m);
          return m;
          }

          3.3內(nèi)存池

          DPDK也有一個(gè)內(nèi)存池管理器,在整個(gè)DPDK中廣泛用于管理大型對(duì)象池,對(duì)象大小固定。它的用途很多——包輸入/輸出、加密操作、事件調(diào)度和許多其他需要快速分配或解除分配固定大小緩沖區(qū)的用例。DPDK內(nèi)存池針對(duì)性能進(jìn)行了高度優(yōu)化,并支持可選的線程安全(如果用戶不需要線程安全,則無(wú)需為之付費(fèi))和批量操作,所有這些都會(huì)導(dǎo)致每個(gè)緩沖區(qū)的分配或空閑操作周期計(jì)數(shù)達(dá)到兩位數(shù)以下。

          也就是說(shuō),即使DPDK內(nèi)存池的主題出現(xiàn)在幾乎所有關(guān)于DPDK內(nèi)存管理的討論中,從技術(shù)上講,內(nèi)存池管理器是一個(gè)建立在常規(guī)DPDK內(nèi)存分配器之上的庫(kù)。它不是標(biāo)準(zhǔn)DPDK內(nèi)存分配工具的一部分,它的內(nèi)部工作與DPDK內(nèi)存管理例程完全分離 (并且非常不同) 。因此,這超出了本系列文章的范圍。但是,有關(guān)DPDK內(nèi)存池管理器庫(kù)的更多信息可以在DPDK文檔中找到。

          瀏覽 85
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操久在线| 久草天堂 | 超碰人人操在线 | 免费的操逼网站 | 一级香蕉视频 |