l2fwd + pdump 的 例程
修改 l2fwd 的 例程
在 l2fwd 的 例程上添加 pdump 的功能:
- 头文件引用
<rte_pdump.h>
。 signal_handler()
中添加rte_pdump_uninit()
。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 的 例程 报错的原因分析
- 第一个错误
dpdk-pdump 的 例程中,第一个报错如下:
EAL: Cannot get a virtual area at requested address: 0x7ffbef800000 (got 0x7ffbef543000) <-- eal_get_virtual_area() 抛出错误
其中的错误,出自 eal_get_virtual_area()
。
如果 requested_addr
和 aligned_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;
}
- 第二个错误
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;
}
- 再回溯一下
再回溯一下,可以找到是 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;
}
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()
中:
- 如果是主进程
RTE_PROC_PRIMARY
,则:
1.1. 调用rte_eal_config_create()
,创建内存配置文件
文件,并写入共享内存配置信息
。 - 辅进程
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");
}
}
- 主进程,创建并写入
共享内存配置信息
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;
}
- 辅进程,打开
内存配置文件
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;
}
- 辅进程,重新映射
内存配置文件
文件。
/* 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;
}
- 解答
- 在副进程调用
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