红黑树 + 双链表最小调度器原型
300 行 C 代码跑通 Linux CFS 骨架:红黑树 + 双链表最小调度器原型
(附源码,直接 gcc 可跑)
标签:
#调度器 #红黑树 #CFS #Linux内核 #数据结构 #C语言
- 一分钟速览
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
换成对象池,就能无缝迁移到生产环境
- 整体架构
数据结构 职责 时间复杂度
红黑树 按 vruntime 排序,找最小节点 O(log n) 插入 / 删除 / 查找
双链表 按插入顺序串起所有任务,支持 O(1) 摘除 O(1) 插入 / 删除 / 遍历
两结构同时维护,但互不干扰:
enqueue
:先插红黑树,再挂链表尾pick_next
:取红黑树最左节点,同时从链表摘除put_prev
:任务用完时间片,重新计算 vruntime,再同时插回两个结构
- 核心代码走读
(完整源码见文末 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);
}
- 运行效果
$ 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)
- 从 demo 到生产:还差多远?
demo 现状 生产级改造
vruntime
随机加 用 sched_clock()
取真实运行时间
malloc/free
每任务 预分配对象池或slab
单线程 每 CPU 一个 runqueue,加自旋锁或CAS
无优先级 / 组调度 引入 load_weight
、cfs_rq->min_vruntime
、task_group
无负载均衡 触发 load_balance()
,在 CPU 之间拉任务
但核心数据结构已一模一样:Linux 5.x 源码 kernel/sched/fair.c
里的 cfs_rq->tasks_timeline
就是一棵红黑树,task->se.run_node
是链表节点。
-
小结
-
红黑树解决「谁该跑」
-
双链表解决「我能删」
-
两者正交,复杂度叠加但不冲突
-
300 行 C 文件即可跑通 1 kHz 级调度频率
-
把随机数换成真实时间、把 malloc 换成对象池,你就拥有了一个可嵌入 kernel 的 CFS 骨架
- 源码下载
GitHub:
https://github.com/yourname/mini-cfs
(单文件 runq.c
,MIT 协议,随意拿去玩)
如果对你有帮助,记得点个 Star ⭐ 再走~