模块之一: 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;
}

备注:

  1. dpdk的启动分主从关系的。主从的逻辑是怎么设计的呢?
  2. 3中任务类型, 正常执行, 循环执行里面分2种类型。
  3. 任务在执行的时候,已经可以确认出来了角色。 怎么做到的呢? 前置的准备有哪些?

看一下触发调度的入口:


putao
8 声望3 粉丝

推动世界向前发展,改善民生。


引用和评论

0 条评论