模块之一: dpvs_scheduler_init
负责任务调度和负载均衡的初始化和终止。调度器管理不同处理器核心上的任务分配和执行
初始化一个二维数组用来维护全局的任务:
static struct list_head dpvs_lcore_jobs[LCORE_ROLE_MAX][LCORE_JOB_TYPE_MAX];
几种角色:
typedef enum dpvs_lcore_role_type {
LCORE_ROLE_IDLE, ///< 空闲角色,不执行任何任务
LCORE_ROLE_MASTER, ///< 主核心角色,通常用于控制和管理其他工作核心
LCORE_ROLE_FWD_WORKER, ///< 转发工作核心角色,主要用于数据包的转发处理
LCORE_ROLE_ISOLRX_WORKER, ///< 独立接收工作核心角色,专门用于数据包的接收处理
LCORE_ROLE_KNI_WORKER, ///< KNI(Kernel NIC Interface)工作核心角色,用于处理内核网络接口相关任务
LCORE_ROLE_MAX ///< 最大角色数,用于边界检查 (这是一个骚操作, 不建议这样写)
} dpvs_lcore_role_t;
任务类型:
typedef enum dpvs_lcore_job_type {
LCORE_JOB_INIT, ///< 初始化作业,通常用于系统启动时的初始化任务
LCORE_JOB_LOOP, ///< 循环作业,持续执行的任务,如数据包处理循环
LCORE_JOB_SLOW, ///< 慢速作业,间歇性执行的任务
LCORE_JOB_TYPE_MAX ///< 最大作业类型数,用于边界检查
} dpvs_lcore_job_t;
每一个任务的具体结构
struct dpvs_lcore_job {
char name[32]; ///< 作业名称,用于标识作业
void (*func)(void *arg); ///< 作业函数指针,指向具体的作业函数
void *data; ///< 作业数据,传递给作业函数的参数
dpvs_lcore_job_t type; ///< 作业类型,定义作业的类别
uint32_t skip_loops; ///< 仅用于 LCORE_JOB_SLOW 类型,表示跳过的循环次数
#ifdef CONFIG_RECORD_BIG_LOOP
uint32_t job_time[DPVS_MAX_LCORE]; ///< 记录每个 lcore 上作业执行的时间
#endif
struct list_head list; ///< 链表节点,用于将作业加入到链表中
} __rte_cache_aligned; ///< 确保结构体在缓存行对齐,以提高性能
dpvs_lcore_job_register:添加一个任务到全局数据结构当中。
dpvs_lcore_job_unregister:删除任务
dpvs_lcore_job_init: 输入各种参数创建一个任务。
do_lcore_job:执行任务的内部的函数, 具体的业务逻辑。
dpvs_lcore_start: 启动调度 (数据准备好之后, 不是立刻就初始化的,二维数组还没有业务逻辑的填充,调度的启动逻辑在main.c 中)。
在dpvs_scheduler_init 虽然没有立刻调度, 我可以先看一下逻辑 。带着问题继续问题。
int dpvs_lcore_start(int is_master) {
if (is_master)
return dpvs_job_loop(NULL);
return rte_eal_mp_remote_launch(dpvs_job_loop, NULL, SKIP_MAIN);
}
执行的逻辑。
static int dpvs_job_loop(void *arg) {
struct dpvs_lcore_job *job;
lcoreid_t cid = rte_lcore_id(); // 获取当前 lcore 的 ID
dpvs_lcore_role_t role = g_lcore_role[cid]; // 获取当前 lcore 的角色
this_poll_tick = 0;
#ifdef CONFIG_RECORD_BIG_LOOP
char buf[512];
uint32_t loop_time, thres_time;
uint64_t loop_start, loop_end;
static uint32_t longest_lcore_loop[DPVS_MAX_LCORE] = { 0 };
// 根据角色设置循环时间阈值
if (likely(role != LCORE_ROLE_MASTER))
thres_time = BIG_LOOP_THRESH_SLAVE;
else
thres_time = BIG_LOOP_THRESH_MASTER;
#endif
// 跳过无关的作业循环
if (role == LCORE_ROLE_MAX)
return EDPVS_INVAL;
if (role == LCORE_ROLE_IDLE)
return EDPVS_IDLE;
RTE_LOG(INFO, DSCHED, "lcore %02d enter %s loop\n", cid, dpvs_lcore_role_str(role));
// 执行初始化作业
list_for_each_entry(job, &dpvs_lcore_jobs[role][LCORE_JOB_INIT], list) {
do_lcore_job(job);
}
while (1) {
#ifdef CONFIG_RECORD_BIG_LOOP
loop_start = rte_get_timer_cycles(); // 获取循环开始时间
#endif
++this_poll_tick;
netif_update_worker_loop_cnt(); // 更新工作循环计数
// 执行正常作业
list_for_each_entry(job, &dpvs_lcore_jobs[role][LCORE_JOB_LOOP], list) {
do_lcore_job(job);
}
// 执行慢速作业
list_for_each_entry(job, &dpvs_lcore_jobs[role][LCORE_JOB_SLOW], list) {
if (this_poll_tick % job->skip_loops == 0) { // 根据 skip_loops 控制执行频率
do_lcore_job(job);
}
}
#ifdef CONFIG_RECORD_BIG_LOOP
loop_end = rte_get_timer_cycles(); // 获取循环结束时间
loop_time = (loop_end - loop_start) * 1000000 / g_cycles_per_sec; // 计算循环时间
if (loop_time > longest_lcore_loop[cid]) {
RTE_LOG(WARNING, DSCHED, "update longest_lcore_loop[%d] = %d (<- %d)\n",
cid, loop_time, longest_lcore_loop[cid]);
longest_lcore_loop[cid] = loop_time;
}
if (loop_time > thres_time) {
print_job_time(buf, sizeof(buf), role);
RTE_LOG(WARNING, DSCHED, "lcore[%d] loop over %d usecs (actual=%d, max=%d):\n%s\n",
cid, thres_time, loop_time, longest_lcore_loop[cid], buf);
}
#endif
}
return EDPVS_OK;
}
备注:
- dpdk的启动分主从关系的。主从的逻辑是怎么设计的呢?
- 3中任务类型, 正常执行, 循环执行里面分2种类型。
- 任务在执行的时候,已经可以确认出来了角色。 怎么做到的呢? 前置的准备有哪些?
看一下触发调度的入口:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。