美文网首页
RTOS学习笔记

RTOS学习笔记

作者: 我是你一博哥哥 | 来源:发表于2017-07-25 23:01 被阅读0次

    最近在学习实时操作系统(RTOS),故将所学知识罗列出来,以供日后参考。

    1.任务的本质

    任务-说白点,即是一个永远不会返回的函数。

    2.任务切换的本质

    即是保存当前运行任务的状态,然后再恢复出下一个要运行的任务的状态。任务状态包括任务自己的栈,堆,数据区,代码区,内核寄存器的值。数据区和代码区由编译器自动分配,故不需要关心。堆目前并未使用,故也不需关心,那么唯一要关心的就是栈和内核寄存器了。而每一个任务我们都会分配给它一个栈空间用于保存自己的状态。

    3.任务切换的实现

    cortex-m3是通过触发pensv中断来进行任务切换的。故我们可以通过设置NVIC相关寄存器的值触发pendsv中断来进行任务切换。

    那么问题来了,第一,我们如何切换进第一个要运行的任务?第二,如何实现两个任务之间的切换?

    答案很简单,像是通过设置标志位一样,我们在初始化任务完成后,通过设置psp堆栈指针为0代表着这是第一次进入任务切换的中断函数(pendsv_handler),直接恢复该任务的堆栈值到相关寄存器中,等跳出中断后即进入了第一个要运行的任务;当再次进入任务切换的中断函数时,此时psp不为0,故应知当前进入中断是想要切换任务,则把当前任务的运行状态保存起来,然后再恢复下一个要运行的任务的状态即可。

    具体实现代码如下:

    __asm void PendSV_Handler ()
    {
    IMPORT  currentTask              // 使用import导入C文件中声明的全局变量
    IMPORT  nextTask                 
    
    MRS    R0, PSP                  // 获取当前任务的堆栈指针
    CBZ    R0, PendSVHandler_nosave  // 判断psp是否为0,如果为0,则跳转。
    STMDB  R0!, {R4-R11}            // 如果不为0,则先保存当前任务状态
    LDR    R1, =currentTask         
    LDR    R1, [R1]             
    STR    R0, [R1]                // 重置任务栈的栈顶   
    
    PendSVHandler_nosave  
    LDR    R0, =currentTask
    LDR    R1, =nextTask
    LDR    R1, [R1]                
    STR    R1, [R0]                  // 交换指针值
    
    LDR    R0, [R1]                  //加载该任务的栈顶指针,用于恢复出任务状态 
    LDMIA  R0!, {R4-R11}       // 恢复{R4, R11},其余硬件自动恢复
    
    MSR    PSP, R0                  // 最后,恢复真正的堆栈指针到PSP
    ORR    LR, LR, #0x04            // 切换到PSP,使用用户级堆栈
    BX     LR                        // 恢复到上次运行停止的位置
    }
    
    

    4 双任务时间片运行原理

    可以通过定时器定时触发pendsv中断,实现任务的切换。对于cotex-m3芯片,一般使用内核定时器systick_handler

    5 任务的延时原理与空闲任务

    由于硬件定时器资源有限,而任务的数量可能很多,所以一般无法给每一个任务都配置一个硬件定时器。所以一般使用软件定时器,即在任务的结构中添加一个变量,表示该任务当前需要延时的周期数。代码如下:

    typedef struct _task
     {
        uint32_t *stack;   // 指向任务栈的栈顶指针
        uint32_t delayTicks;   // 任务延时计数器
    }Task_t;
    
    
    

    但是这种软延时的方法有一个明显的缺陷,便是延时精度可能并不是特别准确,具体情况见下图:

    另外,当所有任务都处于延时状态时,CPU应该做什么呢?正确的做法是提供一个空闲任务。

    6 临界区保护

    临界区指的是一个访问共享资源的程序片段;言而简之,之所以设置临界区保护,是为了防止读-改-写过程被打断,从而导致写回变量时,覆盖了打断过程对变量的改写。具体的实现方法便是关中断,其一,中断关闭后,任务不可能被中断打断,导致共享变量的改写被覆盖;其二,关闭中断后,即关闭了任务切换,所以即便是多任务共享的资源,也只能由当前任务来访问了。
    唯一的问题在于当有多层嵌套临界区时,第二层的临界区的开中断操作会使前一层的临界区保护失败,故正确的解决办法是:每次关闭中断前,先保存当前中断的开关状态,退出临界区时,再恢复进入临界区时的中断状态,即可完美解决。
    具体实现代码如下:

    uint32_t tTaskEnterCritical (void) 
    {
        uint32_t primask = __get_PRIMASK();
        __disable_irq();        // CPSID I
        return primask;
    }
    
    void tTaskExitCritical (uint32_t status)
     {
        __set_PRIMASK(status);
    }
    

    7调度锁保护

    设置一个变量实现调度保护,当该值大于0时,表示有任务请求禁止调度。

    8位图

    位图是一组连续的标志位,每一位用于标识一种状态的有无。

    9多优先级任务

    RTOS维护一个就绪表,每个表项 对应一个任务,对应一种优先级,就绪表指明哪些优先级的任务等待占用CPU运行,说白了其实就是通过将相应优先级对应的位图位置1来实现。

    10 任务的延时队列

    将所有需要延时的任务放入延时队列中,当发生定时中断时,去扫描该延时队列,依次对队列中各任务的延时时间-1.如果有减到0的,将其从延时队列中移除.

    11 同优先级时间片运行

    将原先位图一个位对应一个优先级的任务改为对应一个任务队列,并在每个任务中加入自己的时间片变量,用于实现同一优先级对应的多个任务按照时间片方式进行运行.具体实现如下图:

    12任务的挂起

    在任务的结构中增加一个挂起计数器,仅当计数器值大于0且该任务不处在延时状态时,才对任务进行挂起,如果该任务是当前正在运行的任务,则要对任务进行切换.挂起方式很简单,就是将其从就绪队列中移除.

    13任务的删除

    一,将任务从其所在延时队列,就绪队列中删除.
    二,释放该任务所占用的资源.
    设计一种机制,当发现任务要被删除时,释放掉该任务所占用的资源,包括内存空间,硬件设备等.
    删除方式1,设置任务删除回调函数,由该函数释放.
    删除方式2,设置删除请求标志,由任务自己决定何时删除.

    相关文章

      网友评论

          本文标题:RTOS学习笔记

          本文链接:https://www.haomeiwen.com/subject/ringkxtx.html