当前位置:   article > 正文

三、DPDK代码——初始化入口(2)_echo4>/sys/devices/system/node/node()/hugepages/hu

echo4>/sys/devices/system/node/node()/hugepages/hugepages-

转载请注明出处,谢谢。

–iova-mode=va

作为物理地址(PA)的IOVA模式

作为PA的IOVA模式下,分配到整个DPDK存储区的IOVA地址都是实际的物理地址,而虚拟内存的分配与物理内存的分配相匹配。该模式的一大优点就是它很简单:它适用于所有硬件(也就是说,不需要IOMMU),并且它适用于内核空间(将真实物理地址转换为内核空间地址的开销是微不足道的)。实际上,这就是DPDK长期以来的运作方式,在很多方面它都被认为是默认的选项。

然而,作为PA的IOVA模式也存在一些缺点。其中一个就是它需要根用户特权——如果无法访问系统的页面映射,DPDK就无法获取内存区域的真实物理地址。因此,如果系统中没有root权限,就无法以该模式运行。作为PA的IOVA模式还有另外一个值得一提的限制——虚拟内存分配要遵循物理内存分配。这意味着如果物理内存空间被分段(被分成许多小段而不是几个大段)时,虚拟内存空间也要遵循同样的分段。极端情况下,分段可能过于严重,导致被分割出来物理上连续的片段数量过多,耗尽DPDK用于存储这些片段相关信息的内部数据结构,就会让DPDK初始化失败. 这种方式的优点显而易见:作为VA的IOVA模式下,所有内存都是VA-和IOVA-连续的。这意味着所有需要大量IOVA连续内存的内存分配更有可能成功,因为对硬件来说,即使底层物理内存可能不存在,内存看上去还是IOVA连续的。由于重新映射,IOVA空间片段化的问题就变得无关紧要。不管物理内存被分段得多么严重,它总能被重新映射为IOVA-连续的大块内存

eal_hugepage_info_init()

         初始化hugepage_info

获取配置信息后eal_hugepage_info_init()

->hugepage_info_init()最后munmap(取消对应映射)。

                              其中hugepage_info_init()获取系统路径的内存大页信息,

然后由大到小进行qsort排序

eal_hugepage_info_init()该函数主要分析

echo 1024 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages

mount -t hugetlbfs nodev /mnt/huge

的配置,对大页进行填充到struct hugepage_info{}然后将info写入/var/run/dpdk/rte/hugepage_info

最后这里给hugedir进行了上锁。

1、读取/sys/kernel/mm/hugepages目录下的各个子目录,通过判断目录名称中包含"hugepages-"字符串,获取hugetlbfs的相关子目录,并获取hugetlbfs配置的内存页大小。例如:  

  [root@YMOS_DEFAULT ~]# ls -ltr /sys/kernel/mm/hugepages/

  total 0

  drwxr-xr-x 2 root root 0 2014-11-04 15:54 hugepages-2048kB

2、通过读取/proc/mounts信息,找到hugetlbfs的挂载点。例如:    

none /mnt/huge hugetlbfs rw,relatime 0 0

3、通过读取/sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages,获取配置的hugepages个数。

root@Ubuntu:~# cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages 64

4、以打开文件的方式,打开挂载点目录,为其FD设置互斥锁,

上述所有获取的信息,都保存在internal_config.hugepage_info[MAX_HUGEPAGES_SIZE]中,hugepage_info数据结构如下:

1 struct hugepage_info {

2   size_t hugepage_sz; /**< size of a huge page */

3   const char *hugedir; /**< dir where hugetlbfs is mounted */

4   uint32_t num_pages[RTE_MAX_NUMA_NODES];

5   /**< number of hugepages of that size on each socket */

6   int lock_descriptor; /**< file descriptor for hugepage dir */

7 };

  具体赋值如下,

  hpi->hugepage_sz = 2M;

  hpi->hugedir = /mnt/huge;

  hpi->num_pages[0] = 64; // 由于此时还不知道哪些内存页分处在哪个socket上,故,都先放在socket-0上。

  hpi->lock_descriptor = open(hpi->hugedir, O_RONLY); // 在读取hugetlbfs配置的时候,需要锁住整个目录。当所有hugepage_info结构(非mem)都mmap完成后,会解锁。

  5、将internal_config.hugepage_info[MAX_HUGEPAGES_SIZE]按内存页的大小排序

 

rte_eal_vfio_setup()

rte_eal_vfio_setup()   默认内核>3.6.0 以上支持 qemu-kvm需要加入vfio参数

VFIO是一个可以安全地把设备I/O、中断、DMA等暴露到用户空间(userspace),从而可以在用户空间完成设备驱动的框架。用户空间直接设备访问,虚拟机设备分配可以获得更高的IO性能。

依赖于IOMMU. vfio-pci.

相比于UIO,VFIO更为强健和安全

vfio是一套用户态驱动框架,主要提供两种基本服务:

1):向用户态提供访问硬件设备的接口。

2):向用户态提供提供IOmmu的接口.

在用户态将vfio分为container/group/device。通过打开dev/vfio 可以得到container,通过打来/dev/vfio/X 可以得到group。通过ioctl可以得到device.

VFIO是Linux Kernel UIO特性的升级版本。UIO的作用是把一个设备的IO和中断能力暴露给用户态,从而实现在用户态对硬件的直接访问。它的基本实现方法是,当我们probe一个设备的时候,通过uio_register_device()注册为一个字符设备/dev/uioN,用户程序通过对这个设备mmap访问它的IO空间,通过read/select等接口等待中断。VFIO通过IOMMU的能力来解决这个问题。IOMMU可以为设备直接翻译虚拟地址,这样我们在提供虚拟地址给设备前,把地址映射提供给VFIO,VFIO就可以为这个设备提供页表映射,从而实现用户程序的DMA操作。

背负提供DMA操作这个使命,VFIO要解决一个更大的问题,就是要把设备隔离掉。在Linux的概念中,内核是可信任的,用户程序是不可信任的,如果我们允许用户程序对设备做DMA,那么设备也是不可信任的,我们不能允许设备访问程序的全部地址空间(这会包括内核),所以,每个设备,针对每个应用,必须有独立的页表。这个页表,通过iommu_group承载(iommu_group.domain),和进程的页表相互独立。进程必须主动做DMA映射,才能把对应的地址映射写进去。

所以VFIO的概念空间是container和group,前者代表设备iommu的格式,后者代表一个独立的iommu_group(vfio中用vfio_group代表),我们先创建container,然后把物理的iommu_group绑定到container(类似iommu)上,让container解释group,之后我们基于group(类似用户进程的页表)访问设备(IO,中断,DMA等等)即可。

UIO的缺点在于,用户态的虚拟地址无法直接用于做设备的DMA地址。Dpdk的网卡驱动将网卡io缓存映射到接受的环形队列。直接把内存地址拷贝到IO空间的场景(相当于不做DMA)。我们有人通过UIO设备自己的ioctl来提供求物理地址的机制,从而实现DMA,但这种方案是有风险的,因为你做ioctl求得的物理地址,可能因为swap而被放弃,就算你做gup,但gup只保证物理内存不被释放,不能保证vma还指向这个物理页,要保证后者需要vm_pin这样的解决方案,但vm_pin根本就没有能够上传主线。

(dpdk保留了物理大页,防止被swap掉)。

rte_eal_memzone_init()

        初始化会构建RTE_MAX_MEMZONE 2560个memzones

对于memzones,直接使用rt_ememseg不一定缓存对齐,使用memzones较为方便,可以实现共享内存的快速访问。

rte_eal_memzone_init()->rte_fbarray_init()->eal_get_virtual_area()/resize_and_map()

struct rte_fbarray {

         char name[RTE_FBARRAY_NAME_LEN]; /**< name associated with an array */

         unsigned int count;              /**< number of entries stored */

         unsigned int len;                /**< current length of the array */

         unsigned int elt_sz;             /**< size of each element */

         void *data;                      /**< data pointer */

         rte_rwlock_t rwlock;             /**< multiprocess lock */

};

eal_get_virtual_area

首次调用(next_baseaddr == NULL)可以使用用户使用--baseaddr指定的地址,如果没有指定,使用默认baseaddr对应的地址。

接下来使用mmap申请一块只读匿名空间。

在上面内存申请完成后,resize_and_map将这些信息映射到共享文件,文件的路径默认是/var/run/dpdk/rte/fbarray_memzone,

实际就是申请了2560个72字节的memzone空间,使用fbarray结构管理2560个memzones空间。实际fbarray占用大小分data和mask 72x2560 =184,320->4k对齐后188416

rte_eal_memory_init()

(note:进行了2次map)

rte_eal_memory_init()

->rte_memseg_init()->rte_eal_memseg_init() type = primary->memseg_primary_init() -> eal_dymem_memseg_lists_init()

                                                                                                   ->memseg_secondary_init()

1、构建n_memtypes

2、根据类型确定memsegs的socket_id和page_size,然后分配页面,每个类型页面不超过128/ n_memtypes

Eg:对于2 socket  2 不同pagesize 有4种类型 对每一个memtype来说,需要n_seglists个segment list,每个segment list包含n_segs个segment,这个结构是使用上面说的fb_array表示的, alloc_memseg_list分配rte_memseg对应的fbarray, alloc_va_space分配匿名大页内存(read only)

https://img-blog.csdnimg.cn/20190429113135236.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3doZW5sb2Nl,size_16,color_FFFFFF,t_70

这里的rte_memseg写错,应为elt_sz,是fbarray结构

->eal_memalloc_init()->type = secondary rte_memseg_list_walk() 遍历memsegs 调用fd,为每个page(segment)分配对应的fd空间

->if type = primary ->rte_eal_hugepage_init() 根据是否有legacy_mem选择 eal_legacy_hugepage_init() or eal_dynmem_hugepage_init()

对于eal_legacy_hugepage_init(),

*  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
          *  8.remap_needed_hugepages

-> eal_dynmem_hugepage_init()计算hugepage_info信息和num_pages数量,然后分别计算不同socket_id的page数量,标记预分配页作为非空闲

  ->rte_eal_memdevice_init() 对全局nchanel和nrank初始化,仅对primary有效

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/541971
推荐阅读
相关标签
  

闽ICP备14008679号