/***********************************
*作者:蔡军生
*出处:http://blog.youkuaiyun.com/caimouse/
************************************/
学习ARM开发(20)
OS的任务切换
有了前面的Tick中断,那么基本的任务切换条件已经是“万事俱备,只欠东风”了。不过,这个“东风”也是很难搞得懂的,只有不断地通过实践才会找到合适的方法。现在我就需要去找这个东风了,就是解决不同的任务切换的问题。从简单到复杂,这是任何事物的认识过程,也是行之有效的方法。绝对不要一上来就搞一个很复杂的,因为人的理解能力还是有限的。最简单的任务切换,就是我需要实现的:只需要实现两个任务不断地来回切换,就已经说明可行了。那我先把这两个任务设置为最简单的,因此,就把任务的栈定下来,因为每个任务的栈是肯定不同的,所以我选择了固定地设置栈地址。比如第一个任务的栈地址是0x0c700000,第二个任务的栈地址是0x0c700200。接着就需要把任务这两个栈初始化成中断返回的方式,就是需要保存R0到R12,LR,PC,CPSR等寄存器的值。
这时就需要理解ARM的几种工作模式了,目前我使用到的只有两种模式:IRQ和SVC模式。在这两种模式下,它的寄存器是有一些不同的。就是SP,LR,SPCR的寄存器不同样,并且LR与SPCR在两种模式中是有关联的。当从SVC模式转换到IRQ模式时,它的LR,就是在SVC下的执行的下一条指令地址减4;SPSR就是SVC模式下的CPSR寄存器的值。因此,在IRQ中断之后,一定要想办法把这两个寄存器保存下来,否则就返回不到先前的任务了。由于SVC与IRQ的SP是不一样的,并且在SVC下的LR也没有办法保存,那么就一定要切换回到SVC模式下,才能访问这个任务的栈了,并且那时才能保存LR的值。因此,特殊的要求就在这里了。
由于我的OS是采用时间片轮转算法,那么当时间片到时,TICK中断就会中断任务的运行,并且要切换到新的任务运行。具体过程是这样的:启动时第一个任务运行,当时间片使用完了,那么TICK中断就发生,接着就保存IRQ模式下的LR,SPCR寄存器到内存某个位置,并且把R0到R12的所有寄存器恢复,接着切换回到SVC模式。接着保存一个寄存器的值,然后用这个寄存器从内存读回来在IRQ方式保存的LR值。然后把这个LR值压入栈,这个LR值就是任务再回来时要运行的PC值。因此把它放到最先栈里,到时出栈才方便。接着保存SVC模式下的LR值,保存R0到R12的值,然后保存SPCR值。到这里,就把所有任务恢复时所需要的寄存器保存了。
到后面,接着再写一段可以恢复任务的汇编程序就可以了,这个就是上面的压栈反向过程。通过这样的方法,就可以不断来回地切换任务了。
学习ARM开发(21)
OS任务切换源程序分析
先要声明任务指针,因为后面需要使用。
//任务指针.
volatile TASK_TCB* volatile g_pCurrentTask = NULL;
volatile TASK_TCB* volatile g_pCurrentTask1 = NULL;
volatile TASK_TCB* volatile g_pCurrentTask2 = NULL;
接着就需要初始化这些任务栈,用下面的代码进行初始化,为了简单,全部使用内存地址操作的方式,当然后面会改成动态地分配内存的方式。代码如下:
///////////////////////////////////////////////////////////////////////////////
//函数名称: TaskInitStack
//函数功能: 分配任务的栈。
//输入参数:
//输出参数:
//返 回 值:
//开发人员: 蔡军生
//时 间: 2006/02/26
//修改说明:
//
///////////////////////////////////////////////////////////////////////////////
void TaskInitStack(void)
{
g_pCurrentTask1 = (PTASK_TCB)0x0c700000;
g_pCurrentTask1->pStackStart = (UINT*)(0x0c700000+0x200);
g_pCurrentTask1->pStackTop = g_pCurrentTask1->pStackStart + 0x100;
<!--[if !supportEmptyParas]--> <!--[endif]-->
g_pCurrentTask2 = (PTASK_TCB)(0x0c700000 + 0x400);
g_pCurrentTask2->pStackStart = (UINT*)(0x0c700000+0x400 + 0x200);
g_pCurrentTask2->pStackTop = g_pCurrentTask2->pStackStart + 0x100;
}
<!--[if !supportEmptyParas]--> <!--[endif]-->
接着再创建两个简单的任务,它们都是输出一行字符串,就等待一会,代码如下:
//
void TaskTest1(void)
{
for(;;)
{
Lock();
puts("TaskTest1/n");
UnLock();
<!--[if !supportEmptyParas]--> <!--[endif]-->
SoftDelay(100);
}
}
//
void TaskTest2(void)
{
for(;;)
{
Lock();
puts("TaskTest2/n");
UnLock();
<!--[if !supportEmptyParas]--> <!--[endif]-->
SoftDelay(100);
}
}
<!--[if !supportEmptyParas]--> <!--[endif]-->
然后再初始化任务栈,代码如下:
void TaskStart(void)
{
//
UINT* pTemp = g_pCurrentTask1->pStackTop;
//
*g_pCurrentTask1->pStackTop = (UINT)TaskTest1;
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x14141414; /* R14 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
// *g_pCurrentTask1->pStackTop = (UINT)pTemp; /* R13 */
// g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x12121212; /* R12 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x11111111; /* R11 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x10101010; /* R10 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x09090909; /* R9 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x08080808; /* R8 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x07070707; /* R7 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x06060606; /* R6 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x05050505; /* R5 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x04040404; /* R4 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x03030303; /* R3 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x02020202; /* R2 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x01010101; /* R1 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0; /* R0 */
g_pCurrentTask1->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask1->pStackTop = (UINT)0x13; /* SPSR */
<!--[if !supportEmptyParas]--> <!--[endif]-->
<!--[if !supportEmptyParas]--> <!--[endif]-->
//
//
//
pTemp = g_pCurrentTask2->pStackTop;
//
*g_pCurrentTask2->pStackTop = (UINT)TaskTest2;
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x14141414; /* R14 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
// *g_pCurrentTask2->pStackTop = (UINT)pTemp; /* R13 */
// g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x12121212; /* R12 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x11111111; /* R11 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x10101010; /* R10 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x09090909; /* R9 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x08080808; /* R8 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x07070707; /* R7 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x06060606; /* R6 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x05050505; /* R5 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x04040404; /* R4 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x03030303; /* R3 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x02020202; /* R2 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x01010101; /* R1 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0; /* R0 */
g_pCurrentTask2->pStackTop--;
<!--[if !supportEmptyParas]--> <!--[endif]-->
*g_pCurrentTask2->pStackTop = (UINT)0x13; /* SPSR */
//设置首先运行的任务是1.
g_pCurrentTask = g_pCurrentTask1;
}
代码初始化了两个任务栈后,接着设置第一个任务为优先运行的任务。
<!--[if !supportEmptyParas]--> <!--[endif]-->
最后就需要进行中断任务调度任务进行运行了,代码如下:
///////////////////////////////////////////////////////////////////////////////
//函数名称: EIntTickIsr
//函数功能: 时钟中断函数。
//输入参数:
//输出参数:
//返 回 值:
//开发人员: 蔡军生
//时 间: 2006/02/26
//修改说明:
//
///////////////////////////////////////////////////////////////////////////////
void EIntTickIsr(void) __attribute__((naked));
void EIntTickIsr(void)
{
//保存R0-R12寄存器到栈里.
asm volatile(" STMDB SP!, {R0-R12} ");
//关闭TICK中断.
INTMSK |= BIT_GLOBAL|BIT_TICK;
<!--[if !supportEmptyParas]--> <!--[endif]-->
//取得保存IRQ的LR地址,并保存LR.
asm volatile (" LDR R0,=g_dwIRQLR" );
asm volatile (" SUBS LR,LR,#4 ");
asm volatile (" STR LR,[R0] ");
<!--[if !supportEmptyParas]--> <!--[endif]-->
//取回前面保存的R0-R12寄存的值.
asm volatile (" LDMIA SP!, {R0-R12} ");
<!--[if !supportEmptyParas]--> <!--[endif]-->
//取得最后返回的SPSR,就是SVC模式下的CPSR.
//从IRQ模式切换回到SVC模式.
asm volatile (" MRS LR,SPSR ");
asm volatile (" ORR LR,#0xc0 ");
asm volatile (" MSR CPSR,LR ");
<!--[if !supportEmptyParas]--> <!--[endif]-->
//现在已经转换回到SVC模式,取回IRQ模式下保存的LR.
//把R0的值保存到栈中第二个位置.
asm volatile (" STR R0,[SP,#-8] ");
<!--[if !supportEmptyParas]--> <!--[endif]-->
//从保存位置取回IRQ模式下保存的LR值.
asm volatile (" LDR R0,=g_dwIRQLR" );
asm volatile (" LDR R0,[R0] ");
<!--[if !supportEmptyParas]--> <!--[endif]-->
//把IRQ模式下的LR保存到栈里,就是出栈时的PC值.
asm volatile (" STMDB SP!,{R0} ");
<!--[if !supportEmptyParas]--> <!--[endif]-->
//从栈里第二个位置取回先前保存的R0值.
asm volatile (" SUBS SP,SP,#4 ");
asm volatile (" LDMIA SP!,{R0} ");
<!--[if !supportEmptyParas]--> <!--[endif]-->
//保存R0-R12,LR到栈里,入栈顺序是刚好相反的.
asm volatile (" STMDB SP!,{R0-R12,LR} ");
<!--[if !supportEmptyParas]--> <!--[endif]-->
//保存CPSR的值到栈里,就是SPSR的位置.
asm volatile (" MRS R4,CPSR ");
asm volatile (" BIC R4,#0xc0 ");
asm volatile (" STMDB SP!,{R4} ");
<!--[if !supportEmptyParas]--> <!--[endif]-->
//保存栈顶到任务指针里.
asm volatile ( "LDR R0, %0" : : "m" (g_pCurrentTask) );
asm volatile ( "STR SP, [R0]" );
<!--[if !supportEmptyParas]--> <!--[endif]-->
//增加时钟计数.
g_dwTickCount++;
//
printf("g_dwTickCount = (%d)/n",g_dwTickCount);
//
//清除屏蔽位.
I_ISPC = BIT_TICK;
INTMSK &= ~(BIT_GLOBAL|BIT_TICK);
<!--[if !supportEmptyParas]--> <!--[endif]-->
//
if (g_dwTickCount < 3)
{
g_pCurrentTask = g_pCurrentTask1;
}
else if (g_dwTickCount < 4)
{
g_pCurrentTask = g_pCurrentTask2;
}
else
{
g_dwTickCount = 0;
}
<!--[if !supportEmptyParas]--> <!--[endif]-->
//取得新任务指针
asm volatile ( "LDR R0, %0" : : "m" (g_pCurrentTask) );
asm volatile ( "LDR SP, [R0]" );
<!--[if !supportEmptyParas]--> <!--[endif]-->
//取回SPSR值.
asm volatile ( "LDMIA SP!, {R0}" );
asm volatile ( "MSR SPSR, R0" );
<!--[if !supportEmptyParas]--> <!--[endif]-->
//取回R0-R12,LR,PC值,并运行最后的任务.
asm volatile ( "LDMIA SP!, {R0-R12,LR,PC}^" );
}
<!--[if !supportEmptyParas]--> <!--[endif]-->
写完上面的代码,就可以真正地实现了任务调度了。有了以上的代码,写其它复杂的任务调试都变得很容易了,这些代码得来是不太容易的,我经历了好几个星期的调试才通过的。
这些都是在我的S3C44B0开发板上调试通过的,如果你没有开发板,可以跟我购买。联系方法ccaimouse#gmail.com(请把#换成@)。