rte_eal_remote_launch()

在过去的几篇文章中,我们重点分析了DPDK初始化过程rte_eal_init()的主要流程,了解了其内存分配,primary和secondary之间如何实现数据共享。Hello world例子中,在DPDK初始化完成之后,调用rte_eal_remote_launch()在指定的lcore上启动了一个线程。接下来简单分析一下该过程是如何实现的。

在init()过程的第30步中简单介绍了每个worker lcore上都会启动一个线程,入口函数是rte_eal_remote_launch  (),该函数会无休止执行从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 < 0 && errno == EINTR)) //通过main_to_worker管道发送消息告知worker的线程,有任务需要执行了n = write(m2w, &c, 1);if (n < 0)rte_panic("cannot write on configuration pipe\n");do { //通过worker_to_main管道接收消息,表示对端成功接收n = read(w2m, &c, 1);} while (n < 0 && errno == EINTR);if (n <= 0)rte_panic("cannot read on configuration pipe\n");rc = 0; //到此表示f()正常执行了
finish:rte_eal_trace_thread_remote_launch(f, arg, worker_id, rc); //这里是一个通过宏定义定义的func,会在日志中打印信息方便今后的追踪return rc;
}

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

example/ethtool

至此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 < 0)rte_exit(EXIT_FAILURE, "rte_eal_init(): Failed");cnt_ports = rte_eth_dev_count_avail();printf("Number of NICs: %i\n", cnt_ports);if (cnt_ports == 0)rte_exit(EXIT_FAILURE, "No available NIC ports!\n");if (cnt_ports > 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() < 2)rte_exit(EXIT_FAILURE, "No available worker core!\n");/* Assume there is an available worker.. */id_core = rte_lcore_id();id_core = rte_get_next_lcore(id_core, 1, 1);rte_eal_remote_launch(worker_main, NULL, id_core);ethapp_main();app_cfg.exit_now = 1;RTE_LCORE_FOREACH_WORKER(id_core) { if (rte_eal_wait_lcore(id_core) < 0)return -1;}return 0;
}

rte_eth_dev_count_avail()

初始化完成之后,调用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 < (unsigned int)RTE_MAX_ETHPORTS; \p = rte_eth_find_next_owned_by(p + 1, o))uint64_t rte_eth_find_next_owned_by(uint16_t port_id, const uint64_t owner_id) {port_id = rte_eth_find_next(port_id);while (port_id < RTE_MAX_ETHPORTS &&rte_eth_devices[port_id].data->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 &&rte_eth_devices[port_id].state == RTE_ETH_DEV_UNUSED)port_id++;if (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_bus_scan()

在前面分析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;               /**< Inherit the generic class */struct rte_pci_device_list device_list;  /**< List of PCI devices */struct rte_pci_driver_list driver_list;  /**< List of PCI drivers */
};struct rte_pci_bus rte_pci_bus = {.bus = {.scan = rte_pci_scan,.probe = pci_probe,.find_device = pci_find_device,.plug = pci_plug,.unplug = pci_unplug,.parse = pci_parse,.dma_map = pci_dma_map,.dma_unmap = pci_dma_unmap,.get_iommu_class = rte_pci_get_iommu_class,.dev_iterate = rte_pci_dev_iterate,.hot_unplug_handler = pci_hot_unplug_handler,.sigbus_handler = pci_sigbus_handler,},.device_list = TAILQ_HEAD_INITIALIZER(rte_pci_bus.device_list),.driver_list = TAILQ_HEAD_INITIALIZER(rte_pci_bus.driver_list),
};RTE_REGISTER_BUS(pci, rte_pci_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/devicesif (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) < 0)goto error;}closedir(dir);return 0;
error:closedir(dir);return -1;
}

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

struct rte_pci_addr {uint32_t domain;                /**< Device domain */uint8_t bus;                    /**< Device bus */uint8_t devid;                  /**< Device ID */uint8_t function;               /**< Device function. */
};

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

未完待续……

DPU网络开发SDK——DPDK(九)相关推荐

  1. DPU网络开发SDK——DPDK(二)

    Hello world 上一次的文章中主要介绍了DPDK是什么,主要用在什么地方.作为一个SDK,DPDK提供了大量的function接口用于网络转发面程序的编写.接下来的几篇文章,我们会基于DPDK ...

  2. DPU网络开发SDK——DPDK(十二)

    rte_bus_probe()->pci_probe_all_drivers()->rte_pci_probe_one_driver()->eth_ixgbe_pci_probe() ...

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

     rte_eal_init() 接上次内容继续对rte_eal_init()所做的工作进行分析. 24. 内存初始化 24.2. 内存分配 调用eal_memalloc_init()来处理内存分配,前 ...

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

    rte_eal_init() 接上次内容继续对rte_eal_init()所做的工作进行分析. 25. 内存堆初始化 调用rte_eal_malloc_heap_init()进行初始化. 如果进程类型 ...

  5. DPU网络开发SDK—DPDK(六)

    rte_eal_init() 接上次内容继续对rte_eal_init()所做的工作进行分析. 20. 大页内存配置 internal_conf中的no_hugetlbfs指明是否禁用大页内存,通过命 ...

  6. Senparc.Weixin.MP SDK 微信公众平台开发教程(九):自定义菜单接口说明

    Senparc.Weixin.MP SDK 微信公众平台开发教程(九):自定义菜单接口说明 原文:Senparc.Weixin.MP SDK 微信公众平台开发教程(九):自定义菜单接口说明 上一篇&l ...

  7. 麒麟系统开发笔记(九):在国产麒麟系统上搭建宇视摄像头SDK基础环境Demo

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/127532255 红胖子(红模仿)的博文大全:开发技术集 ...

  8. JavaCV音视频开发宝典:使用JavaCV读取海康平台或海康网络摄像头sdk回调视频TS码流并解析预览图像

    <JavaCV音视频开发宝典>专栏目录导航 <JavaCV音视频开发宝典>专栏介绍和目录 ​ 前言 两年前博主写了如何利用JavaCV解析各种h264裸流,<JavaCV ...

  9. 基于Qt的海康威视网络摄像头SDK的二次开发——摄像头登录和预览

    海康威视网络摄像头SDK的二次开发,需要摄像头IP地址,与主机的IP地址在同一网段,能够ping通摄像头IP,才能登录成功.摄像头是有账户名和密码,这需要记住,在开发的时候需要用到. 在开发前可以先用 ...

最新文章

  1. 读取Cert格式证书的密钥
  2. spring boot配置druid
  3. 深度学总结:RNN训练需要注意地方:pytorch每一个batch训练之前需要把hidden = hidden.data,否者反向传播的梯度会遍历以前的timestep
  4. osg图元绑定方式总结
  5. verilator编译 更新文件的规则
  6. 数据流图 系统流程图 程序流程图 系统结构图联系与区别
  7. 【今日CV 计算机视觉论文速览】 6 Mar 2019
  8. 金融数据分析与挖掘实战1.4.1-1.4.3
  9. 12.RabbitMQ实战 --- 聪明的Rabbit:扩展RabbitMQ
  10. U8修改销售订单模板
  11. Python人脸识别考勤打卡系统
  12. BackgroundWorker DoWork事件调用多次的问题
  13. catia设计树_CATIA目录树节点管理 | 坐倚北风
  14. dataframe 查找的isin()用法
  15. CNVD-2020-10487复现(Apache Tomcat文件包含漏洞)
  16. hyperlink的学习
  17. Kruskal算法的应用
  18. php接入阿里云OOS
  19. Codeforces Round #727 (Div. 2)2021.6.20
  20. 计算机网络地址块例题,计算机网络习题计算机络习题.ppt

热门文章

  1. Android中将像素转换为sp/dp
  2. 股票买卖接口源码分享
  3. Unity编写Shader内置各种矩阵和方法介绍
  4. 在Oracle中,如何定时清理INACTIVE状态的会话?
  5. 数据库忘记密码(重置用户密码方法教程)
  6. 趣图:程序员的鄙视链/图
  7. 如何让ie6 ie7 并存
  8. Simon IELTS: Reading
  9. 2022年终几段晋升、述职等汇报文案参考
  10. 数据结构(0719-林雪阵)