上个笔记说到调用使命延时函数后,体系将会进行使命切换,不然当时运转使命就会一向霸占着CPU的运用权。那么这个使命延时函数中到底有什么奥妙?调用它为什么能够让使命切换自若?这个笔记咱就要揭开uC/OS-II的一大规划精华——使命切换。
特权同学并非软件工程或是核算机科班出身,还真没学过什么操作体系,关于CPU内部架构和作业机制的了解和知道完全赖自身的实践、探索加一些教科书的研读。关于一些概念的论述或许不行专业,假如有些误差也十分欢迎咱们提出来加以纠正,可是我想这些“草根”式的图文或许多少能够协助咱们快速的了解和知道一些作业机理,希望“八九不离十”应该是描述这种状况比较适宜的词汇吧。其实假如能起到这样的作用,那么对这些文章而言也就满足。究竟有板有眼、中规中矩的教科书咱们看得太多了,真的是有些审美疲劳了。
由于要说使命调度的机理,那么咱们不得不先把简直一切嵌入式处理器相关的书本中都会提及的中止概念再提一下。尽管讲中止的书满大街都是,可是我想像图1这样一个简略暗示图就能够把中止说清楚的还真不多(怎样有点“王婆卖瓜自我吹嘘”的嫌疑,脸红中~~)。一个“裸奔”的CPU软件,无非就是一个main函数里边while(1)中包揽一切功用,偶然来个中止呼应一些实时性要求较高的处理,仅此而已。那么,很显然,中止呼应时有一个脱离当时main函数的行为发生,想要让中止呼应前后CPU回到原有的main函数履行状况,则有必要有一些额定的作业要干,第3和6步的出栈、入栈就是。这个暗示图中,咱们需求理解,当某个函数或某些指令占用CPU时,意味着CPU中的寄存器存储着和当时处理状况相关的各种中心数据信息;相同的道理,当中止函数占有CPU数据时,CPU中的寄存器存储着和中止函数相关的各种中心数据。仓库是专门拓荒的一片存储空间,用于和CPU寄存器相映射。一个在main函数中运转着的程序(包含在它的子函数中运转),假如被一个中止信号打断后去履行中止处理,最终回来,这样一个进程,发生了以下一些作业:
1. 运用程序(即Main函数)中履行某条指令,此刻运用程序控制CPU的运用权,表现为占有CPU寄存器。
2. 一个中止源发生,运用程序停下了CPU的运用。
3. 运用程序停下来那一刻的CPU寄存器内容被copy到仓库中,即咱们称之为入栈操作。
4. 程序转到中止处理函数履行,表现为行将到来的下一时刻中止处理函数占有CPU运用权。
5. 中止处理函数具有CPU运用权,表现为占有CPU寄存器,直到中止处理函数履行完结。
6. CPU寄存器康复履行入栈操作前的状况,即从仓库中copy之前入栈的信息,咱们称之为出栈。
7. 运用程序回到中止前的下一条指令开端履行,尽管经过2-6步的“意外事件”,可是除了运用程序比预期延时了一小段时刻履行外,如同这个“意外事件”没有发生过相同。
图1
再来看uC/OS-II中的使命切换是怎么完结的,应该说,和传统CPU的中止机制有着异曲同工之妙。说白了,uC/OS-II其实也是假借中止之名移花接木般完结了使命的切换。由于uC/OS-II中的每个task都比如“裸奔”着的软件程序中的main函数,他们都有时机独立的占用CPU的运用权。Task的写法一般有两种:
void user_task(void* pdata)
{
while (1)
{
//用户代码
}
}
或许
void user_task(void* pdata)
{
//用户代码
OSTaskDel(OS_PRIO_SELF); //删去当时使命
}
前者咱们现已触摸过,在用户代码的最终咱们一般也会加上使命延时函数,让出CPU的控制权。而后者相当于一次性完结的使命,履行过一次该使命后,自我删去,从此隐姓埋名,除非该使命在其他使命函数中被从头树立康复。
OSTaskDel();函数
INT8U OSTaskDel (INT8U prio);
当使命创立并由OSStart()函数发动后,要么处于运转态(同一时刻有且只要一个运转态的使命),要么处于安排妥当态,假如处于某个正在运转的使命运用OSTaskDel()函数删去了该使命自身或许其他使命(闲暇使命OSTaskIdle()是仅有不能被删去的使命),那么被删去的使命并不是从存储代码的程序中物理消失了,这段代码还在,只不过它现已不在使命切换优先级列表中了,今后的使命切换中不会考虑运转该使命,咱们说这种状况叫做休眠态,假如要从休眠态唤醒到安排妥当态,则需求从头创立该函数。
OSTaskIdle()函数
闲暇使命是在OSInit();函数中被树立的,看这个函数的具体内容,发现它其实并没有干什么大事,无非是在那里“消磨时刻”,也的确是这样。但uC/OS-II中有必要树立闲暇函数,并且它的优先级一定是最低的,至于为什么,咱们接下来先讲讲使命切换的机理,然后咱们很简略就能理解的。
void OS_TaskIdle (void *p_arg)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
(void)p_arg; /* Prevent compiler warning for not using 'p_arg' */
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtr++;
OS_EXIT_CRITICAL();
OSTaskIdleHook(); /* Call user definable HOOK */
}
}
如图2所示,这就是使命切换的大体流程。和中止很类似,这儿假定task1要切换到task2,task1中一定会调用使命延时函数(上一个笔记现已说到,这是使命切换的必要条件),咱仍是简略的12345把它说理解:
1. Task1具有CPU的控制权,当时CPU寄存器存储着和task1当时履行指令相关的信息。
2. Task1调用了使命延时函数。
3. CPU寄存器的数据信息被copy到了仓库中,即入栈,这个仓库是task1独有的,圣神不行侵略。
4. 把Task2独有的仓库数据信息paste到CPU寄存器中,即出栈,这是要康复task2在上一次具有CPU控制权的最终一条履行指令留下的现场。当然也或许task2之前未曾具有过CPU控制权,不要紧,那么默许这个仓库是应该是个空的。
5. 模仿发生了一个软中止源发生,使命延时函数被中止,中止持续履行。
6. 模仿中止处理函数,大约这个函数中什么都不做,为的是有一个中止回来的动作(还真有点买椟还珠的滋味)。
7. 中止回来时,当时的CPU寄存器是task2的作业现场,那么这意味着task2现已具有了CPU的控制权。怎样样?真得是被“移花接木”了,CPU现已从task1的while(1)里边被“俘虏”到了task2中。至此,一次使命切换完结。
图2
咱们能够好好消化一下,其实使命的切换还真的不是传说中那么奇特。到这儿,仍是没有把使命延时函数的奥秘面纱揭开,不要紧,一个一个来,咱要各个击破,每个知识点都吃透了才行。
咱们先给出使命延时的一个最基本函数OSTimeDly()的程序,留意看该函数的最终调用了OS_Sched()函数,该函数就是CPU使命切换的“元凶巨恶”,好奇心强的朋友可不能放过它。
void OSTimeDly (INT16U ticks)
{
INT8U y;
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
if (OSIntNesting > 0) { /* See if trying to call from an ISR */
return;
}
if (ticks > 0) { /* 0 means no delay! */
OS_ENTER_CRITICAL();
y = OSTCBCur->OSTCBY; /* Delay current task */
OSRdyTbl[y] &= ~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0) {
OSRdyGrp &= ~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks; /* Load ticks in TCB */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next task to run! */
}
}
OS_Sched()函数完结使命级的调度,该函数完结了前一个使命CPU寄存器的入栈和后一个使命CPU寄存器的出栈,并且在最终做了一次“模仿”中止回来的操作,这个操作是由OS_TASK_SW()函数里完结的。
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3 /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0;
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting == 0) { /* Schedule only if all ISRs done and … */
if (OSLockNesting == 0) { /* … scheduler is not locked */
OS_SchedNew();
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
#if OS_TASK_PROFILE_EN > 0
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
}
}
OS_EXIT_CRITICAL();
}
函数void OSCtxSw(void);咱还真无法右键open翻开,一般不是用C言语写的,而是用汇编来完结这个操作。
再回到 OSTaskIdle()函数,试想想假如体系中只要两个用户使命task1和task2,假如他们都调用使命延时函数,那么模仿中止回来后体系应该到哪里持续履行程序呢?不得而知,或许程序就要跑飞了,基于此,OSTaskIdle()函数就有存在的必要了,尽管它如同不干什么事,但至少它能确保体系在没有task可履行的时分处于一个可控的状况中。除此以外,OSTaskIdle()函数中还做了一件或许咱们多少仍是有些介意的CPU运用率的核算,它是经过核算闲暇时刻来揣度每秒钟CPU的运用率的。