操作系统实验:从零实现时间片轮转调度算法

📅 2026/7/13 13:04:04
操作系统实验:从零实现时间片轮转调度算法
1. 时间片轮转调度算法基础时间片轮转Round-Robin是操作系统课程中最经典的进程调度算法之一。它的核心思想就像餐厅叫号系统所有顾客进程按先来后到排队服务员CPU每次服务当前队首顾客固定时长时间片超时后顾客重新排队等待下次服务。我第一次在Linux系统上实现这个算法时发现它完美解决了长进程霸占CPU的问题。比如有一个计算圆周率到小数点后百万位的进程在FCFS算法中会阻塞其他所有进程而RR算法通过时间片划分让所有进程都能分到计算资源。关键参数设计要点时间片长度通常10-100ms太短会导致频繁上下文切换实测时间片1ms时系统开销增加40%太长会退化成FCFS就绪队列采用双向链表实现新进程从尾部加入调度时从头部取进程上下文保存需要保存PC、寄存器等现场信息实测x86架构下每次切换消耗约5μs2. 进程控制块(PCB)设计PCB是操作系统的身份证管理系统记录着每个进程的全部家当。在模拟器中我们这样设计结构体struct PCB { int pid; // 进程ID好比身份证号 int remaining; // 剩余运行时间单位时间片 int wait_time; // 累计等待时间用于性能分析 char state[10]; // 状态标识RUNNING/READY/FINISHED struct PCB *next; // 队列指针 };实际开发中的坑点状态字段建议用枚举而非字符串我最初用字符串比较时出现过READY 和READY不匹配的bug剩余时间建议用绝对时钟周期而非相对值避免累计误差调试时可以增加create_time字段方便追踪进程生命周期3. 核心调度逻辑实现调度器的核心就像个永不停歇的流水线工人其工作流程如下void scheduler() { while(1) { if(current_process ! NULL) { // 检查是否用完时间片 if(--current_process-remaining 0) { terminate_process(current_process); } else { move_to_tail(current_process); // 时间片用完重新排队 } } // 选取下一个进程 current_process get_next_process(); if(current_process NULL) break; // 所有进程完成 // 上下文切换模拟 printf(调度进程%d剩余时间%d\n, current_process-pid, current_process-remaining); } }性能优化技巧使用O(1)复杂度的队列操作避免O(n)遍历采用时间片到期中断机制而非忙等待添加饥饿检测当进程等待时间超过阈值时提升优先级4. 可视化调试技巧在开发初期我经常用ASCII艺术来观察调度过程时间片3 | 进程1 [RR] | 进程2 [---] | 进程3 [---] 时间片4 | 进程2 [RR] | 进程3 [---] | 进程1 [---]进阶版可以输出SVG时序图或者用以下方法实时监控# Python可视化示例 import matplotlib.pyplot as plt def draw_gantt(schedule): fig, ax plt.subplots() for i, (pid, start, end) in enumerate(schedule): ax.broken_barh([(start, end-start)], (i-0.4, 0.8), facecolorsblue) plt.show()5. 完整C语言实现以下是经过教学实践验证的代码框架#include stdio.h #include stdlib.h #define TIME_QUANTUM 4 // 时间片长度 typedef struct PCB { int pid; int burst_time; struct PCB *next; } PCB; PCB *ready_queue NULL; void enqueue(PCB *process) { if(ready_queue NULL) { ready_queue process; process-next process; // 循环队列 } else { process-next ready_queue-next; ready_queue-next process; ready_queue process; } } PCB *dequeue() { if(ready_queue NULL) return NULL; PCB *process ready_queue-next; if(process ready_queue) { ready_queue NULL; } else { ready_queue-next process-next; } return process; } void rr_scheduler() { int clock 0; while(ready_queue ! NULL) { PCB *current dequeue(); int execute_time (current-burst_time TIME_QUANTUM) ? TIME_QUANTUM : current-burst_time; printf(时间%d-%d: 运行进程%d\n, clock, clockexecute_time, current-pid); current-burst_time - execute_time; clock execute_time; if(current-burst_time 0) { enqueue(current); } else { free(current); } } } int main() { // 示例创建3个进程 for(int i1; i3; i) { PCB *p (PCB*)malloc(sizeof(PCB)); p-pid i; p-burst_time i * 3; // 进程1需要3单位进程2需要6... enqueue(p); } rr_scheduler(); return 0; }6. 进阶实验建议完成基础实现后可以尝试这些扩展动态时间片根据负载情况自动调整时间片长度// 根据就绪队列长度调整时间片 int adaptive_quantum() { int length queue_length(); return (length 5) ? 2 : TIME_QUANTUM; }多级反馈队列实现不同优先级的RR队列性能统计计算平均周转时间和带权周转时间平均周转时间 ∑(完成时间-到达时间)/进程数 带权周转时间 ∑(周转时间/服务时间)/进程数真实环境测试通过Linux的sched_setscheduler测试实际调度效果7. 常见问题排查问题1进程卡死无法结束检查队列操作是否正确移除已完成进程验证时间片递减逻辑添加调试打印问题2调度顺序不符合预期用3个进程耗时2/4/6测试基础案例检查enqueue/dequeue是否保持FIFO顺序问题3内存泄漏使用valgrind工具检测确保每个malloc都有对应的free记得在实验报告中包含以下验证案例测试用例1 进程A需要6个时间片 进程B需要4个时间片 进程C需要2个时间片 预期调度顺序A-B-C-A-B-A通过这个从零实现的实验你会真正理解操作系统的调度机制而不再只是死记硬背算法描述。我在内核开发中调试真实调度器时这些实验积累的经验派上了大用场。