嵌入式工程师实现一个简单的操作系统(四)

一、前言

上一节我们实现了2个在任务中主动调用,实现任务切换的函数接口,其具有一下几个功能:

  1. 从一个循环的任务切换到另一个循环的任务

  2. 切换回来之后,从切换之前被打断的位置继续执行

  3. 这是函数调用做不到的,函数调用只能从函数开始位置执行

但是一个操作系统,大部分的调用场景会发生在定时器中断处理中,在中断环境下,我们还需要定义并实现中断环境的任务切换。

中断环境除了要保存任务的状态以外,中断本身也需要保存一些现场状态,因为中断对正常执行的程序来说,可能会发生在任意一个指令之间,所以为了退出中断之后程序还可以正确运行,需要在进出中断的过程中进行保护现场与恢复现场

二、中断环境下任务切换的流程

  1. 保存寄存器到当前任务的堆栈
  2. 调用中断处理函数
  3. 设置中断切换标志位、设置全局from任务信息、设置全局to任务信息
  4. 完成中断处理,准备退出中断
  5. 判断是否需要在退出中断时进行任务切换
  6. 如果不需要,从当前任务栈恢复寄存器,返回继续执行
  7. 如果需要进行任务切换
  8. 把任务栈中保存的寄存器信息恢复
  9. 把from任务的寄存器数据保存到from任务的堆栈已经任务结构体
  10. 把to任务堆栈中的寄存器状态恢复到当前寄存器中
  11. 退出异常,进入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系统中,会根据一些策略去选择一个任务去执行。

当前我们已经实现了在任务环境与中断环境下的任务切换功能。

接下来通过添加一些策略来选择合适的任务执行,就可以实现一个简单的根据优先级以及时间片去进行调度的小系统。

可以添加和丰富更多任务相关的资源以及功能。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值