当前位置: 首页 > news >正文

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

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 ⭐ 再走~

http://www.xdnf.cn/news/1465237.html

相关文章:

  • 【JMeter】分布式集群压测
  • 解锁上下文的力量:大型语言模型中的上下文工程全解析
  • Java基础篇02:基本语法
  • CAD:修改
  • 23.【C++进阶】异常(try、catch、throw)
  • SQL表一共有几种写入方式
  • 零基础入门AI: YOLOv5 详解与项目实战
  • 数据库存储大量的json文件怎么样高效的读取和分页,利用文件缓存办法不占用内存
  • 数据结构:排序
  • 【Day21】146.LRU缓存 (Least Recently Used)
  • 详细解读Docker
  • STC携手VEX发起全球首个碳资产RWA生态,泰国峰会即将引爆绿色金融
  • 飞算JavaAI炫技赛:电商系统开发全流程实战解析
  • 卫星在轨光压计算详解
  • openharmony之AV_CodeC音视频编解码模块详解(二)
  • (未完待续...)如何编写一个用于构建python web项目镜像的dockerfile文件
  • Kubernetes实战系列(4)
  • v4l2设置图像分辨率失败的问题
  • react+umi项目如何添加electron的功能
  • PyTorch 中.backward() 详解使用
  • 前后端国密加密传输用户密码流程
  • Unity 解决天空盒中间出现一条线
  • flink 伪代码
  • 高效管理网络段和端口集合的工具之ipset
  • Bug排查日记:高效记录与解决之道
  • 高通AR1平台Recovery架构分析与自动恢复出厂设置实现
  • 从 elecworks 到云端协同:SOLIDWORKS Electrical 发展历史 + 核心功能 + 采购指南
  • Linux 磁盘扩容及分区相关操作实践
  • 从Java全栈到云原生:一场技术深度对话
  • Golang语言设计理念