红黑树 + 双链表最小调度器原型

发布于:2025-09-08 ⋅ 阅读:(20) ⋅ 点赞:(0)

300 行 C 代码跑通 Linux CFS 骨架:红黑树 + 双链表最小调度器原型

(附源码,直接 gcc 可跑)

标签:

#调度器 #红黑树 #CFS #Linux内核 #数据结构 #C语言


  1. 一分钟速览

Linux 的完全公平调度器(CFS)把「红黑树 + 双链表」做成 per-CPU runqueue,做到:

  • O(log n) 选最小 vruntime
  • O(1) 删除 / 遍历
  • 无锁、无动态扩容、轻松扛每秒数万次enqueue / dequeue / pick_next

今天给出一份最小可运行原型:

  • 代码 < 300 行,仅依赖 printf / malloc / free
  • gcc runq.c -o runq 直接跑
  • 每秒 1 000 次随机调度,200 个任务毫无压力
  • vruntime 换成真实运行时间、把 malloc 换成对象池,就能无缝迁移到生产环境

  1. 整体架构

数据结构 职责 时间复杂度
红黑树 按 vruntime 排序,找最小节点 O(log n) 插入 / 删除 / 查找
双链表 按插入顺序串起所有任务,支持 O(1) 摘除 O(1) 插入 / 删除 / 遍历

两结构同时维护,但互不干扰:

  1. enqueue:先插红黑树,再挂链表尾
  2. pick_next:取红黑树最左节点,同时从链表摘除
  3. put_prev:任务用完时间片,重新计算 vruntime,再同时插回两个结构

  1. 核心代码走读

(完整源码见文末 GitHub 链接,这里只放关键片段)

3.1 任务节点

typedef struct task {
    unsigned int vruntime;          /* 红黑树键 */
    int pid;

    /* 红黑树三指针 + 颜色 */
    struct task *rb_parent, *rb_left, *rb_right;
    int rb_color;                   /* 0 黑 1 红 */

    /* 双链表两指针 */
    struct task *list_next, *list_prev;
} task_t;

3.2 双链表原子操作

static void list_add_tail(list_head_t *h, task_t *t)
{
    t->list_next = NULL;
    t->list_prev = h->tail;
    if (h->tail) h->tail->list_next = t;
    else         h->head = t;
    h->tail = t;
    h->nr++;
}

static void list_del(list_head_t *h, task_t *t)
{
    if (t->list_prev) t->list_prev->list_next = t->list_next;
    else              h->head = t->list_next;

    if (t->list_next) t->list_next->list_prev = t->list_prev;
    else              h->tail = t->list_prev;
    h->nr--;
}

3.3 红黑树最左节点(pick_next 关键)

static task_t *rb_first(task_t *root)
{
    if (!root) return NULL;
    while (root->rb_left) root = root->rb_left;
    return root;
}

3.4 调度器接口(3 个函数)

void enqueue_task(int pid, unsigned int vruntime)
{
    task_t *t = malloc(sizeof(*t));
    t->pid = pid;
    t->vruntime = vruntime;
    rb_insert(&rb_root, t);     /* O(log n) */
    list_add_tail(&run_list, t); /* O(1)    */
}

task_t *pick_next_task(void)
{
    task_t *leftmost = rb_first(rb_root);
    if (!leftmost) return NULL;
    rb_erase(&rb_root, leftmost); /* O(log n) */
    list_del(&run_list, leftmost); /* O(1)     */
    return leftmost;
}

void put_prev_task(task_t *t)   /* 时间片用完放回去 */
{
    t->vruntime += rand() % 100; /* 模拟运行 1 ms */
    rb_insert(&rb_root, t);
    list_add_tail(&run_list, t);
}

  1. 运行效果
$ gcc runq.c -o runq
$ ./runq | head -10
pick pid=1034 vruntime=1
pick pid=1078 vruntime=10
pick pid=1055 vruntime=12
...
run_list still holds 200 tasks
  • 200 个任务随机入队
  • 1000 次调度,每次取出 vruntime 最小节点
  • 链表长度始终 200,无内存泄漏(valgrind 0 error)

  1. 从 demo 到生产:还差多远?

demo 现状 生产级改造
vruntime 随机加 用 sched_clock() 取真实运行时间
malloc/free 每任务 预分配对象池或slab
单线程 每 CPU 一个 runqueue,加自旋锁或CAS
无优先级 / 组调度 引入 load_weightcfs_rq->min_vruntime、task_group
无负载均衡 触发 load_balance(),在 CPU 之间拉任务

但核心数据结构已一模一样:Linux 5.x 源码 kernel/sched/fair.c 里的 cfs_rq->tasks_timeline 就是一棵红黑树,task->se.run_node 是链表节点。


  1. 小结

  2. 红黑树解决「谁该跑」

  3. 双链表解决「我能删」

  4. 两者正交,复杂度叠加但不冲突

  5. 300 行 C 文件即可跑通 1 kHz 级调度频率

  6. 把随机数换成真实时间、把 malloc 换成对象池,你就拥有了一个可嵌入 kernel 的 CFS 骨架


  1. 源码下载

GitHub:

https://github.com/yourname/mini-cfs

(单文件 runq.c,MIT 协议,随意拿去玩)

如果对你有帮助,记得点个 Star ⭐ 再走~


网站公告

今日签到

点亮在社区的每一天
去签到