Raspberry Pi OS 项目解析:深入理解任务调度器实现
概述
在嵌入式系统开发中,任务调度是操作系统的核心功能之一。本文将深入分析Raspberry Pi OS中任务调度器的实现原理,帮助读者理解如何在裸机环境下构建一个简单的任务调度系统。
任务控制块(task_struct)
任何现代操作系统都需要一个数据结构来描述和管理任务(进程/线程)。Raspberry Pi OS借鉴了Linux的设计,使用task_struct
结构体来保存任务信息:
struct cpu_context {
unsigned long x19;
unsigned long x20;
// ... 其他寄存器
unsigned long fp; // 帧指针(x29)
unsigned long sp; // 栈指针
unsigned long pc; // 程序计数器(x30)
};
struct task_struct {
struct cpu_context cpu_context;
long state;
long counter;
long priority;
long preempt_count;
};
关键字段解析
-
cpu_context:保存任务执行上下文,仅包含x19-x30寄存器。根据ARM调用约定,x0-x18寄存器由被调用方负责保存,因此无需在上下文切换时保存。
-
state:任务状态,当前仅实现TASK_RUNNING状态。
-
counter:任务剩余时间片,每次时钟中断减1。
-
priority:任务优先级,决定时间片大小。
-
preempt_count:禁止抢占计数器,非零时表示任务正在执行关键代码。
内存管理
任务调度需要为每个任务分配独立的栈空间。RPi OS实现了一个简单的页式内存分配器:
unsigned long get_free_page() {
for (int i = 0; i < PAGING_PAGES; i++){
if (mem_map[i] == 0){
mem_map[i] = 1;
return LOW_MEMORY + i*PAGE_SIZE;
}
}
return 0;
}
这个分配器具有以下特点:
- 管理1GB-1MB的物理内存(最后1MB保留给设备)
- 前4MB保留给内核和初始任务
- 使用位图(mem_map)跟踪页面分配状态
任务创建
创建新任务的核心函数是copy_process
:
int copy_process(unsigned long fn, unsigned long arg) {
// 分配task_struct和栈空间
struct task_struct *p = (struct task_struct *)get_free_page();
// 初始化任务属性
p->priority = current->priority;
p->state = TASK_RUNNING;
p->counter = p->priority;
p->preempt_count = 1; // 初始禁止抢占
// 设置执行上下文
p->cpu_context.x19 = fn; // 要执行的函数
p->cpu_context.x20 = arg; // 函数参数
p->cpu_context.pc = (unsigned long)ret_from_fork;
p->cpu_context.sp = (unsigned long)p + THREAD_SIZE;
// 加入任务数组
task[nr_tasks++] = p;
return 0;
}
关键点:
- 新任务的栈空间与task_struct共享同一内存页
- pc寄存器指向
ret_from_fork
,这是任务的入口点 - 初始状态下禁止抢占,直到完成初始化
调度算法
RPi OS采用了Linux 0.01版本的调度算法,主要逻辑在_schedule
函数中:
void _schedule(void) {
while (1) {
// 第一轮:寻找counter最大的就绪任务
for (int i = 0; i < NR_TASKS; i++) {
if (task[i] && task[i]->state == TASK_RUNNING &&
task[i]->counter > c) {
c = task[i]->counter;
next = i;
}
}
if (c) break; // 找到合适任务
// 第二轮:重新分配时间片
for (int i = 0; i < NR_TASKS; i++) {
if (task[i]) {
task[i]->counter = (task[i]->counter >> 1) + task[i]->priority;
}
}
}
switch_to(task[next]);
}
算法特点:
- 优先选择counter值最大的就绪任务
- 若无合适任务,则重新计算所有任务的时间片
- 时间片计算公式保证:
- 任务不会饿死
- 时间片上限为2*priority
上下文切换
实际的任务切换由switch_to
函数完成,它最终调用汇编函数cpu_switch_to
:
.globl cpu_switch_to
cpu_switch_to:
mov x10, #THREAD_CPU_CONTEXT
add x8, x0, x10
mov x9, sp
stp x19, x20, [x8], #16
// 保存其他寄存器...
str x9, [x8]
add x8, x1, x10
ldp x19, x20, [x8], #16
// 恢复其他寄存器...
ldr x9, [x8]
mov sp, x9
ret
关键点:
- 仅保存/恢复被调用方保存的寄存器(x19-x30)
- 切换栈指针(sp)实现任务栈切换
- 通过ret指令跳转到新任务的pc寄存器值
调度触发机制
调度可能在两种情况下触发:
- 主动调度:任务调用
schedule()
自愿放弃CPU - 被动调度:时钟中断处理程序调用
timer_tick()
void timer_tick() {
--current->counter;
if (current->counter>0 || current->preempt_count >0) {
return;
}
current->counter=0;
enable_irq(); // 调度期间允许中断
_schedule();
disable_irq();
}
注意调度期间会临时启用中断,这是为了让等待中断的任务有机会被唤醒。
总结
Raspberry Pi OS实现了一个简单但完整的任务调度系统,具有以下特点:
- 支持多任务时间片轮转
- 采用优先级动态调整算法
- 实现基本的任务上下文保存/恢复
- 提供内存管理支持任务隔离
这个实现虽然简单,但包含了现代操作系统调度器的核心思想。读者可以通过扩展状态管理、增加调度策略等方式进一步完善这个系统。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考