热度 14
2014-12-9 16:27
714 次阅读|
2 个评论
扯扯任务切换 最近在玩 STM32,32 位的单片机,寄存器太多了,直接面对寄存器不太适合我这种初学者,也由于实验室的一些原因,所以直接从 OS 开始学习,这次学习的系统叫 msOS, 可以说他是从 ucOS 的精简出来,只有两个任务,逻辑任务( logic )和界面任务( menu ) , 当然少不了消息机制等,麻雀虽小,五脏俱全。 下面我来说说在这个系统的学习过程中我对任务切换的了解。在开始之前我们首先来科普下: 1. 栈 栈是一种先进后出的数据结构。一般栈是由高地址向低地址生长的,这也就是为什么经常会有人说往栈里加入数据时,栈顶下降,而从栈里取出数据时栈顶上升。 2. 中断 基于 cortex 内核的单片机发生中断时,会有一个保护现场的动作,就是自动 把一些数据压入栈中,其中首先入栈的是状态寄存器( XPSR )中的数据,接着是 PC ,然后是 LR 、 R12 、 R3 、 R2 、 R1 、 R0 ,这八个寄存器中的数据压栈完毕之后,栈顶地址由 MSP/PSP 保存,那么退出中断后从 MSP/PSP 里得到的栈顶地址就可以把数据重新加载回相应的寄存器中,其中就包含返回断点的断点地址,因为这一步涉及 PC 指针的赋值,所以一般任务切换都是从这里着手。( PS:XPSR 是状态寄存器; PC 寄存器保存的是中断退出时的返回地址; R3-R0 在父函数调用子函数时用于函数传递,所以中断的时候会把它们里面的数据也一起压栈) 我们都知道单片机里面程序准确的说是指令存储在程序存储空间( flash )里面,而单片机从哪取出指令是由 PC 指针的指向决定的,可以这么理解 PC 指向哪程序就运行到哪,那么如果我们想要从 A 任务切换到 B 任务的话,只要把原本指向 A 任务的 PC 指向 B 就可以了,当然在切换的时候要保存当前 A 任务执行时产生的一些数据,而从 B 任务切换回 A 任务也是同样的道理,这样的话我们就需要用到两个栈来分别保存 A 任务和 B 任务运行时产生的数据。那么好,基于以上的简单了解,下面我们来看看该怎么样实现任务切换。 首先,我们要创建两个栈分别用于保存 A 任务和 B 任务运行时产生的数据,并进行如下初始化(为了不绕,现用 a 表示任务 A 中的某一条指令的地址, b 表示任务 B 中的某一条指令的地址 , 也就是当把 a 赋值给 PC 时, PC 指向 A 任务,准确的说应该是单片机执行 A 任务, b 同理。): 这两个栈各有 64 个字节的空间,其中栈底往下 32 个字节用于保存中断发生时单片机自动保存数据,后 32 个字节主要是考虑到当程序运行产生的数据过多时,用于保存 R4-R11 这八个寄存器中的数据(这部分数据是手动保存的)。 当系统开始运行的时候,会一直在优先级最低的任务中循环,每当节拍到来的时候进入节拍函数程序段执行,只有在需要的时候才会切换任务。那么现在我们假设任务 A 的优先级最低,假如在执行 A 任务的时候突然进来一个消息,需要执行 B 任务进行处理,单片机会触发中断,然后 cpu 会自动把当前的一些数据压入栈中, cortex 内核的单片机第一次进入中断时默认把数据压入 MSP, 接着执行中断服务程序,在中断服务中我们可以这样处理,首先把 B 任务栈中自栈顶开始往上取出 32 个字节的数据加载到 R4-R11 这八个寄存器中,再让栈顶上升 8 个字,然后把栈顶地址赋值给 PSP, 接着激活 PendSV 中断然后退出。中断退出后我们的单片机会自动加载一些数据到 cpu 里面,因为我们退出中断的时候激活了 PendSV ,所以单片机会根据 PSP 中的地址开始往上取出 32 个字节的数据加载到相应的寄存器中,其中就包括把 b 加载到 PC 寄存器里面,这样 PC 就指向了任务 B ,单片机开始执行 B 任务。这就完成了第一次切换,我们再来看后续的切换。 因为 A 任务的优先级最低,所以执行完 B 任务之后会切换回 A 任务。一样的,单片机先触发中断,不过这次是把数据压入 PSP 指向的存储空间也就是 B 任务的栈中,接着执行中断服务程序,在中断服务程序里面,我们需要手动的把 R4-R11 这八个寄存器里的数据保存到 B 任务的栈里面 , 然后栈顶下降 8 个字,接着切换栈顶,切换栈顶是从 B 任务栈切换到 A 任务栈,切换到 A 任务栈后先把栈顶开始往上取 32 个字节的数据加载到 R4-R11 这八个寄存器里面,然后栈顶上升 32 个字节,再把栈顶地址赋值给 PSP 后退出中断,接下来单片机会根据 PSP 中指向的地址开始往上取出 32 个字节的数据加载到相应的寄存器里面,其中就包括把 a 加载到 PC 寄存器,这样子 PC 就指向了 A 任务,单片机开始执行 A 任务 ...... 下面再上个图,这样可以比较形象点 上图看不太清楚的文字是: 1. 触发中断时单片机自动加载 XPSR 、 PC 、 R12 、 R3-R0 中的数据到栈里面 2. 进入中断服务函数后手动加载 R4-R11 这八个寄存器的数据到栈里面 3. 切换栈之后,把新栈里面的数据从栈顶开始往上取 32 个字节的数据加载到 R4-R11 这八个寄存器中 4. 退出中断后单片机自动把栈里面剩余的数据加载到 XPSR 、 PC 、 R12 、 R3-R0 这八个寄存器中 1 、 4 步骤都是单片机自动完成的, 2 、 3 步时我们手动完成的,这样就完成了任务切换 总的来说,任务切换其实就是人为的改变 PC 的指向,但是因为单片机不允许我们随意的去更改 PC 的值,所以我们就需要用触发中断的方式更改 PC 指针,又任务切换的时候需要保存数据和往 CPU 中加载新的数据,而这个保存和加载数据的过程分自动保存 / 加载和手动保存 / 加载