UCOS-II任务与OSCtxSw详解

作者:GWD 时间:2019.12.16

一、 任务切换详解
假设实现TASK1->TASK2的切换

1.代码跟踪


int main(void)
	 OSTaskCreate( )
		OS_Sched( );
			OS_TASK_SW( ); 
			注意: OS_TASK_SW( );只是一个宏,代替了OSCtxSw() 
		        PendSV_Handler
				PendSV_Handler_Nosave

2、OSCtxSw()详解:

UCOS-II任务与OSCtxSw详解_第1张图片
向量中断控制器, 简称 NVIC,是 Cortex‐M3 不可分离的一部分, 它与 CM3 内核的逻辑紧密耦合, 有一部分甚至水乳交融在一起。查看M3内核指南可知,NVIC 的访问地址是 0xE000_E000。 所有 NVIC 的中断控制/状态寄存器都只能在特权级下访问。OSCtxSw()是用来触发PendSV异常的,通过设置0XE000ED22地址的寄存器的第28位设置进去中断处理函数。
在这里插入图片描述
UCOS-II任务与OSCtxSw详解_第2张图片
这四行代码实现的功能就是NVIC_PENDSVSET-> NVIC_INT_CTRL,之后进入PendSV中断

3、在中断向量表中找到PendSV中断函数

UCOS-II任务与OSCtxSw详解_第3张图片

4、PendSV_Handler函数分析(关键)

UCOS-II任务与OSCtxSw详解_第4张图片
注:每次出现异常之前,寄存器的值都自动保存在堆栈中了,这是硬件做的工作;
161:关闭中断,防止切换期间被打断;
162:将堆栈指针赋给R0,此时R0指向了任务堆栈的栈顶;
163:如果R0等于0,说明当前任务没有运行过,不需要保存断点之类的信息,直接跳转即可,否则继续向下运行。
170:保存4-11这8个寄存器因为每个寄存器4字节,所以总共32个字节;
171:保存4-11这8个寄存器的值到R0
173:将OSTCBCur(当前任务的控制块)地址给R1;
174:R1的地址是OSTCBCur,也就是将TASK1的任务控制块堆栈指针读出来给R1;
175:将R0这个栈顶的地址,存放到TASK1任务控制块的堆栈指针所指的位置,这样以后切换回来的时候就可以找到了。
以上实现了保存TASK1,接下来是取出TASK2的状态也就是切换下文。

5、PendSV_Handler_Nosave详解

注:OS_EXT INT8U OSPrioHighRdy;
/* Priority of highest priority task*/
UCOS-II任务与OSCtxSw详解_第5张图片
184:R0指向task1优先级存放的位置;
185:R1指向task2优先级存放的位置;
186:将task2的优先级取出来放到R2寄存器;
187:将R2存的优先级写入R0这四条实现功能是OSPrioHighRdy->OSPrioCur;
189:运行后R0和OSTCBCur一样,都指向正在运行的task1的任务控制块存放的位置,接下来的就是查到的最高优先级的任务控制块指针的值送给OSTCBCur,这样当前的运行任务就变了;
190:R1和OSTCBHighRdy一样指向TASK2的任务控制块存放的位置;
191:将Task2的任务控制块的地址取出来给R2;
192:将R2送给OSTCBCur指向的位置,因此现在OSTCBCur指向了Task2的任务控制块,这四条实际实现的是OSTCBCur->OSTCBHighRdy;
194:从R2地址取数据给R0,也就是将TASK2任务控制块的OSTCBStkPtr内容送给R0,因此R0指向了TASK2的栈顶;
195:从堆栈取出8个数据给R4-R11;
203:将R0的值送给PSP寄存器,完成切换;
204:确保异常返回后执行的是PSP指针;
250:开中断;

二、任务的相关函数

我们在进入systick异常时,有执行OSIntNesting++;准备出来时,自然需要OSIntNesting–;如果OSIntNesting不等于零,退出systick异常。 只有OSIntNesting等于零(无其他异常/中断发生)并且OSLockNesting等于零(无任务调度锁),才执行OS_SchedNew()查就任务绪表中最高优先级并返回,比较返回的优先级是否为当前运行任务的优先级, 仅不相等时,执行OSIntCtxSw()函数,生成pendsv异常。OSIntCtxSw()函数的实现见上边篇幅。pendsv异常OS_CPU_PendSVHandler,实现上下文的切换。
(1)任务可以是一个无限的循环,也可以在一次执行完毕后被删除。 这里需要注意的是,任务的代码并不是真正的删除了,而是UCOSII不再理会该任务代码,所以该任务代码不会再执行;
(2)建立任务,OSTaskCreate() 如果想让UCOSII管理用户的任务,必须先建立任务,可以通过将任务的地址(函数名)和其他参数传递到这2个函数中来建立任务;
(3)任务可以在多任务调度之前开始建立,也可以在其他的任务中创建需要的任务。但是有一点需要注意的是,在启动UCOS之前必须至少得建立一个任务。

1、分析创建任务函数

INT8U OSTaskCreate (void (*task)(void *p_arg),void *p_arg,
OS_STK *ptos,INT8U prio)
(1) 参数分析:
UCOS-II任务与OSCtxSw详解_第6张图片
参数1:任务的函数名:其实就是为了在任务切换的时候跳转到任务中执行的入口地址;
参数2:传递给建立任务的参数,这个参数基本不会用到;
参数3:传递给建立任务的堆栈,每个任务都有独一无二的堆栈;
参数4:传递给任务的优先级。
(2) 函数内容分析: 当OS_TASK_CREATE_EN宏大于0的时候,我们才可以使用创建任务的函数。如果创建的时候检测到任务的优先级比最大的优先级(数值上,实际上是最小)还大的话,那么就直接退出,输出一个错误码。我们不允许创建任务是在中断中进行的,所以我们也会在中断时创建任务返回一个错误码。最后就是把刚刚的四个参数赋值到任务当中去,实现任务的创建。

2、再谈任务堆栈

(1)
UCOS-II任务与OSCtxSw详解_第7张图片
UCOSII支持的堆栈可以是递减的,也可以是递增的。在调用函数OS_TaskCreate(),必须知道堆栈是递减的,还是递增的。 因为必须把堆栈的栈顶地址传递给上面的两个函数。 PS:这里面就有OS_CPU.h文件中的OS_STK_GROWTH为0,需要将堆栈的最低地址传递给任务创建的函数。这个是堆栈从下往上增长的;

3、删除任务,OSTaskDel()

(1)有时候我们需要删除任务,就是说任务返回到休眠状态,并不是说任务代码被删除了,而是仅仅从就绪队列中删除了而已。
参数1:prio:也就是该任务的优先级,当我们支持多任务相同优先级的时候,必须指明任务堆栈,或者任务名,才能删除;
(2)实现这个函数的关键步骤:
3.2.1、把任务从就绪表中移除,也就是不让该任务处于就绪状态中。 3.2.2、假如任务需要事件控制块,消息队列,邮箱等,那么我们就需要在删除任务之前将他所在的链表中移除。
3.2.3、把任务的各种资源释放掉。

4、挂起任务,OS_TaskSuspend()和恢复挂起任务OSTaskResume()

(1)这个函数是必须进行说明的一个函数,因为他涉及到任务的状态机; (2)这个实现挂起的函数主要是删除就绪表中的位图的相应优先级的那个位进行置0的操作;
(3)然后将任务的相应的状态进行赋值为挂起的状态;
(4)最后在最后要进行任务的调度的操作,如果当前是这个任务在进行的话,要切换到别的任务中继续去运行。

你可能感兴趣的:(UCOS,任务切换)