l2fwd + pdump 的 例程 和 `--base-virtaddr` 选项


l2fwd + pdump 的 例程

修改 l2fwd 的 例程

在 l2fwd 的 例程上添加 pdump 的功能:

  1. 头文件引用 <rte_pdump.h>
  2. signal_handler() 中添加 rte_pdump_uninit()
  3. main() 中添加 rte_pdump_init()

差分结果

  • 左边 是修改前。
  • 右边 是修改后。添加了 pdump 的 功能。
	41c41,43
	< 
	---
	> #ifdef RTE_LIBRTE_PDUMP
	> #include <rte_pdump.h>
	> #endif
	509a512,515
	> #ifdef RTE_LIBRTE_PDUMP
	> 		/* uninitialize packet capture framework */
	> 		rte_pdump_uninit();
	> #endif
	536a543,548
	> 
	> #ifdef RTE_LIBRTE_PDUMP
	> 	#warning l2fwd with pdump
	> 	/* initialize packet capture framework */
	> 	rte_pdump_init(NULL);
	> #endif

启动 l2fwd 的 例程(没有设置好 --base-virtaddr

	./build/l2fwd -d librte_pmd_ixgbe.so -w 08:00.0 -w 08:00.1 -c 0xf0 -n 4 --proc-type primary  -- -q 8 -p 0x3 -T 1 --no-mac-updating

启动 dpdk-pdump 的 例程

	./dpdk-pdump -w 08:00.0 -w 08:00.1 -d librte_pmd_pcap.so -d librte_pmd_ixgbe.so --proc-type secondary -- --pdump 'port=0,queue=*,rx-dev=/tmp/capture.pcap'  
	
	>	EAL: Cannot get a virtual area at requested address: 0x7ffbef800000 (got 0x7ffbef543000)
	>	EAL: Could not mmap 17179869184 bytes at [0x7ffbef800000] - please use '--base-virtaddr' option
	>	EAL: Cannot preallocate VA space for hugepage memory
	>	EAL: FATAL: Cannot init memory
	>	EAL: Cannot init memory
    >	
	>	PANIC in main():
	>	Cannot init EAL

分析

由于 dpdk-pdump 的例程 无法 映射到 0x7ffbef800000,但却可以 映射到 0x7ffbef543000。所以出现错误退出。

EAL: Cannot get a virtual area at requested address: 0x7ffbef800000 (got 0x7ffbef543000) <-- eal_get_virtual_area() 抛出错误

dpdk-pdump 的例程 给出了提示:

EAL: Could not mmap 17179869184 bytes at [0x7ffbef800000] - please use ‘–base-virtaddr’ option <-- alloc_va_space() 的提示

所以需要修改 primary 进程(也就是 l2fwd 的 例程),启动的时候设置 --base-virtaddr

注意

dpdk-pdump 的例程 的命令行无需改变。

重新启动 l2fwd 的 例程(正确的设置好 --base-virtaddr

增加 l2fwd 的 例程的启动参数 --base-virtaddr。参数为 0x7ffbef543000
新的 l2fwd 的 例程的命令行参数如下:

	./build/l2fwd -d librte_pmd_ixgbe.so -w 08:00.0 -w 08:00.1 -c 0xf0 -n 4 --proc-type primary --base-virtaddr 0x7ffbef543000 -- -q 8 -p 0x3 -T 1 --no-mac-updating

使用 --base-virtaddr 的 原因

dpdk-pdump 的 例程 报错的原因分析

  1. 第一个错误

dpdk-pdump 的 例程中,第一个报错如下:

EAL: Cannot get a virtual area at requested address: 0x7ffbef800000 (got 0x7ffbef543000) <-- eal_get_virtual_area() 抛出错误

其中的错误,出自 eal_get_virtual_area()
如果 requested_addraligned_addr 不匹配。则提示出错。

eal_get_virtual_area() 的源码,经过简化后如下:

	void *
	eal_get_virtual_area(void *requested_addr, size_t *size,
			size_t page_sz, int flags, int mmap_flags)
	{
		/* ...  */
		
		/* 映射虚拟地址 */
		mapped_addr = mmap(requested_addr, (size_t)map_sz, PROT_READ,
				mmap_flags, -1, 0);

		/* 经过了简化,去除了地址对齐 */
		aligned_addr = mapped_addr :

		/* 如果 `requested_addr` 和 `aligned_addr` 不匹配。则提示出错 */
		if (requested_addr != NULL 
		&& 	aligned_addr != requested_addr) {
			RTE_LOG(ERR, EAL, "Cannot get a virtual area at requested address: %p (got %p)\n",
				requested_addr, aligned_addr);
			munmap(mapped_addr, map_sz);
			rte_errno = EADDRNOTAVAIL;
			return NULL;
		} 
		return aligned_addr;
	}
  1. 第二个错误

dpdk-pdump 的 例程中,第二个报错如下:

EAL: Could not mmap 17179869184 bytes at [0x7ffbef800000] - please use ‘–base-virtaddr’ option <-- alloc_va_space() 的提示

其中的错误,出自 alloc_va_space()
alloc_va_space()的实现,主要是计算 struct rte_memseg_list *msl的需要映射字节长度。
然后调用 eal_get_virtual_area()来映射地址。

	static int
	alloc_va_space(struct rte_memseg_list *msl)
	{
		uint64_t page_sz;
		size_t mem_sz;
		void *addr;
		int flags = 0;

		page_sz = msl->page_sz;
		mem_sz = page_sz * msl->memseg_arr.len;

		addr = eal_get_virtual_area(msl->base_va, &mem_sz, page_sz, 0, flags);
		if (addr == NULL) {
			if (rte_errno == EADDRNOTAVAIL)
				RTE_LOG(ERR, EAL, "Could not mmap %llu bytes at [%p] - please use '--base-virtaddr' option\n",
					(unsigned long long)mem_sz, msl->base_va);
			return -1;
		}
		msl->base_va = addr;

		return 0;
	}
  1. 再回溯一下

再回溯一下,可以找到是 memseg_secondary_init() 调用 alloc_va_space() 的。

如此可以画出相关的函数调用图:

	main
	+->	rte_eal_init
		+-> rte_eal_memory_init
			|	/* case: [secondary process type] */
			+-> memseg_secondary_init
				|	/* [遍历所有的 rte_memseg_list] { */
				+-> for (msl_idx = 0; msl_idx < RTE_MAX_MEMSEG_LISTS; msl_idx++) {
				+-> rte_fbarray_attach	/* attach to primary process memseg lists */				
				+-> alloc_va_space		/* preallocate VA space */
				|	+-> eal_get_virtual_area(requested_addr, ...)
				|		+-> mapped_addr = mmap(requested_addr)	/* 映射虚拟地址 */
				|			|	/* if [(requested_addr != NULL 	&& 	aligned_addr != requested_addr)] { */
				|			+-> RTE_LOG(ERR, EAL, "Cannot get a virtual area at requested address: %p (got %p)\n"
				|					, requested_addr
				|					, aligned_addr);
				+-> } /* end of for (msl_idx = 0; msl_idx < RTE_MAX_MEMSEG_LISTS; msl_idx++) */

memseg_secondary_init() 的作用的从 rte_eal_get_configuration() 中取得全局的内存设置 rte_config->mem_config
然后遍历其中所有的 rte_config->mem_config->memsegs[]
最后映射地址。

	static int
	memseg_secondary_init(void)
	{
		struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
		int msl_idx = 0;
		struct rte_memseg_list *msl;

		/* 遍历所有的 rte_memseg_list (`rte_config->mem_config->memsegs[]`)*/
		for (msl_idx = 0; msl_idx < RTE_MAX_MEMSEG_LISTS; msl_idx++) {

			msl = &mcfg->memsegs[msl_idx];
			
			/* ... */
			
			/* preallocate VA space */
			if (alloc_va_space(msl)) {
				RTE_LOG(ERR, EAL, "Cannot preallocate VA space for hugepage memory\n");
				return -1;
			}
		}

		return 0;
	}
  1. rte_config->mem_config 从哪里来?

跟踪 rte_config->mem_config 在哪里进行过改写。可以追查得到 rte_config_init()

从而画出函数 rte_config_init()的函数调用图:
可见 rte_config_init() 运行在 rte_eal_memory_init() 的前面。

	main
	+->	rte_eal_init
		|
		+-> rte_config_init
		|	.	/* case: [primary process type] */
		|	+-> rte_eal_config_create();
		|	.
		|	.	/* case: [secondary process type] */
		|	+-> rte_eal_config_attach();
		|	+-> rte_eal_mcfg_wait_complete(rte_config.mem_config);
		|	+-> rte_eal_config_reattach();
		|
		+-> rte_eal_memory_init
			.	/* case: [primary process type] */
			+-> memseg_primary_init
			.
			.	/* case: [secondary process type] */
			+-> memseg_secondary_init

rte_config_init() 中:

  1. 如果是主进程 RTE_PROC_PRIMARY,则:
    1.1. 调用 rte_eal_config_create(),创建内存配置文件文件,并写入共享内存配置信息
  2. 辅进程 RTE_PROC_SECONDARY,则:
    2.1. 调用 rte_eal_config_attach()。打开内存配置文件文件。
    2.2. 调用 rte_eal_mcfg_wait_complete()。用于 辅进程 等待 主进程 完成共享内存配置信息的初始化。
    2.3. 调用 rte_eal_config_reattach()。 重新映射内存配置文件文件。

注意:

  • 内存配置文件 的默认文件路径为 /var/run/.config
  • 重新映射内存配置文件文件。是为了保证主副进程的虚拟地址一致。
	static void
	rte_config_init(void)
	{
		rte_config.process_type = internal_config.process_type;

		switch (rte_config.process_type){
		case RTE_PROC_PRIMARY:
			rte_eal_config_create();
			break;
		case RTE_PROC_SECONDARY:
			rte_eal_config_attach();
			rte_eal_mcfg_wait_complete(rte_config.mem_config);
			rte_eal_config_reattach();
			break;
		case RTE_PROC_AUTO:
		case RTE_PROC_INVALID:
			rte_panic("Invalid process type\n");
		}
	}
  1. 主进程,创建并写入共享内存配置信息

rte_eal_config_create() 用于创建 内存配置文件 文件,并写入共享内存配置信息
以下是简化后的代码:


	static void
	rte_eal_config_create(void)
	{
		void *rte_mem_cfg_addr;
		int retval;
	
		/* 得到 `内存配置文件` 的路径,默认是`/var/run/.config` */
		const char *pathname = eal_runtime_config_path();

		/* 如果在 主进程 命令行中,有使用 `--base-virtaddr` 给定 `内存配置文件` 的虚拟地址,则使用。*/
		/* 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;

		/* 打开 `内存配置文件` */
		mem_cfg_fd = open(pathname, O_RDWR | O_CREAT, 0660);

		/* 调整 `内存配置文件` 的大小,可以容纳 `rte_config.mem_config` 所指向的数据结构 */
		retval = ftruncate(mem_cfg_fd, sizeof(*rte_config.mem_config));

		/* 文件加锁 */
		retval = fcntl(mem_cfg_fd, F_SETLK, &wr_lock);

		/* 映射出 `内存配置文件` 到虚拟地址 `rte_mem_cfg_addr` 
		 * 虚拟地址 `rte_mem_cfg_addr` 所指向空间,后续会写入 `共享内存配置信息`。
		 */
		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` 到 虚拟地址 `rte_mem_cfg_addr`
		 * `early_mem_config` 是一个全局变量。在没有内存映射之前,占一下位置。
		 * `rte_config.mem_config` 在初始的时候,就是指向 `early_mem_config`。
		 */
		memcpy(rte_mem_cfg_addr, &early_mem_config, sizeof(early_mem_config));
		
		/* `rte_config.mem_config` 指向 `内存配置文件` 的虚拟地址 `rte_mem_cfg_addr` 
		 * 在主进程后续的初始化中。对`rte_config.mem_config`的读写。
		 * 会直接写入到`内存配置文件`。
		 */
		rte_config.mem_config = rte_mem_cfg_addr;

		/* store address of the config in the config itself so that secondary
		 * processes could later map the config into this exact location */
		/* 保存 `内存配置文件` 的虚拟地址 `rte_mem_cfg_addr` 到 `内存配置文件` 中。
		 * 后续,辅进程 就可以使用。
		 */
		rte_config.mem_config->mem_cfg_addr = (uintptr_t) rte_mem_cfg_addr;

	}

  1. 辅进程,打开 内存配置文件

rte_eal_config_attach()。打开 内存配置文件 文件,并映射。

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

		/* 得到 `内存配置文件` 的路径,默认是`/var/run/.config` */
		const char *pathname = eal_runtime_config_path();

		/* 打开 `内存配置文件` */
		mem_cfg_fd = open(pathname, O_RDWR);

		/* 映射出 `内存配置文件` 
		 * 注意:
		 *    这是第一的映射,mmap 第一个 参数 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);

		rte_config.mem_config = mem_config;
	}
  1. 辅进程,重新映射内存配置文件文件。
	/* 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;

		/* 取得 `内存配置文件` 的 `实际`地址 */
		/* save the address primary process has mapped shared config to */
		rte_mem_cfg_addr = (void *) (uintptr_t) rte_config.mem_config->mem_cfg_addr;

		/* 取消之前的映射 */
		/* unmap original config */
		munmap(rte_config.mem_config, sizeof(struct rte_mem_config));

		/* 重新映射 `内存配置文件`
		 * 这样 主副进程的 `共享内存配置信息` 的虚拟地址都会是一致的了。
		 */
		/* 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);

		close(mem_cfg_fd);

		rte_config.mem_config = mem_config;
	}

  1. 解答
  • 在副进程调用eal_get_virtual_area()的过程中。很有可能无法映射到合适的地址。
  • 但是副进程会提示出可用的虚拟地址。如 (got 0x7ffbef543000):

EAL: Cannot get a virtual area at requested address: 0x7ffbef800000 (got 0x7ffbef543000)

  • 将副进程提示的可用的虚拟地址记录下来。
  • 重启主进程,在主进程 命令行中,使用 --base-virtaddr可用的虚拟地址,来指定虚拟地址。
  • 主进程在调用 rte_eal_config_create() 的时候,就可以用可用的虚拟地址作为的 内存配置文件 的虚拟地址,从而令到副进程可以映射得上。

ref:

dpdk quick start
dpdk-dev-how-to-get-base-virtaddr-when-using-dpdk

猜你喜欢

转载自blog.csdn.net/yk_wing4/article/details/88784230