赞
踩
转载请注明出处,谢谢。
作为物理地址(PA)的IOVA模式
作为PA的IOVA模式下,分配到整个DPDK存储区的IOVA地址都是实际的物理地址,而虚拟内存的分配与物理内存的分配相匹配。该模式的一大优点就是它很简单:它适用于所有硬件(也就是说,不需要IOMMU),并且它适用于内核空间(将真实物理地址转换为内核空间地址的开销是微不足道的)。实际上,这就是DPDK长期以来的运作方式,在很多方面它都被认为是默认的选项。
然而,作为PA的IOVA模式也存在一些缺点。其中一个就是它需要根用户特权——如果无法访问系统的页面映射,DPDK就无法获取内存区域的真实物理地址。因此,如果系统中没有root权限,就无法以该模式运行。作为PA的IOVA模式还有另外一个值得一提的限制——虚拟内存分配要遵循物理内存分配。这意味着如果物理内存空间被分段(被分成许多小段而不是几个大段)时,虚拟内存空间也要遵循同样的分段。极端情况下,分段可能过于严重,导致被分割出来物理上连续的片段数量过多,耗尽DPDK用于存储这些片段相关信息的内部数据结构,就会让DPDK初始化失败. 这种方式的优点显而易见:作为VA的IOVA模式下,所有内存都是VA-和IOVA-连续的。这意味着所有需要大量IOVA连续内存的内存分配更有可能成功,因为对硬件来说,即使底层物理内存可能不存在,内存看上去还是IOVA连续的。由于重新映射,IOVA空间片段化的问题就变得无关紧要。不管物理内存被分段得多么严重,它总能被重新映射为IOVA-连续的大块内存
初始化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() 默认内核>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_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
(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)
这里的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有效
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。