1. DPDK核绑定的全局概览

DPDK核绑定的相关函数,都集中在rte_eal_init()函数中调用:
其中主要分为六部分:

  1. 检测所有的cpu。
  2. 解释核绑定相关参数。
  3. 主线程的核绑定。
  4. 中断处理线程的创建
  5. 副线程的创建。
  6. 线程启动和等待。

注意:

本文中,cpu所指的是机器上的逻辑核(也被称为logical processor,简称为processor)。

以下的是rte_eal_init()函数的调用图:

    main+-> rte_eal_init+-> rte_eal_log_early_init+-> eal_log_level_parse+-> rte_set_log_level||   /* <-- 检测所有的`cpu` -->*/+=> rte_eal_cpu_init|   +=> rte_eal_get_configuration                               /* 取得 全局变量`rte_config`的指针。 */|   +=> lcore_config[].detected = eal_cpu_detected(lcore_id);   /* 检测`cpu`是否存在 */|   +=> config->lcore_role[lcore_id] = ROLE_RTE;                /* 'ROLE_RTE`表示`cpu`存在 */|   +=> lcore_config[].core_id = eal_cpu_core_id(lcore_id);     /* 取得`cpu`的`lcore_id`。 */|   +=> lcore_config[].socket_id = eal_cpu_socket_id(lcore_id); /* 取得`NUMA node id`。 */||   /* <-- 解释核绑定相关参数 --> */+=> eal_parse_args|   +-> eal_parse_common_option|       ||       |   /* option: -c */|       +=> eal_parse_coremask|       ||       |   /* option: -l */|       +=> eal_parse_corelist|       ||       |   /* option: --proc-type */|       +=> eal_parse_proc_type|       ||       |   /* option: --master-lcore */|       +=> eal_parse_master_lcore|       ||       |   /* option: --lcores */|       +=> eal_parse_lcores|+-> rte_srand(rte_rdtsc());+-> rte_config_init+-> rte_eal_pci_init+-> rte_eal_vfio_setup+-> rte_eal_ivshmem_init+-> rte_eal_memory_init+-> eal_hugedirs_unlock+-> rte_eal_memzone_init+-> rte_eal_tailqs_init+-> rte_eal_ivshmem_obj_init+-> rte_eal_log_init+-> rte_eal_alarm_init+-> rte_eal_timer_init+-> eal_check_mem_on_local_socket+-> eal_plugins_init||   /* <--- `主线程`的核绑定 ---> */+=> eal_thread_init_master(rte_config.master_lcore) /* 主线程绑核 */|   +=> RTE_PER_LCORE(_lcore_id) = lcore_id;        /* 使用`线程变量`记录`lcore`下标 */|   +=> eal_thread_set_affinity()                   /* 线程绑定`cpu` */|       +=> rte_gettid                              /* 使用`线程变量`记录`线程号` */|       |   +=> static RTE_DEFINE_PER_LCORE(int, _thread_id); /* 声明静态的`线程变量`记录`线程号` */|       |   +=> RTE_PER_LCORE(_thread_id) = rte_sys_gettid(); /* 取得`线程号` */|       |       +=> syscall(SYS_gettid);                        /* 系统函数取得`线程号` */|       +=> rte_thread_set_affinity(&lcore_config[lcore_id].cpuset); /* 线程核绑定 */|           +=> pthread_setaffinity_np                          /* pthread库的线程核绑定 */|           |   /* 使用实际核绑定后的`cpusetp`,更新到相关线程变量`RTE_PER_LCORE`和全局变量`lcore_config` */|           +=> RTE_PER_LCORE(_socket_id) = eal_cpuset_socket_id(cpusetp);|           +=> memmove(&RTE_PER_LCORE(_cpuset), cpusetp,...);|           +=> lcore_config[lcore_id].socket_id = RTE_PER_LCORE(_socket_id);|           +=> memmove(&lcore_config[lcore_id].cpuset, cpusetp, sizeof(rte_cpuset_t));|+-> eal_thread_dump_affinity    /* 打印核绑定设置 */|+-> rte_eal_dev_init        /* init pmd devices */|  |   /* <--- `中断处理线程`的创建 ---> */+-> rte_eal_intr_init /* init interrupt-handling */|   +-> pthread_create(&intr_thread, NULL, eal_intr_thread_main, NULL);|   |   +~> eal_intr_thread_main    /* 这个处理中断的线程是没有绑核的 */|   +-> rte_thread_setname ("eal-intr-thread")||   /* <--- `副线程`的创建 ---> */|   /* 遍历所有的`副线程`*/+=> RTE_LCORE_FOREACH_SLAVE(i)|   /* 创建`主线程`和`副线程`间的沟通管道 */+=> pipe(lcore_config[i].pipe_master2slave);+=> pipe(lcore_config[i].pipe_slave2master);|   /* 创建`副线程` */+=> pthread_create(&lcore_config[i].thread_id, NULL, eal_thread_loop, NULL);|   +~> eal_thread_loop|       +=> eal_thread_set_affinity /*`副线程`核绑定*/|       +=> read(m2s, &c, 1);       /* 等待`主线程`发送到`副线程`的消息 */|       +=> write(s2m, &c, 1);      /* `副线程`确认收到`主线程`的消息 */|       +=> lcore_config[lcore_id].f(fct_arg); /* 执行`业务处理回调函数` */|+-> rte_thread_setname              /*`副线程`重命名*/||   /* <--- `线程`启动和等待 ---> */+=> rte_eal_mp_remote_launch(sync_func, NULL, SKIP_MASTER); /* 设置每一个`副线程`的回调函数为`sync_func()` */|   +=> RTE_LCORE_FOREACH_SLAVE(lcore_id)           /* 遍历所有的`副线程`来执行回调函数 */|   +=> rte_eal_remote_launch(f, arg, lcore_id);    /* `副线程`来执行回调函数 */|       +=> lcore_config[slave_id].f = f;           /* 设置`副线程`的`业务函数f` */|       +=> lcore_config[slave_id].arg = arg;       /* 设置`副线程`的`参数arg` */|       +=> write(m2s, &c, 1);                      /* 发送消息到`副线程`,通知`副线程`执行`业务函数`。 */|       +=> read(s2m, &c, 1);                       /* 等待`副线程`的确认。并判断`副线程`启动是否有异常。 */+=> rte_eal_mp_wait_lcore();                        /* 等待所有线程返回 */|   +=> RTE_LCORE_FOREACH_SLAVE(lcore_id);          /* 遍历所有的`副线程` */|   +=> rte_eal_wait_lcore(lcore_id);               /* 等待某一`副线程`返回 */|+-> rte_eal_pci_probe+-> rte_eal_mcfg_complete

说明:

  1. 以下的例子采用的是同一物理构造的机器。
  2. 机器有 2 路物理cpu插槽。单颗cpu插槽有 12 个核。
  3. 没有开启超线程。所以单颗cpu插槽的cpu数也是 12 个。
  4. 2 路合计cpu数为 24 个。

2. 数据结构和全局变量

2.1. rte_cpuset_t类型

rte_cpuset_t类型在 linux 中其实就是系统的cpu_set_t类。
其中 __bits[] 上的每一位代表了一个cpu。

    typedef unsigned long __cpu_masktypedef struct {__cpu_mask __bits[16];} cpu_set_t;typedef cpu_set_t rte_cpuset_t;

注意:

  • 使用CPU_ZERO(),CPU_SET()等宏函数来操作rte_cpuset_t类型。

2.2. struct lcore_config结构体

struct lcore_config结构体,用于表示一个lcore的用户设置。其中可以将成员变量分为三类:

  1. 核绑定相关成员变量。
  2. 主副线程通信相关成员变量。
  3. 业务处理相关成员变量。

注意:

  • lcore实际上是使用线程来实现。两者在后续的描述中可能会混用。请根据上下文理解。

详细的描述如下:

核绑定相关成员变量:

detected:cpu是否存在。

0:不存在。
非0:存在。

socket_id:cpu所在的NUMA node id。(这里没有任何错误,请看下面详细的说明!)
这里的socket_id字段的名字容易误导。通过分析代码发现:
由于在 OS 层中可以自由开启或关闭NUMA功能。从而在不同的情况下,NUMA node_id会有所不同。
最简单的例子就是,关闭NUMA功能后,所有的cpu都会属于node0。但是cpu的总数是没有改变的。

而物理cpu插槽的标识号,是由主板电路决定的。

物理cpu插槽的标识号,是不会随NUMA功能的开关而影响的。
物理cpu插槽的标识号,可以通过cat /sys/devices/system/cpu/cpu${cpu_index}/topology/physical_package_id指令得出。

其中${cpu_index}是cpu序号。

DPDK中的socket_id字段在分析代码后,其实所指的是cpu所在的NUMA node id。
NUMA node id的数值为/sys/devices/system/cpu/cpu${cpu_index}/node${node_id}的${node_id}。
其中 ${cpu_index} 是cpu序号。
在后续的描述中。代码上会保留使用socket_id;但是在解释中会使用NUMA node_id来表达。
core_id:cpu的标识号。数值与硬件相关。
cpu的标识号不一定连续。
cpu的标识号数值为 /sys/devices/system/cpu/cpu${cpu_index}/topology/core_id。其中${cpu_index}是cpu序号。
core_index:lcore的序号。
有效的序号从零开始,且是连续的。
-1:无效数据。

只有core_index可以唯一的区分lcore。

cpuset

lcore所绑定的cpuset。
lcore只可以绑定到一个cpu上。当使用-l或者-c命令行参数。
lcore可以绑定到多个cpu上。当使用--lcores命令行参数。
主副线程通信相关成员变量:

  • pipe_master2slave[2]:主线程到副线程的通信管道。
  • pipe_master2slave[0],表示的是主线程到副线程管道的读端。
  • pipe_master2slave[1],表示的是主线程到副线程管道的写端。
  • pipe_slave2master[2]:副线程到主线程的通信管道。
  • pipe_slave2master[0],表示的是副线程到主线程`管道的读端。
  • pipe_slave2master[1],表示的是主线程到副线程管道的写端。

业务处理相关成员变量:

  1. thread_id:线程号。
  2. f:用户业务函数。
  3. arg:输入到用户业务函数的参数。
  4. ret:用户业务函数的返回值。
  5. state:线程的状态。
  6. WAIT:等待命令。
  7. RUNNING:线程正在运行业务函数。
  8. FINISHED:线程运行完业务函数。

注意:

  1. struct lcore_config结构体中,成员变量core_id,socket_id,无法可靠区分lcore。
  2. 因为当NUMA关闭的时候。socket_id的数值会全部变为0,而core_id却有可能会重复。
  3. 详细的例子请看后续的rte_eal_cpu_init()函数。
  4. socket_id的主要作用是识别NUMA node,用于内存的分配。
  5. struct lcore_config结构体中,成员变量core_index和thread_id,无论NUMA是否开启,都可以正确区分lcore。

结构体源码如下:

    /*** Structure storing internal configuration (per-lcore)*/struct lcore_config {unsigned detected;         /**< true if lcore was detected */pthread_t thread_id;       /**< pthread identifier */int pipe_master2slave[2];  /**< communication pipe with master */int pipe_slave2master[2];  /**< communication pipe with master */lcore_function_t * volatile f;         /**< function to call */void * volatile arg;       /**< argument of function */volatile int ret;          /**< return value of function */volatile enum rte_lcore_state_t state; /**< lcore state */unsigned socket_id;        /**< physical socket id for this lcore */unsigned core_id;          /**< core number on socket for this lcore */int core_index;            /**< relative index, starting from 0 */rte_cpuset_t cpuset;       /**< cpu set which the lcore affinity to */};

2.2.1. 全局变量 lcore_config

全局变量lcore_config[]数组,表示lcore的用户设置。

全局变量lcore_config[]的定义如下:

    /* internal configuration (per-core) */struct lcore_config lcore_config[RTE_MAX_LCORE];

注意:
全局变量lcore_config[n]的下标比struct lcore_config结构体中的core_idcore_index的作用还要大。具体请看RTE_LCORE_FOREACH_SLAVE()宏函数的实现。

2.3. struct rte_config结构体

struct rte_config结构体,用于记录lcore和内存在DPDK应用程序中的设置。

成员变量描述如下:

master_lcore:主线程所在的lcore的序号(序号从零开始,并且是连续的)。
lcore_count:机器上所有lcore的个数。
lcore_role:每一个lcore的状态。
ROLE_OFF:lcore没有在DPDK中使用。
ROLE_RTE:lcore在DPDK中使用。
process_type:进程是主进程还是副进程。
RTE_PROC_AUTO:自动检测。
RTE_PROC_PRIMARY:默认值。主进程。
RTE_PROC_SECONDARY:副进程。
RTE_PROC_INVALID:无效进程。
mem_config:内存设置。
源代码如下:

    /*** The global RTE configuration structure.*/struct rte_config {/* master lcore 的 id */uint32_t master_lcore;       /**< Id of the master lcore */uint32_t lcore_count;        /**< Number of available logical 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;/*** Pointer to memory configuration, which may be shared across multiple* DPDK instances*/struct rte_mem_config *mem_config;} __attribute__((__packed__));

2.3.1. 全局变量rte_config

全局变量rte_config,表示DPDK的用户配置。

全局变量rte_config的定义如下:

    /* Address of global and public configuration */static struct rte_config rte_config = {.mem_config = &early_mem_config,};

3. 检测所有的cpu

rte_eal_cpu_init()函数用于检测所有的cpu。并用来初始化全局变量rte_config和lcore_config[]。

函数流程如下:

使用eal_cpu_detected函数,遍历所有的路径 /sys/devices/system/cpu/cpu${cpu_index}。其中 ${cpu_index} 从0到RTE_MAX_LCORE - 1。

  • 1.1. 如果路径 /sys/devices/system/cpu/cpu${cpu_index}不存在:
  • 1.2.1. lcore_config[lcore_id].cpuset设置为0。
  • 1.1.2. rte_config.lcore_role[${cpu_index}]设置为ROLE_OFF。
  • 1.1.3. lcore_config[${cpu_index}].core_index设置为-1。
  • 1.1.4. lcore_config[${cpu_index}].core_id设置为0。
  • 1.1.5. lcore_config[${cpu_index}].socket_id设置为0。
  • 1.2. 如果路径 /sys/devices/system/cpu/cpu${cpu_index}存在:
  • 1.2.1. lcore_config[lcore_id].cpuset设置为0x1U << lcore_id。
  • 1.2.2. rte_config.lcore_role[${cpu_index}]设置为ROLE_RTE。
  • 1.2.3. lcore_config[${cpu_index}].core_index设置为core index。
  • 1.2.4. lcore_config[${cpu_index}].core_id设置为eal_cpu_core_id(${cpu_index})。
  • 1.2.5. lcore_config[${cpu_index}].socket_id设置为eal_cpu_socket_id(${cpu_index})。

rte_config.lcore_count设置为 机器上所有cpu的个数。

函数调用图如下:

    rte_eal_init+-> rte_eal_cpu_init+-> rte_eal_get_configuration                               /* 取得 全局变量`rte_config`的指针。 */+-> lcore_config[].detected = eal_cpu_detected(lcore_id);   /* 检测`cpu`是否存在 */+-> config->lcore_role[lcore_id] = ROLE_RTE;                /* 'ROLE_RTE`表示`cpu`存在 */+-> lcore_config[].core_id = eal_cpu_core_id(lcore_id);     /* 取得`cpu`的`lcore_id`。 */+-> lcore_config[].socket_id = eal_cpu_socket_id(lcore_id); /* 取得`NUMA node id`。 */

rte_eal_cpu_init简化后的代码;

    intrte_eal_cpu_init(void){/* pointer to global configuration */struct rte_config *config = rte_eal_get_configuration();unsigned lcore_id;unsigned count = 0;/** Parse the maximum set of logical cores, detect the subset of running* ones and enable them by default.*/for (lcore_id = 0; lcore_id < RTE_MAX_LCORE; lcore_id++) {lcore_config[lcore_id].core_index = count;/* init cpuset for per lcore config */CPU_ZERO(&lcore_config[lcore_id].cpuset);/* in 1:1 mapping, record related cpu detected state */lcore_config[lcore_id].detected = eal_cpu_detected(lcore_id);if (lcore_config[lcore_id].detected == 0) {config->lcore_role[lcore_id] = ROLE_OFF;lcore_config[lcore_id].core_index = -1;continue;}/* By default, lcore 1:1 map to cpu id */CPU_SET(lcore_id, &lcore_config[lcore_id].cpuset);/* By default, each detected core is enabled */config->lcore_role[lcore_id] = ROLE_RTE;lcore_config[lcore_id].core_id = eal_cpu_core_id(lcore_id);lcore_config[lcore_id].socket_id = eal_cpu_socket_id(lcore_id);count++;}/* Set the count of enabled logical cores of the EAL configuration */config->lcore_count = count;return 0;}

例子:

不论是否开启NUMA功能。rte_eal_cpu_init函数运行完后rte_config.lcore_count都为 24。
但是rte_config.lcore_count在后面,经过解释核绑定相关参数后,会有可能修改。

NUMA关闭时:
使用lscpu查看到的系统配置为:

    lscpu>    Architecture:          x86_64>    CPU(s):                24          # 总`cpu`数>    On-line CPU(s) list:   0-23        # `cpu序号`>    Thread(s) per core:    1           # 每个`核`的`线程`个数(没有开启`超线程`)>    Core(s) per socket:    12          # 每个`cpu插槽`的`核`数>    Socket(s):             2           # `cpu插槽`个数>    NUMA node(s):          1           # `NUMA node`个数>    NUMA node0 CPU(s):     0-23        # `node 0`上`cpu`序号

eal_cpu_detected运行完后,lcore_config[n]rte_config.lcore_role[n] 的数值如下表所示:

DPDK lcore学习笔记相关推荐

  1. 《深入浅出DPDK》读书笔记(四):并行计算-SIMD是Single-Instruction Multiple-Data(单指令多数据)

    本文内容为读书笔记,摘自<深入浅出DPDK>. 47.提高处理器主频率对于性能的提升作用是明显而直接的.但一味地提高频率很快会触及频率墙,因为处理器的功耗正比于主频的三次方. 48.提高并 ...

  2. 深入浅出DPDK学习笔记(3)——— Cache和内存

    深入浅出DPDK学习笔记(3)--- Cache和内存 系统架构的演进 Cache系统简介 Cache的种类 TLB Cache Cache地址映射和变换 全关联型Cache 直接关联型Cache 组 ...

  3. 《深入浅出DPDK》读书笔记(十三):DPDK虚拟化技术篇(加速包处理的vhost优化方案)

    Table of Contents 加速包处理的vhost优化方案 142.vhost的演进和原理 143.Qemu与virtio-net 144.Linux内核态vhost-net 145.用户态v ...

  4. DPDK技术系统学习一(接收,发送,arp,icmp功能测试)

    如何技术不去手动做练习实践,就总有一种无从下手的感觉 文末附上小编总结的DPDK学习路线图以及我的学习资料. 0:准备环境并启动,使用dpdk接管其中一个网卡. ubuntu虚拟机环境配置多队列网卡, ...

  5. PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 call

    您的位置 首页 PyTorch 学习笔记系列 PyTorch 学习笔记(六):PyTorch hook 和关于 PyTorch backward 过程的理解 发布: 2017年8月4日 7,195阅读 ...

  6. 容器云原生DevOps学习笔记——第三期:从零搭建CI/CD系统标准化交付流程

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  7. 容器云原生DevOps学习笔记——第二期:如何快速高质量的应用容器化迁移

    暑期实习期间,所在的技术中台-效能研发团队规划设计并结合公司开源协同实现符合DevOps理念的研发工具平台,实现研发过程自动化.标准化: 实习期间对DevOps的理解一直懵懵懂懂,最近观看了阿里专家带 ...

  8. 2020年Yann Lecun深度学习笔记(下)

    2020年Yann Lecun深度学习笔记(下)

  9. 2020年Yann Lecun深度学习笔记(上)

    2020年Yann Lecun深度学习笔记(上)

最新文章

  1. 从零开始学习tensorflow2.0之熟悉tf2.0的数据
  2. 零基础入门学习Python(17)-函数的参数
  3. 让你大脑变冷静的28句英文
  4. 第18节 知识管理
  5. 通过VMware vsphere搭建集群
  6. IOS对plist配置文件的读写操作
  7. NAT与DHCP协议
  8. use 在php 用法中的总结
  9. 上海美特斯邦威成被执行人 执行标的超79万
  10. 大学英语计算机开学考试试题,2018年全国大学英语四级考试阅读理解试题:学习计算机...
  11. 【论文总结】TextGCN
  12. SDP在SIP协议中的应用
  13. cuteftp pro 3.2多线程下载导致文件MD5校验值改变
  14. 3Dmax建模教程详细步骤3D建模速成入门到高级教程 小白必看
  15. AutoCAD .Net 创建椭圆Ellipse
  16. 图像分割目标检测论文集锦
  17. ipados 蓝牙 android,iPadOS13.4对于无线或蓝牙鼠标的兼容性… - Apple 社区
  18. IOS逆向之汇编基础
  19. Web开发浅涉(以JAVA为例)
  20. 分布式事务解决方案(二)

热门文章

  1. 编写android驱动程序,Android 驱动编写LED-NDK程序
  2. Spring框架----IOC的概念和作用之工厂模式
  3. 操作系统——生产者-消费者问题
  4. SSH框架hibernate无法添加或修改,saveorupdate方法失效
  5. runtime的意义
  6. practice:在win2008R2上使用(NLB)网络负载均衡
  7. Android学习笔记——ProgressBarHandler
  8. postgreSQL源码分析——索引的建立与使用——GIST索引(3)
  9. matlab 曲面拟合_利用python进行曲面拟合并进行3D显示
  10. 若某计算机字长为16位,题目来源于王道论坛 某计算机字长为16位,主存地址空间...