一、前言
上一节我们实现了2个在任务中主动调用,实现任务切换的函数接口,其具有一下几个功能:
-
从一个循环的任务切换到另一个循环的任务
-
切换回来之后,从切换之前被打断的位置继续执行
-
这是函数调用做不到的,函数调用只能从函数开始位置执行
但是一个操作系统,大部分的调用场景会发生在定时器中断处理中,在中断环境下,我们还需要定义并实现中断环境的任务切换。
中断环境除了要保存任务的状态以外,中断本身也需要保存一些现场状态,因为中断对正常执行的程序来说,可能会发生在任意一个指令之间,所以为了退出中断之后程序还可以正确运行,需要在进出中断的过程中进行保护现场与恢复现场
二、中断环境下任务切换的流程
- 保存寄存器到当前任务的堆栈
- 调用中断处理函数
- 设置中断切换标志位、设置全局from任务信息、设置全局to任务信息
- 完成中断处理,准备退出中断
- 判断是否需要在退出中断时进行任务切换
- 如果不需要,从当前任务栈恢复寄存器,返回继续执行
- 如果需要进行任务切换
- 把任务栈中保存的寄存器信息恢复
- 把from任务的寄存器数据保存到from任务的堆栈已经任务结构体
- 把to任务堆栈中的寄存器状态恢复到当前寄存器中
- 退出异常,进入to任务
三、代码展示
aarch64 中断处理函数,在退出中断时,会根据标志变量判断是否需要进行任务切换操作
IRQInterruptHandler:
//进入中断,保存寄存器
saveregister
savefloatregister
//执行中断处理函数
bl handle_domain_irq
//判断退出中断时候是否需要进行任务切换
adr x2, task_thread_switch_interrupt_flag
ldr x3, [x2]
cmp x3, #1
beq exit_irq_with_task_switch
//不需要任务切换,恢复寄存器之后退出中断
restorefloatregister
restoreregister
eret
//如果标志置位,需要在退出中断的时候进行任务切换
exit_irq_with_task_switch:
//将中断切换标志位清零
adr x2, task_thread_switch_interrupt_flag
mov x3, #0
str x3, [x2]
//恢复寄存器,恢复到进入中断之前的状态
restorefloatregister
restoreregister
//此时的状态是进入中断之前的状态,也就是from任务的状态
//开始进行任务切换
//保存from任务的状态到堆栈
//1 保存from任务通用寄存器状态
sub sp, sp, #30 * 8
stp x0, x1, [sp, #16 * 0]
stp x2, x3, [sp, #16 * 1]
stp x4, x5, [sp, #16 * 2]
stp x6, x7, [sp, #16 * 3]
stp x8, x9, [sp, #16 * 4]
stp x10, x11, [sp, #16 * 5]
stp x12, x13, [sp, #16 * 6]
stp x14, x15, [sp, #16 * 7]
stp x16, x17, [sp, #16 * 8]
stp x18, x19, [sp, #16 * 9]
stp x20, x21, [sp, #16 * 10]
stp x22, x23, [sp, #16 * 11]
stp x24, x25, [sp, #16 * 12]
stp x26, x27, [sp, #16 * 13]
stp x28, x29, [sp, #16 * 14]
str x30, [sp, #16 * 15]
//将from任务和to任务的任务信息恢复到x0、x1寄存器
adr x2, task_interrupt_from_thread
ldr x0, [x2]
adr x2, task_interrupt_to_thread
ldr x1, [x2]
//2 保存from任务sp状态
//获取当前任务sp状态
//获取任务结构体中变量地址
//保存sp地址到任务结构体中对应位置
mov x3, sp
mov x2, x0
add x2, x2, #8 * 0
str x3, [x2]
//3 保存from任务cpcr状态
//获取当前任务spsr状态
//获取任务结构体中spsr变量位置
//保存spsr到任务结构体中对应位置
mrs x3, spsr_el1
mov x2, x0
add x2, x2, #8 * 1
str x3, [x2]
//4 保存from任务的返回地址
//获取当前任务返回地址
//获取任务结构体中变量地址
//保存返回地址到任务结构体中对应位置
mrs x3, elr_el1
mov x2, x0
add x2, x2, #8 * 2
str x3, [x2]
//开始从to任务堆栈恢复to任务
//1 恢复to任务sp寄存器
mov x2, x1
add x2, x2, #8 * 0
ldr x3, [x2]
mov sp, x3
//2 恢复to任务spsr状态寄存器
mov x2, x1
add x2, x2, #8 * 1
ldr x3, [x2]
msr spsr_el1, x3
//3 恢复to任务异常返回地址
mov x2, x1
add x2, x2, #8 * 2
ldr x3, [x2]
msr elr_el1, x3
//4 恢复to任务通用寄存器
ldp x0, x1, [sp, #16 * 0]
ldp x2, x3, [sp, #16 * 1]
ldp x4, x5, [sp, #16 * 2]
ldp x6, x7, [sp, #16 * 3]
ldp x8, x9, [sp, #16 * 4]
ldp x10, x11, [sp, #16 * 5]
ldp x12, x13, [sp, #16 * 6]
ldp x14, x15, [sp, #16 * 7]
ldp x16, x17, [sp, #16 * 8]
ldp x18, x19, [sp, #16 * 9]
ldp x20, x21, [sp, #16 * 10]
ldp x22, x23, [sp, #16 * 11]
ldp x24, x25, [sp, #16 * 12]
ldp x26, x27, [sp, #16 * 13]
ldp x28, x29, [sp, #16 * 14]
ldr x30, [sp, #16 * 15]
//数据从堆栈取出之后,堆栈指针移动
add sp, sp, #30 * 8
//当前处理器状态已经切换到了to任务,异常返回之后到to任务执行
eret
设置任务切换标志变量
.global task_interrupt_from_thread;
.global task_interrupt_to_thread;
.global task_thread_switch_interrupt_flag;
.global interrupt_task_switch_from_to
.type interrupt_task_switch_from_to, "function"
// x0: from
// x1: to
//设置切换标志为1,通过全局变量传递from和to任务信息
interrupt_task_switch_from_to:
adr x2, task_thread_switch_interrupt_flag
mov x3, #1
str x3, [x2]
adr x2, task_interrupt_from_thread
mov x3, x0
str x3, [x2]
adr x2, task_interrupt_to_thread
mov x3, x1
str x3, [x2]
ret
任务调度代码
void timer_handler(struct irq_desc *desc)
{
arch_timer_compare(arch_timer_frequecy());
log_i("arch timer_handler!");
if((count++) % 2 == 0)
{
log_i("switch to taskb!");
interrupt_task_switch_from_to(&taska, &taskb);
}
else
{
log_i("switch to taska!");
interrupt_task_switch_from_to(&taskb, &taska);
}
}
运行结果
qemu-system-aarch64 -machine virt,gic-version=3 -cpu cortex-a57 -smp 1 -m 1024 -nographic -serial mon:stdio -kernel app
run in main!
[ 0.002736] EasyLogger V2.2.99 is initialize success.
i am taska run! line: 20
i am taska run! line: 22
i am taska run! line: 20
i am taska run! line: 22
i am taska run! line: 20
i am taska run! line: 22
[ 1.007194] irq_enter
[ 1.007440] handle_domain_irq: [30]
[ 1.007831] arch timer_handler!
[ 1.008077] switch to taskb!
[ 1.008260] irq_exit
i am taskb run! line: 38
i am taskb run! line: 40
i am taskb run! line: 38
i am taskb run! line: 40
[ 2.008983] irq_enter
[ 2.009176] handle_domain_irq: [30]
[ 2.009412] arch timer_handler!
[ 2.009574] switch to taska!
[ 2.009716] irq_exit
i am taska run! line: 20
i am taska run! line: 22
i am taska run! line: 20
i am taska run! line: 22
i am taska run! line: 20
i am taska run! line: 22
四、操作系统的任务切换
在rtos系统中,会根据一些策略去选择一个任务去执行。
当前我们已经实现了在任务环境与中断环境下的任务切换功能。
接下来通过添加一些策略来选择合适的任务执行,就可以实现一个简单的根据优先级以及时间片去进行调度的小系统。
可以添加和丰富更多任务相关的资源以及功能。