dpdk 多进程共享内存描述信息的机制 |
您所在的位置:网站首页 › 共享内存机制的原理是什么 › dpdk 多进程共享内存描述信息的机制 |
dpdk 多进程共享内存描述信息的机制
在 dpdk legacy memory 模型浅析 这篇文章中,我描述了 linux 平台 dpdk 早期版本的内存模型并分析了此模型下对 hugepage 的管理代码,在此模型中,dpdk primary 进程负责 layout hugepage 到 VA 中。为了支持多进程共享大页内存,dpdk primary 进程将大页内存初始化过程中得到的每个 memseg 与 hugepage file 信息通过 mmap 记录到特定文件中,secondary 进程通过 mmap 相同文件来访问这些信息并映射大页到相同的 VA 地址以共享 primary 进程映射的大页。 当 dpdk 进程完成了内存初始化后,在一个进程中使用 dpdk 内存分配接口分配的内存可以直接在其它 dpdk 进程中使用,可其它 dpdk 进程如何知道某个进程分配的某块区域的地址呢?显然需要引入新的描述信息来保存这一信息,且这些信息也需要在多进程之间共享。 dpdk 中抽象了 rte_config 结构来保存共享内存区域的描述信息。dpdk 多进程共享内存示意图如下: rte_config 结构定义如下: struct rte_config { uint32_t master_lcore; /**< Id of the master lcore */ uint32_t lcore_count; /**< Number of available logical cores. */ uint32_t service_lcore_count;/**< Number of available service cores. */ enum rte_lcore_role_t lcore_role[RTE_MAX_LCORE]; /**< State of cores. */ /** Primary or secondary configuration */ enum rte_proc_type_t process_type; /** PA or VA mapping mode */ enum rte_iova_mode iova_mode; /** * Pointer to memory configuration, which may be shared across multiple * DPDK instances */ struct rte_mem_config *mem_config;它可以划分为两部分: 每个进程的私有数据所有进程共享的共享内存描述数据共享内存描述数据为 rte_mem_config 结构,此结构定义如下: struct rte_mem_config { volatile uint32_t magic; /**< Magic number - Sanity check. */ /* memory topology */ uint32_t nchannel; /**< Number of channels (0 if unknown). */ uint32_t nrank; /**< Number of ranks (0 if unknown). */ /** * current lock nest order * - qlock->mlock (ring/hash/lpm) * - mplock->qlock->mlock (mempool) * Notice: * *ALWAYS* obtain qlock first if having to obtain both qlock and mlock */ rte_rwlock_t mlock; /**< only used by memzone LIB for thread-safe. */ rte_rwlock_t qlock; /**< used for tailq operation for thread safe. */ rte_rwlock_t mplock; /**< only used by mempool LIB for thread-safe. */ uint32_t memzone_cnt; /**< Number of allocated memzones */ /* memory segments and zones */ struct rte_memseg memseg[RTE_MAX_MEMSEG]; /**< Physmem descriptors. */ struct rte_memzone memzone[RTE_MAX_MEMZONE]; /**< Memzone descriptors. */ struct rte_tailq_head tailq_head[RTE_MAX_TAILQ]; /**< Tailqs for objects */ /* Heaps of Malloc per socket */ struct malloc_heap malloc_heaps[RTE_MAX_NUMA_NODES]; /* address of mem_config in primary process. used to map shared config into * exact same address the primary process maps it. */ uint64_t mem_cfg_addr; } __attribute__((__packed__));核心字段为 memseg、memzone、tailq_head、malloc_heaps,这些字段保存了整个共享的描述信息,实际运行中 dpdk 多进程之间内存的分配、查找、释放就是通过控制这些结构实现的,下面分别对这些结构的功能进行描述。 rte_memseg 结构此结构描述了单块物理内存连续区域的信息,其定义如下: struct rte_memseg { RTE_STD_C11 union { phys_addr_t phys_addr; /**< deprecated - Start physical address. */ rte_iova_t iova; /**< Start IO address. */ }; RTE_STD_C11 union { void *addr; /**< Start virtual address. */ uint64_t addr_64; /**< Makes sure addr is always 64 bits */ }; size_t len; /**< Length of the segment. */ uint64_t hugepage_sz; /**< The pagesize of underlying memory */ int32_t socket_id; /**< NUMA socket ID. */ uint32_t nchannel; /**< Number of channels. */ uint32_t nrank; /**< Number of ranks. */ } __rte_packed;它由 primary 进程初始化,保存大页的映射信息。secondary 进程通过访问此结构得到大页内存的 layout 信息,然后按照此信息 mmap 大页到 primary 进程中相同的地址以实现整个大页内存区域的共享。 rte_memzone 结构此结构描述一块在 memseg 上分配的带标识符的内存区域,标识符为字符串,其定义如下: struct rte_memzone { #define RTE_MEMZONE_NAMESIZE 32 /**< Maximum length of memory zone name.*/ char name[RTE_MEMZONE_NAMESIZE]; /**< Name of the memory zone. */ RTE_STD_C11 union { phys_addr_t phys_addr; /**< deprecated - Start physical address. */ rte_iova_t iova; /**< Start IO address. */ }; RTE_STD_C11 union { void *addr; /**< Start virtual address. */ uint64_t addr_64; /**< Makes sure addr is always 64-bits */ }; size_t len; /**< Length of the memzone. */ uint64_t hugepage_sz; /**< The page size of underlying memory */ int32_t socket_id; /**< NUMA socket ID. */ uint32_t flags; /**< Characteristics of this memzone. */ uint32_t memseg_id; /**< Memseg it belongs. */ } __attribute__((__packed__));name 唯一区分每块内存区域,在其它进程中可以以字符串为参数遍历 rte_mem_config 结构的 memzone 数组,匹配每个 memzone 结构的 name 来获取到某块内存区域的 rte_memzone 结构进而得到内存其实地址实现共享,这就是 rte_memzone_lookup 的实现原理。 rte_tailq_head 结构rte_tailq_head 是 dpdk 中双向链表的表头,dpdk 中为多个功能不同的内存结构注册不同的 tailq 链表用于多进程间共享。此结构定义如下: struct rte_tailq_head { struct rte_tailq_entry_head tailq_head; /**< NOTE: must be first element */ char name[RTE_TAILQ_NAMESIZE]; };rte_mem_config 中保存所有的 rte_tailq_head 信息,每一个 rte_tailq_head 的 name 唯一标识一个 tailq。 dpdk 通过共享 rte_tailq_head 头并在大页上分配 rte_tailq_head 链表中链入的每一个 rte_tailq_entry 来实现多进程间基于 tailq 获取共享内存信息功能。 malloc_heap 结构此结构维护大页内存分配信息,每个 numa 节点上有一个独立的结构以支持在特定 numa 节点上分配内存。其定义如下: struct malloc_heap { rte_spinlock_t lock; LIST_HEAD(, malloc_elem) free_head[RTE_HEAP_NUM_FREELISTS]; unsigned alloc_count; size_t total_size; }核心数据为 free_head,free_head 按照不同的内存大小范围分为不同的 free_list,free_list 链表的成员单位为 malloc_elem。free_head 内容分配方式如下: * Example element size ranges for a heap with five free lists: * heap->free_head[0] - (0 , 2^8] * heap->free_head[1] - (2^8 , 2^10] * heap->free_head[2] - (2^10 ,2^12] * heap->free_head[3] - (2^12, 2^14] * heap->free_head[4] - (2^14, MAX_SIZE]在内存释放时会根据大小放入对应的 free_head 中,在内存申请时也会根据大小计算需要查找的 free_head 来分配。malloc_elem 也在大页内存上分配,是 dpdk 内存分配的核心数据结构。 多进程与多线程自旋、读写锁涉及共享数据一般都离不开锁的使用,rte_mem_config 中有如下读写锁定义: rte_rwlock_t mlock; /**< only used by memzone LIB for thread-safe. */ rte_rwlock_t qlock; /**< used for tailq operation for thread safe. */ rte_rwlock_t mplock; /**< only used by mempool LIB for thread-safe. */malloc_heap 中有如下自旋锁定义: rte_spinlock_t lock;这些锁用于多线程互斥保护,而它们又在多进程之间共享,也用于多进程之间的共享数据保护。dpdk memzone、tailq、mempool 一般用于分配固定格式的数据,一次分配、多次访问,符合读写锁的使用场景,而 malloc_heap 结构会频繁的分配、释放,故而使用自旋锁。 这些锁都基于 dpdk 原生实现,工作在用户态,有很好的性能,然而却不支持异常回收。在一个进程中使用没有问题,在多进程中使用时,如果某个进程在临界区内异常退出,就会产生死锁。在 程序启动顺序引发的血案之 dpdk 进程死锁 这篇文章中,我描述了这个问题,并提供了相应的解决方案。 总结dpdk 多进程模型以共享大页内存为基础,实际实现中仅仅共享大页内存还不够,还需要共享内存分配的一些描述文件,这就是 rte_config 等结构的由来。使用这些结构,dpdk 能够灵活的在某个进程中找到在另外一个进程中分配的内存来使用,表面看上去好像不符合每个进程虚拟地址独立的特点,其实不过是因为这些虚拟地址指向的物理地址完全一致而已。 在实际使用场景中,常常需要在多线程与多进程模型之间进行选择,使用多线程机制不用创建许多进程,然而一个线程异常很大概率会影响整个进程的执行,而采用多进程模型,某个进程异常,其它进程仍旧能够正常工作。 基于可靠性考虑多进程模型是更好的选择。 dpdk 内存架构原生支持多进程模型,支持一个 primary 进程与多个 secondary,甚至于可以使用 --file-prefix 来创建多个 primary 进程,在开发时不用做额外修改即可使用。 |
今日新闻 |
点击排行 |
|
推荐新闻 |
图片新闻 |
|
专题文章 |
CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭 |