P小二 P小二
← 返回文章 DPU 约 8 分钟

10.DPU网络开发SDK——DPDK(九)

DPU专栏:DPU网络开发SDK——DPDK(九)

在过去的几篇文章中,我们重点分析了DPDK初始化过程rte_eal_init()的主要流程,了解了其内存分配,primary和secondary之间如何实现数据共享。

Hello world例子中,在DPDK初始化完成之后,调用rte_eal_remote_launch()在指定的lcore上启动了一个线程。接下来简单分析一下该过程是如何实现的。

在init()过程的第30步中简单介绍了每个worker lcore上都会启动一个线程,入口函数是    (),该函数会无休止执行从main_to_worker管道中接收消息,然后执行lcore_config[lcore_id].f(),最后返回结果。rte_eal_remote_launch的实现上即是基于此,分析如下:

int rte_eal_remote_launch(int (*f)(void *), void *arg, unsigned int worker_id) { int n; char c = 0; int m2w = lcore_config[worker_id].pipe_main2worker[1]; //获取两个main到对应worker的两条管道 int w2m = lcore_config[worker_id].pipe_worker2main[0]; int rc = -EBUSY; if (lcore_config[worker_id].state != WAIT) // 当前lcore的状态不是WAIT状态时,表示有正在执行的任务,则退出 goto finish; lcore_config[worker_id].f = f; //设定新的要执行的入口func和参数 lcore_config[worker_id].arg = arg; n = 0; while (n == 0 || (n

在Hello world最后,调用rte_eal_mp_wait_lcore()对每个lcore调用rte_eal_wait_lcore(),该func是主要检查lcore_config[lcore_id].state是否是FINISH状态,如果是FINISH状态则改为WAIT状态,表示该lcore上可以接受下一次执行任务了。

至此DPDK的Hello world基本上分析完毕。接下来我们以DPDK项目中example/ethtool这个例子为例,分析一下如何使用DPDK对网卡进行操作。

int main(int argc, char **argv) { int cnt_args_parsed; uint32_t id_core; uint32_t cnt_ports; cnt_args_parsed = rte_eal_init(argc, argv); if (cnt_args_parsed MAX_PORTS) { printf(“Info: Using only %i of %i ports\n”, cnt_ports, MAX_PORTS ); cnt_ports = MAX_PORTS; } setup_ports(&app_cfg, cnt_ports); app_cfg.exit_now = 0; app_cfg.cnt_ports = cnt_ports; if (rte_lcore_count()

初始化完成之后,调用rte_eth_dev_count_avail()获取当前有多少个可用的网卡设备,

uint16_t rte_eth_dev_count_avail(void) { uint16_t p; uint16_t count = 0; RTE_ETH_FOREACH_DEV(p) count++; return count;}#define RTE_ETH_FOREACH_DEV(p) \ RTE_ETH_FOREACH_DEV_OWNED_BY(p, RTE_ETH_DEV_NO_OWNER)#define RTE_ETH_FOREACH_DEV_OWNED_BY(p, o) \ for (p = rte_eth_find_next_owned_by(0, o); \ (unsigned int)p owner.id != owner_id) port_id = rte_eth_find_next(port_id + 1); return port_id;}uint16_t rte_eth_find_next(uint16_t port_id) { while (port_id = RTE_MAX_ETHPORTS) return RTE_MAX_ETHPORTS; return port_id;}


从以上代码可看出,统计可用的dev数量是通过遍历rte_eth_devices数组中无owner,且状态不是unused的项的数量。rte_eth_devices数组是一个比较重要的数组,该数组的数据填充是由各驱动完成的。接下来我们以pci驱动的ixgbe设备为例,分析在DPDK中如何注册总线和在总线上注册驱动,以及网卡设备的探测工作。

在前面分析rte_eal_init()过程的第17步中,我们提到过初始化过程会调用rte_bus_scan(),scan过程是依次遍历rte_bus_list这个模块全局列表中的每一个总线元素,然后依次调用总线的scan方法。每一种总线是通过注册的方式添加到rte_bus_list这个列表中的,调用的方法是rte_bus_register()

- -
- -
- -
- -
- -
- -
- ```
void rte_bus_register(struct rte_bus *bus) {   RTE_VERIFY(bus);   RTE_VERIFY(bus->name && strlen(bus->name));    ……   RTE_VERIFY(bus->scan);   RTE_VERIFY(bus->probe);   RTE_VERIFY(bus->find_device);    ……   RTE_VERIFY(!bus->plug || bus->unplug);   TAILQ_INSERT_TAIL(&rte_bus_list, bus, next);    ……}

其中RTE_VERIFY这个宏定义检查指针是不是空的,是空的则抛出panic。最后将总线插入到列表中。该func是封装成下面的宏定义提供给总线驱动使用的:

#define RTE_REGISTER_BUS(nm, bus) \RTE_INIT_PRIO(businitfn_##nm, BUS) {\ (bus).name = RTE_STR(nm);\ rte_bus_register(&bus); }#define RTE_INIT_PRIO(func, prio) \static void attribute((constructor(RTE_PRIO(prio)), used)) func(void)


当PCI总线驱动注册时,使用方法如下:

- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- - ```
struct rte_pci_bus {   struct rte_bus bus;               /**

从结构上可以看出,PCI总线定义了自己的结构类型rte_pci_bus,该结构将rte_bus进行了封装,并额外增加了两个列表,PCI设备列表和设备驱动列表。在定义PCI总线的地方,调用了宏RTE_REGISTER_BUS,这个宏在展开之后,变成了如下内容:





- -
- - ```
static void __attribute__((constructor(RTE_PRIORITY_BUS), used)) businitfn_pci(void) {   (rte_pci_bus.bus).name = RTE_STR(pci);   rte_bus_register(&rte_pci_bus.bus);}

这里宏定义变成了一个C语言的构造函数,C语言的构造函数会在执行main函数之前就得以执行,故在DPDK进程正式开始执行前,总线就会被注册到总线列表当中了。

PCI的scan方法被调用时,会执行rte_pci_scan()

int rte_pci_scan(void) { struct dirent *e; DIR *dir; char dirname[PATH_MAX]; struct rte_pci_addr addr; …… dir = opendir(rte_pci_get_sysfs_path()); // /sys/bus/pci/devices if (dir == NULL) { RTE_LOG(ERR, EAL, “%s(): opendir failed: %s\n”, func, strerror(errno)); return -1; } …… while ((e = readdir(dir)) != NULL) { if (e->d_name[0] == ’.’) continue; if (parse_pci_addr_format(e->d_name, sizeof(e->d_name), &addr) != 0) continue; if (rte_pci_ignore_device(&addr)) continue; snprintf(dirname, sizeof(dirname), “%s/%s”, rte_pci_get_sysfs_path(), e->d_name); if (pci_scan_one(dirname, &addr)

该func中,会遍历系统的/sys/bus/pci/devices目录,获取每一个设备的addr信息,设备的addr信息实际上就是设备的PCI ID,分为以下4个部分:

struct rte_pci_addr { uint32_t domain; /**

获取设备的addr之后,如果这个设备不是需要无视的设备,那么调用pci_scan_one()在处理这个设备。

往期回顾:

09.DPU网络开发SDK—DPDK(八)

08.DPU网络开发SDK—DPDK(七)