μC/OSIII学习day6
介绍
任务的删除,任务管理
# 任务的删除
# μC/OS如何实现任务的删除
# 任务删除函数
OSTaskDel()函数
//删除一个指定的任务(包括自身) #if OS_CFG_TASK_DEL_EN > 0u void OSTaskDel(OS_TCB *p_tcb,OS_ERR *p_err){ CPU_SR_ALLOC(); /* 不允许删除空闲任务 */ if (p_tcb == &OSIdleTaskTCB) { *p_err = OS_ERR_TASK_DEL_IDLE; return; } /* 删除自己 */ if (p_tcb == (OS_TCB *)0) { CPU_CRITICAL_ENTER(); p_tcb = OSTCBCurPtr; CPU_CRITICAL_EXIT(); } OS_CRITICAL_ENTER(); /* 根据任务的状态来决定删除的动作 */ switch (p_tcb->TaskState) { case OS_TASK_STATE_RDY: OS_RdyListRemove(p_tcb); break; case OS_TASK_STATE_SUSPENDED: break; /* 任务只是在延时,并没有在任何等待列表 */ case OS_TASK_STATE_DLY: case OS_TASK_STATE_DLY_SUSPENDED: OS_TickListRemove(p_tcb); break; case OS_TASK_STATE_PEND: case OS_TASK_STATE_PEND_SUSPENDED: case OS_TASK_STATE_PEND_TIMEOUT: case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED: OS_TickListRemove(p_tcb); #if 0/* 目前我们还没有实现等待列表,暂时先把这部分代码注释 */ /* 看看在等待什么 */ switch (p_tcb->PendOn) { case OS_TASK_PEND_ON_NOTHING: /* 任务信号量和队列没有等待队列,直接退出 */ case OS_TASK_PEND_ON_TASK_Q: case OS_TASK_PEND_ON_TASK_SEM: break; /* 从等待列表移除 */ case OS_TASK_PEND_ON_FLAG: case OS_TASK_PEND_ON_MULTI: case OS_TASK_PEND_ON_MUTEX: case OS_TASK_PEND_ON_Q: case OS_TASK_PEND_ON_SEM: OS_PendListRemove(p_tcb); break; default: break; } break; #endif default: OS_CRITICAL_EXIT(); *p_err = OS_ERR_STATE_INVALID; return; } /* 初始化 TCB 为默认值 */ OS_TaskInitTCB(p_tcb); /* 修改任务的状态为删除态,即处于休眠 */ p_tcb->TaskState = (OS_STATE)OS_TASK_STATE_DEL; OS_CRITICAL_EXIT_NO_SCHED(); /* 任务切换,寻找最高优先级的任务 */ OSSched(); *p_err = OS_ERR_NONE; } #endif/* OS_CFG_TASK_DEL_EN > 0u */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76- 任务删除是一个可选功能,由
OS_CFG_TASK_DEL_EN
控制(os_cfg.h). - 空闲任务不能被删除.系统必须至少有一个任务在运行,当没有其他用户任务运行的时候,系统就会运行空闲任务.
- 任务删除是一个可选功能,由
# 任务管理
# 任务管理的基本概念
- 从系统的角度看,任务是竞争系统资源的最小运行单元.μC/OS是一个支持多任务的操作系统.在μC/OS中,任务可以使用或等待CPU,使用内存空间等系统资源,并独立于其他任务运行,任何数量的任务可以共享同一个优先级,处于就绪态的多个相同优先级任务将会以时间片切换的方式共享处理器.
- μC/OS的任务可认为是一系列独立任务的集合,每个任务在自己的环境中运行.在任何时刻,只有一个任务得到运行,μC/OS调度器决定运行哪个任务.调度器会不断的启动,轮换每一个任务,宏观看上去所有的任务都在同时在执行.任务不需要对调度器的活动有所了解,在任务切入切出时保存上下文环境(寄存器值,栈内容)是调度器主要的职责.为了实现这点,μC/OS中每个任务都需要有自己的栈空间.当任务切出时,它的执行环境会被保存在该任务的栈空间中,这样当任务再次运行时,就能从栈中正确的恢复上次的运行环境,任务越多,需要的栈空间就越大,而一个系统能运行多少个任务,取决于系统的可用的SRAM.
- μC/OS的可以给用户提供多个任务单独享有独立的栈空间.系统可用决定任务的状态,决定任务是否可以运行,同时还能运用内核的IPC通信资源,实现了任务之间的通信,帮助用户管理业务程序流程.这样用户可以将更多的精力投入到业务功能的实现中.
- μC/OS中的任务是抢占式调度机制,高优先级的任务可打断低优先级任务,低优先级任务必须在高优先级任务阻塞或结束后才能得到调度.同时μC/OS也支持时间片轮转调度方式,只不过时间片的调度是不允许抢占任务的CPU使用权.
- 任务通常会运行在一个死循环中,也不会退出,如果一个任务不再需要,可以调用μC/OS中的任务删除API函数接口显式地将其删除.
# 任务调度器的基本概念
- μC/OS中提供的任务调度器是基于优先级的全抢占式调度:在系统中除了中断处理函数,调度器上锁部分的代码和禁止中断的代码是不可抢占的之外,系统的其他部分都是可以抢占的.系统理论上可以支持无数个优先级 (0-N,优先级数值越大的任务优先级越低,
(OS_CFG_PRIO_MAX - 1u)
为最低优先级,分配给空闲任务使用,一般不建议用户来使用这个优先级.一般系统默认的最大可用优先级数目为 32.在一些资源比较紧张的系统中,用户可以根据实际情况选择只支持8个或自定义个数优先级的系统配置.在系统中,当有比当前任务优先级更高的任务就绪时,当前任务将立刻被切出,高优先级任务抢占处理器运行. - 一个操作系统如果只是具备了高优先级任务能够"立即"获得处理器并得到执行的特点,那么它仍然不算是实时操作系统.因为这个查找最高优先级任务的过程决定了调度时间是否具有确定性,例如一个包含n个就绪任务的系统中,如果仅仅从头找到尾,那么这个时间将直接和n相关,而下一个就绪任务抉择时间的长短将会极大的影响系统的实时性.
- μC/OS内核中采用两种方法寻找最高优先级的任务,第一种是通用的方法,因为μC/OS防止CPU平台不支持前导零指令,就采用C语言模仿前导零指令的效果实现了快速查找到最高优先级任务的方法.而第二种方法则是特殊方法,利用硬件计算前导零指令CLZ,这样子一次就能知道哪一个优先级任务能够运行,这种调度算法比普通方法更快捷,但受限于平台(在STM32中我们就使用这种方法).
- μC/OS内核中也允许创建相同优先级的任务.相同优先级的任务采用时间片轮转方式进行调度(也就是通常说的分时调度器),时间片轮转调度仅在当前系统中无更高优先级就绪任务存在的情况下才有效.为了保证系统的实时性,系统尽最大可能地保证高优先级的任务得以运行.任务调度的原则是一旦任务状态发生了改变,并且当前运行的任务优先级小于优先级队列组中任务最高优先级时,立刻进行任务切换(除非当前系统处于中断处理程序中或禁止任务切换的状态).
# 任务状态迁移
任务状态迁移图①:创建任务→就绪态(Ready):任务创建完成后进入就绪态,表明任务已准备就绪,随时可以运行,只等待调度器进行调度.
任务状态迁移图②:就绪态→运行态(Running):发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态.
任务状态迁移图③:运行态→就绪态:有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪列表中,等待最高优先级的任务运行完毕继续运行原来的任务(此处可以看作CPU使用权被更高优先级的任务抢占了).
任务状态迁移图④:运行态→阻塞态(或者称为挂起态Suspended):正在运行的任务发生阻塞(挂起,延时,读信号量等待)时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务.
任务状态迁移图⑤:阻塞态→就绪态:阻塞的任务被恢复后(任务恢复,延时时间超时,读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态.
任务状态迁移图⑥⑦⑧:就绪态,阻塞态,运行态→删除态(Delete):任务可以通过调用
OSTaskDel()
API函数都可以将处于任何状态的任务删除,被删除后的任务将不能再次使用,关于任务的资源都会被系统回收.任务状态迁移图⑨:删除态→就绪态:这就是创建任务的过程,一个任务将会从无到有,创建成功的任务可以参与系统的调度.
ps:此处的任务状态只是大致的任务状态而并非μC/OS的所有任务状态.
# μC/OS任务状态
μC/OS系统中的每一任务都有多种运行状态.系统初始化完成后,创建的任务就可以在系统中竞争一定的资源,由内核进行调度.
μC/OS的任务状态通常分为以下几种:
- 就绪
OS_TASK_STATE_RDY
:该任务在就绪列表中,就绪的任务已经具备执行的能力,只等待调度器进行调度,新创建的任务会初始化为就绪态. - 延时
OS_TASK_STATE_DLY
:该任务处于延时调度状态. - 等待
OS_TASK_STATE_PEND
:任务调用OSQPend()
,OSSemPend()
这类等待函数,系统就会设置一个超时时间让该任务处于等待状态,如果超时时间设置为0,任务的状态,无限期等下去,直到事件发生.如果超时时间为N(N>0),在N个时间内任务等待的事件或信号都没发生,就退出等待状态转为就绪状态. - 运行
Running
:该状态表明任务正在执行,此时它占用处理器,ΜC/OS调度器选择运行的永远是处于最高优先级的就绪态任务,当任务被运行的一刻,它的任务状态就变成了运行态,其实运行态的任务也是处于就绪列表中的. - 挂起
OS_TASK_STATE_SUSPENDED
:任务通过调用OSTaskSuspend()
函数能够挂起自己或其他任务,调用OSTaskResume()
是使被挂起的任务回复运行的唯一的方法.挂起一任务意味着该任务再被恢复运行以前不能够取得CPU的使用权,类似强行暂停一个任务. - 延时+挂起
OS_TASK_STATE_DLY_SUSPENDED
:任务先产生一个延时,延时没结束的时候被其他任务挂起,挂起的效果叠加,当且仅当延时结束并且挂起被恢复了,该任务才能够再次运行. - 等待+挂起
OS_TASK_STATE_PEND_SUSPENDED
:任务先等待一个事件或信号的发生(无限期等待),还没等待到就被其他任务挂起,挂起的效果叠加,当且仅当任务等待到事件或信号并且挂起被恢复了,该任务才能够再次运行. - 超时等待+挂起
OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED
:任务在指定时间内等待事件或信号的产生,但是任务已经被其他任务挂起. - 删除
OS_TASK_STATE_DEL
:任务被删除后的状态,任务被删除后将不再运行,除非重新创建任务.
# 常用任务函数
函数名称 | 函数作用 |
---|---|
OS_TaskSuspend() | 任务挂起 |
OSTaskResume() | 任务恢复 |
OSTaskDel() | 任务删除 |
OSTimeDly() | 任务延时 |
OSTimeDlyHMSM() | 任务延时 |
OS_TaskSuspend()
被挂起的任务绝不会得到CPU的使用权,不管该任务具有什么优先级.任务可以通过调用
vTaskSuspend()
函数都可以将处于任何状态的任务挂起,被挂起的任务得不到CPU的使用权,也不会参与调度,它相对于调度器而言是不可见的,除非它从挂起态中解除.任务挂起是我们经常使用的一个函数,想要使用的就必须将宏定义OS_CFG_TASK_SUSPEND_EN
启用,这样在编译的时候才会包含OS_TaskSuspend()
函数.#if OS_CFG_TASK_SUSPEND_EN > 0u//如果启用了函数 OSTaskSuspend() void OS_TaskSuspend(OS_TCB *p_tcb,OS_ERR *p_err){ CPU_SR_ALLOC();//使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和 //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器 //SR(临界段关中断只需保存SR),开中断时将该值还原. CPU_CRITICAL_ENTER(); //关中断 if (p_tcb == (OS_TCB *)0) { //如果 p_tcb 为空 p_tcb = OSTCBCurPtr; //挂起自身 } if (p_tcb == OSTCBCurPtr) { //如果是挂起自身 if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { //如果调度器被锁 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_SCHED_LOCKED; //错误类型为"调度器被锁" return; //返回,停止执行 } } *p_err = OS_ERR_NONE; //错误类型为"无错误" switch (p_tcb->TaskState) {//根据 p_tcb 的任务状态分类处理 case OS_TASK_STATE_RDY://如果是就绪状态 OS_CRITICAL_ENTER_CPU_EXIT(); //锁调度器,重开中断 p_tcb->TaskState = OS_TASK_STATE_SUSPENDED; //任务状态改为"挂起状态" p_tcb->SuspendCtr = (OS_NESTING_CTR)1; //挂起前套数为 1 OS_RdyListRemove(p_tcb); //将任务从就绪列表移除 OS_CRITICAL_EXIT_NO_SCHED(); //开调度器,不进行调度 break; //跳出 case OS_TASK_STATE_DLY: (7)//如果是延时状态将改为"延时中被挂起" p_tcb->TaskState = OS_TASK_STATE_DLY_SUSPENDED; p_tcb->SuspendCtr = (OS_NESTING_CTR)1; //挂起前套数为 1 CPU_CRITICAL_EXIT(); //开中断 break; //跳出 case OS_TASK_STATE_PEND://如果是无期限等待状态将改为"无期限等待中被挂起" p_tcb->TaskState = OS_TASK_STATE_PEND_SUSPENDED; p_tcb->SuspendCtr = (OS_NESTING_CTR)1; //挂起前套数为 1 CPU_CRITICAL_EXIT(); //开中断 break; //跳出 case OS_TASK_STATE_PEND_TIMEOUT://如果是有期限等待将改为"有期限等待中被挂起" p_tcb->TaskState = OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED; p_tcb->SuspendCtr = (OS_NESTING_CTR)1; //挂起前套数为 1 CPU_CRITICAL_EXIT(); //开中断 break; //跳出 case OS_TASK_STATE_SUSPENDED://如果状态中有挂起状态 case OS_TASK_STATE_DLY_SUSPENDED: case OS_TASK_STATE_PEND_SUSPENDED: case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED: p_tcb->SuspendCtr++; //挂起嵌套数加 1 CPU_CRITICAL_EXIT(); //开中断 break; //跳出 default://如果任务状态超出预期 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_STATE_INVALID; //错误类型为"状态非法" return; //返回,停止执行 } OSSched();//调度任务 } #endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61ps:任务可以调用
OS_TaskSuspend()
这个函数来挂起任务自身,但是在挂起自身的时候会进行一次任务上下文切换,需要挂起自身就将任务控制块指针设置为NULL或0传递进来即可.无论任务是什么状态都可以被挂起,只要调用了OS_TaskSuspend()
这个函数就会挂起成功,不论是挂起其他任务还是挂起任务自身.任务的挂起与恢复函数在很多时候都是很有用的,比如我们想暂停某个任务运行一段时间,但是我们又需要在其恢复的时候继续工作,那么删除任务是不可能的,因为删除了任务的话,任务的所有的信息都是不可能恢复的了,删除是完完全全删除了,里面的资源都被系统释放掉,但是挂起任务就不会这样子,调用挂起任务函数,仅仅是将任务进入挂起态,其内部的资源都会保留下来,同时也不会参与系统中任务的调度,当调用恢复函数的时候,整个任务立即从挂起态进入就绪态,并且参与任务的调度,如果该任务的优先级是当前就绪态优先级最高的任务,那么立即会按照挂起前的任务状态继续执行该任务,从而达到我们需要的效果,注意,是继续执行,也就是说,挂起任务之前是什么状态,都会被系统保留下来,在恢复的瞬间,继续执行.这个任务函数的使用方法是很简单的,只需把任务句柄传递进来即可,OS_TaskSuspend()
会根据任务句柄的信息将对应的任务挂起,任务的设计要点.OSTaskResume()函数
任务恢复就是让挂起的任务重新进入就绪状态,恢复的任务会保留挂起前的状态信息,在恢复的时候根据挂起时的状态继续运行.如果被恢复任务在所有就绪态任务中,处于最高优先级列表的第一位,那么系统将进行任务上下文的切换.
#if OS_CFG_TASK_SUSPEND_EN > 0u//如果启用了函数 OSTaskResume() void OSTaskResume(OS_TCB *p_tcb,OS_ERR *p_err){ CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和 //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器 // SR(临界段关中断只需保存 SR),开中断时将该值还原. #ifdef OS_SAFETY_CRITICAL//如果启用了安全检测 if (p_err == (OS_ERR *)0) { //如果 p_err 为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,停止执行 } #endif //如果禁用了中断延迟发布和中断中非法调用检测 #if (OS_CFG_ISR_POST_DEFERRED_EN == 0u)&&(OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u) if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果在中断中调用该函数 *p_err = OS_ERR_TASK_RESUME_ISR; //错误类型为"在中断中恢复任务" return; //返回,停止执行 } #endif CPU_CRITICAL_ENTER(); //关中断 #if OS_CFG_ARG_CHK_EN > 0u//如果启用了参数检测 if ((p_tcb == (OS_TCB *)0)||(p_tcb == OSTCBCurPtr)) { //如果被恢复任务为空或是自身 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_TASK_RESUME_SELF; //错误类型为"恢复自身" return; //返回,停止执行 } #endif CPU_CRITICAL_EXIT(); //关中断 #if OS_CFG_ISR_POST_DEFERRED_EN > 0u//如果启用了中断延迟发布 if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数在中断中被调用 OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_TASK_RESUME, (void *)p_tcb, (void *)0, (OS_MSG_SIZE)0, (OS_FLAGS )0, (OS_OPT )0, (CPU_TS )0, (OS_ERR *)p_err);//把恢复任务命令发布到中断消息队列 return; //返回,停止执行 } #endif /* 如果禁用了中断延迟发布或不是在中断中调用该函数,直接调用`OS_TaskResume()`函数恢复任务*/ OS_TaskResume(p_tcb, p_err); //直接将任务 p_tcb 恢复 } #endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46OS_TaskResume()
函数#if OS_CFG_TASK_SUSPEND_EN > 0u//如果启用了函数OSTaskResume() void OS_TaskResume(OS_TCB *p_tcb,OS_ERR *p_err){ CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和 //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器 // SR(临界段关中断只需保存 SR),开中断时将该值还原. CPU_CRITICAL_ENTER(); //关中断 *p_err = OS_ERR_NONE; //错误类型为"无错误" switch (p_tcb->TaskState) {//根据 p_tcb 的任务状态分类处理 case OS_TASK_STATE_RDY: //如果状态中没有挂起状态 case OS_TASK_STATE_DLY: case OS_TASK_STATE_PEND: case OS_TASK_STATE_PEND_TIMEOUT: CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_TASK_NOT_SUSPENDED;//错误类型为"任务未被挂起" break; //跳出 case OS_TASK_STATE_SUSPENDED://如果是"挂起状态" OS_CRITICAL_ENTER_CPU_EXIT(); //锁调度器,重开中断 p_tcb->SuspendCtr--;//任务的挂起嵌套数减 1 if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0) { //如果挂起前套数为 0 p_tcb->TaskState = OS_TASK_STATE_RDY; //修改状态为"就绪状态" OS_TaskRdy(p_tcb); //把 p_tcb 插入就绪列表 } OS_CRITICAL_EXIT_NO_SCHED(); //开调度器,不调度任务 break; //跳出 case OS_TASK_STATE_DLY_SUSPENDED://如果是"延时中被挂起" p_tcb->SuspendCtr--; //任务的挂起嵌套数减 1 if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0) { //如果挂起前套数为 0 p_tcb->TaskState = OS_TASK_STATE_DLY; //修改状态为"延时状态" } CPU_CRITICAL_EXIT(); //开中断 break; //跳出 case OS_TASK_STATE_PEND_SUSPENDED://如果是"无期限等待中被挂起" p_tcb->SuspendCtr--; //任务的挂起嵌套数减 1 if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0) { //如果挂起前套数为 0 p_tcb->TaskState = OS_TASK_STATE_PEND; //修改状态为"无期限等待状态" } CPU_CRITICAL_EXIT(); //开中断 break; //跳出 case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:(7)//如果是"有期限等待中被挂起" p_tcb->SuspendCtr--; //任务的挂起嵌套数减 1 if (p_tcb->SuspendCtr == (OS_NESTING_CTR)0) { //如果挂起前套数为0 p_tcb->TaskState = OS_TASK_STATE_PEND_TIMEOUT; } CPU_CRITICAL_EXIT(); //开中断 break; //跳出 default: //如果 p_tcb 任务状态超出预期 CPU_CRITICAL_EXIT(); //开中断 *p_err = OS_ERR_STATE_INVALID; //错误类型为"状态非法" return;//跳出 } OSSched();//调度任务 } #endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55ps:
OSTaskResume()
函数用于恢复挂起的任务.任务在挂起时候调用过多少次的OS_TaskSuspend()
函数,那么就需要调用多少次OSTaskResume()
函数才能将任务恢复运行.OSTaskDel()函数
当一个任务删除另外一个任务时,形参为要删除任务创建时返回的任务句柄,如果是删除自身,则形参为NULL.要想使用该函数必须在os_cfg.h中把
OS_CFG_TASK_DEL_EN
宏定义配置为1,删除的任务将从所有就绪,阻塞,挂起和事件列表中删除.#if OS_CFG_TASK_DEL_EN > 0u//如果启用了函数 OSTaskDel() void OSTaskDel (OS_TCB *p_tcb,OS_ERR *p_err){ CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和 //定义一个局部变量,用于保存关中断前的 CPU 状态寄存器 // SR(临界段关中断只需保存 SR),开中断时将该值还原. #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果 p_err 为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,停止执行 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u(1)//如果启用了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0) { //如果该函数在中断中被调用 *p_err = OS_ERR_TASK_DEL_ISR; //错误类型为"在中断中删除任务" return; //返回,停止执行 } #endif if (p_tcb == &OSIdleTaskTCB) {//如果目标任务是空闲任务 *p_err = OS_ERR_TASK_DEL_IDLE; //错误类型为"删除空闲任务" return; //返回,停止执行 } #if OS_CFG_ISR_POST_DEFERRED_EN > 0u//如果启用了中断延迟发布 if (p_tcb == &OSIntQTaskTCB) { //如果目标任务是中断延迟提交任务 *p_err = OS_ERR_TASK_DEL_INVALID; //错误类型为"非法删除任务" return; //返回,停止执行 } #endif if (p_tcb == (OS_TCB *)0) {//如果 p_tcb 为空 CPU_CRITICAL_ENTER(); //关中断 p_tcb = OSTCBCurPtr; //目标任务设为自身 CPU_CRITICAL_EXIT(); //开中断 } OS_CRITICAL_ENTER(); //进入临界段 switch (p_tcb->TaskState) { //根据目标任务的任务状态分类处理 case OS_TASK_STATE_RDY: //如果是就绪状态 OS_RdyListRemove(p_tcb); //将任务从就绪列表移除 break; //跳出 case OS_TASK_STATE_SUSPENDED: //如果是挂起状态 break; //直接跳出 case OS_TASK_STATE_DLY: //如果包含延时状态 case OS_TASK_STATE_DLY_SUSPENDED: OS_TickListRemove(p_tcb); //将任务从节拍列表移除 break; //跳出 case OS_TASK_STATE_PEND: //如果包含等待状态 case OS_TASK_STATE_PEND_SUSPENDED: case OS_TASK_STATE_PEND_TIMEOUT: case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED: OS_TickListRemove(p_tcb);//将任务从节拍列表移除 switch (p_tcb->PendOn) { //根据任务的等待对象分类处理 case OS_TASK_PEND_ON_NOTHING: //如果没在等待内核对象 case OS_TASK_PEND_ON_TASK_Q: //如果等待的是任务消息队列 case OS_TASK_PEND_ON_TASK_SEM://如果等待的是任务信号量 break; //直接跳出 case OS_TASK_PEND_ON_FLAG: //如果等待的是事件 case OS_TASK_PEND_ON_MULTI: //如果等待多个内核对象 case OS_TASK_PEND_ON_MUTEX: //如果等待的是互斥量 case OS_TASK_PEND_ON_Q: //如果等待的是消息队列 case OS_TASK_PEND_ON_SEM: //如果等待的是信号量 OS_PendListRemove(p_tcb);//将任务从等待列表移除 break; //跳出 default: //如果等待对象超出预期 break; //直接跳出 } break; //跳出 default: //如果目标任务状态超出预期 OS_CRITICAL_EXIT(); //退出临界段 *p_err = OS_ERR_STATE_INVALID; //错误类型为"状态非法" return; //返回,停止执行 } #if OS_CFG_TASK_Q_EN > 0u//如果启用了任务消息队列 (void)OS_MsgQFreeAll(&p_tcb->MsgQ); //释放任务的所有任务消息 #endif OSTaskDelHook(p_tcb); //调用用户自定义的钩子函数 #if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u) OS_TLS_TaskDel(p_tcb); /* Call TLSk ␣ ,→ */ #endif #if OS_CFG_DBG_EN > 0u//如果启用了调试代码和变量 OS_TaskDbgListRemove(p_tcb); //将任务从任务调试双向列表移除 #endif OSTaskQty--; //任务数目减 1 OS_TaskInitTCB(p_tcb); //初始化任务控制块 p_tcb->TaskState = (OS_STATE)OS_TASK_STATE_DEL;//标定任务已被删除 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) *p_err = OS_ERR_NONE; //错误类型为"无错误" OSSched(); //调度任务 } #endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100ps:删除任务是说任务将返回并处以删除(休眠)状态,任务的代码不再被μC/OS调用,删除任务不是删除代码,删除任务和挂起任务有些相似,其实有着本质的区别,根本来说,最大的不同就是删除任务队任务控制块的操作,我们知道在任务创建的时候,需要给每个任务分配一个任务控制块,这个任务控制块存储有关这个任务重要的信息,对任务间有至关重要的作用,挂起任务根本不会动任务控制块,但删除任务就会把任务控制块进行初始化,这样子关于任务的任何信息都被抹去.注意了,删除任务并不会释放任务的栈空间.
OSTimeDly()
函数OSTimeDly()
函数在任务设计中用的非常之多,每个任务都必须是死循环,并且是必须要有阻塞的情况,否则低优先级的任务就无法被运行了,OSTimeDly()
函数常用于停止当前任务进行的运行,延时一段时间后再运行.void OSTimeDly (OS_TICK dly,OS_OPT opt,OS_ERR *p_err){ CPU_SR_ALLOC(); //使用到临界段(在关/开中断时)时必须用到该宏,该宏声明和定义一个局部变 //量,用于保存关中断前的 CPU 状态寄存器 SR(临界段关中断只需保存 SR) //开中断时将该值还原. #ifdef OS_SAFETY_CRITICAL(4)//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,不执行延时操作 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u//如果启用(默认启用)了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0u){//如果该延时函数是在中断中被调用 *p_err = OS_ERR_TIME_DLY_ISR; //错误类型为"在中断函数中延时" return; //返回,不执行延时操作 } #endif /* 当调度器被锁时任务不能延时 */ if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0u) { //如果调度器被锁 *p_err = OS_ERR_SCHED_LOCKED; //错误类型为"调度器被锁" return; //返回,不执行延时操作 } switch (opt) {//根据延时选项参数 opt 分类操作 case OS_OPT_TIME_DLY: //如果选择相对时间(从现在起延时多长时间) case OS_OPT_TIME_TIMEOUT: //如果选择超时(实际同上) case OS_OPT_TIME_PERIODIC: //如果选择周期性延时 if (dly == (OS_TICK)0u) {//如果参数 dly 为 0(0 意味不延时) *p_err = OS_ERR_TIME_ZERO_DLY; //错误类型为"0 延时" return; //返回,不执行延时操作 } break; case OS_OPT_TIME_MATCH: //如果选择绝对时间(匹配系统开始运行(OSStart())后的时钟节拍数) break; default://如果选项超出范围 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return; //返回,不执行延时操作 } OS_CRITICAL_ENTER(); //进入临界段 OSTCBCurPtr->TaskState = OS_TASK_STATE_DLY; //修改当前任务的任务状态为延时状态 OS_TickListInsert(OSTCBCurPtr,dly,opt,p_err); //将当前任务插入节拍列表 if (*p_err != OS_ERR_NONE) { //如果当前任务插入节拍列表时出现错误 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) return; //返回,不执行延时操作 } OS_RdyListRemove(OSTCBCurPtr); //从就绪列表移除当前任务 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) OSSched(); //任务切换 *p_err = OS_ERR_NONE; //错误类型为"无错误" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55任务延时OPT
#define OS_OPT_TIME_DLY DEF_BIT_NONE 2 #define OS_OPT_TIME_TIMEOUT ((OS_OPT)DEF_BIT_01) 3 #define OS_OPT_TIME_MATCH ((OS_OPT)DEF_BIT_02) 4 #define OS_OPT_TIME_PERIODIC ((OS_OPT)DEF_BIT_03)
1
2
3
4OS_OPT_TIME_DLY
:dly为相对时间,就是从现在起延时多长时间,到时钟节拍总计数OSTickCtr = OSTickCtr
当前+dly时延时结束.OS_OPT_TIME_TIMEOUT
:跟OS_OPT_TIME_DLY
的作用情况一样.OS_OPT_TIME_MATCH
:dly为绝对时间,就是从系统开始运行(调用OSStart()
)时到节拍总计数OSTickCtr = dly
时延时结束.OS_OPT_TIME_PERIODIC
:周期性延时,跟OS_OPT_TIME_DLY
的作用差不多,如果是长时间延时,该选项更精准一些.
OS_TickListInsert()
函数void OS_TickListInsert (OS_TCB *p_tcb,OS_TICK time,OS_OPT opt,OS_ERR *p_err){ OS_TICK tick_delta; OS_TICK tick_next; OS_TICK_SPOKE *p_spoke; OS_TCB *p_tcb0; OS_TCB *p_tcb1; OS_TICK_SPOKE_IX spoke; if (opt == OS_OPT_TIME_MATCH){ //如果 time 是个绝对时间 tick_delta = time - OSTickCtr - 1u; //计算离到期还有多长时间 if (tick_delta > OS_TICK_TH_RDY){ //如果延时时间超过了门限 p_tcb->TickCtrMatch = (OS_TICK )0u; //将任务的时钟节拍的匹配变量置0 p_tcb->TickRemain = (OS_TICK )0u; //将任务的延时还需时钟节拍数置0 p_tcb->TickSpokePtr = (OS_TICK_SPOKE *)0; //该任务不插入节拍列表 *p_err = OS_ERR_TIME_ZERO_DLY; //错误类型相当于"0 延时" return; //返回,不将任务插入节拍列表 } p_tcb->TickCtrMatch = time; //任务等待的匹配点为 OSTickCtr = time p_tcb->TickRemain = tick_delta + 1u; //计算任务离到期还有多长时间 } else if (time > (OS_TICK)0u){ //如果 time > 0 if (opt == OS_OPT_TIME_PERIODIC) { //如果 time 是周期性时间 tick_next = p_tcb->TickCtrPrev + time; //计算任务接下来要匹配的时钟节拍总计数 tick_delta = tick_next - OSTickCtr - 1u; //计算任务离匹配还有个多长时间 if (tick_delta < time){ //如果 p_tcb->TickCtrPrev<OSTickCtr+1 p_tcb->TickCtrMatch = tick_next; //将 p_tcb->TickCtrPrev +time 设为时钟节拍匹配点 } else { //如果 p_tcb->TickCtrPrev >= OSTickCtr + 1 p_tcb->TickCtrMatch = OSTickCtr + time; //将 OSTickCtr +time 设为时钟节拍匹配点 } p_tcb->TickRemain = p_tcb->TickCtrMatch - OSTickCtr; //计算任务离到期还有多长时间 p_tcb->TickCtrPrev = p_tcb->TickCtrMatch; //保存当前匹配值为下一周期延时用 } else { //如果 time 是相对时间 p_tcb->TickCtrMatch = OSTickCtr + time; //任务等待的匹配点为 ,OSTickCtr + time p_tcb->TickRemain = time; //计算任务离到期的时间就是 time } } else { //如果 time = 0 p_tcb->TickCtrMatch = (OS_TICK )0u; //将任务的时钟节拍的匹配变量置0 p_tcb->TickRemain = (OS_TICK )0u; //将任务的延时还需时钟节拍数置 0 p_tcb->TickSpokePtr = (OS_TICK_SPOKE *)0; //该任务不插入节拍列表 *p_err = OS_ERR_TIME_ZERO_DLY; //错误类型为"0 延时" return; //返回,不将任务插入节拍列表 } spoke = (OS_TICK_SPOKE_IX)(p_tcb->TickCtrMatch % OSCfg_TickWheelSize); //使用哈希算法(取余)来决定任务存于数组 p_spoke = &OSCfg_TickWheel[spoke]; //OSCfg_TickWheel 的哪个元素(组织一个节拍列表), //与更新节拍列表相对应,可方便查找到期任务. if (p_spoke->NbrEntries == (OS_OBJ_QTY)0u) { //如果当前节拍列表为空 p_tcb->TickNextPtr = (OS_TCB *)0; //任务中指向节拍列表中下一个任务的指针置空 p_tcb->TickPrevPtr = (OS_TCB *)0; //任务中指向节拍列表中前一个任务的指针置空 p_spoke->FirstPtr = p_tcb; //当前任务被列为该节拍列表的第一个任务 p_spoke->NbrEntries = (OS_OBJ_QTY)1u; //节拍列表中的元素数目为 1 } else { //如果当前节拍列表非空 p_tcb1 = p_spoke->FirstPtr; //获取列表中的第一个任务 while (p_tcb1 != (OS_TCB *)0) { //如果该任务存在 p_tcb1->TickRemain = p_tcb1->TickCtrMatch - OSTickCtr;//计算该任务的剩余等待时间 if (p_tcb->TickRemain > p_tcb1->TickRemain) { //如果当前任务的剩余等待时间大于该任务的 if (p_tcb1->TickNextPtr != (OS_TCB *)0) {//如果该任务不是列表的最后一个元素 p_tcb1 = p_tcb1->TickNextPtr; //让当前任务继续与该任务的下一个任务作比较 } else { //如果该任务是列表的最后一个元素 p_tcb->TickNextPtr = (OS_TCB *)0; //当前任务为列表的最后一个元素 p_tcb->TickPrevPtr = p_tcb1; //该任务是当前任务的前一个元素 p_tcb1->TickNextPtr = p_tcb; //当前任务是该任务的后一个元素 p_tcb1 = (OS_TCB *)0; //插入完成,退出 while␣ ,→循环 } } else { //如果当前任务的剩余等待时间不大于该任务的 if (p_tcb1->TickPrevPtr == (OS_TCB *)0) {//如果该任务是列表的第一个元素 p_tcb->TickPrevPtr = (OS_TCB *)0; //当前任务就作为列表的第一个元素 p_tcb->TickNextPtr = p_tcb1; //该任务是当前任务的后一个元素 p_tcb1->TickPrevPtr = p_tcb; //当前任务是该任务的前一个元素 p_spoke->FirstPtr = p_tcb; //当前任务是列表的第一个元素 } else { //如果该任务也不是是列表的第一个元素 p_tcb0 = p_tcb1->TickPrevPtr; // p_tcb0 暂存该任务的前一个任务 p_tcb->TickPrevPtr = p_tcb0; //该任务的前一个任务作为当前任务的前一个任务 p_tcb->TickNextPtr = p_tcb1; //该任务作为当前任务的后一个任务 p_tcb0->TickNextPtr = p_tcb; // p_tcb0 暂存的任务的下一个任务改为当前任务 p_tcb1->TickPrevPtr = p_tcb; // 该任务的前一个任务也改为当前任务 } p_tcb1 = (OS_TCB *)0; //插入完成,退出 while 循环 } } p_spoke->NbrEntries++; //节拍列表中的元素数目加 1 } //更新节拍列表的元素数目的最大记录 if (p_spoke->NbrEntriesMax < p_spoke->NbrEntries) { p_spoke->NbrEntriesMax = p_spoke->NbrEntries; } p_tcb->TickSpokePtr = p_spoke; //记录当前任务存放于哪个节拍列表 *p_err = OS_ERR_NONE;//错误类型为"无错误" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98任务的延时在实际中运用特别多,因为需要暂停一个任务,让任务放弃 CPU,延时结束后再继续运行该任务,如果任务中没有阻塞的话,比该任务优先级低的任务则无法得到 CPU 的使用权,就无法运行
OSTimeDlyHMSM()函数
OSTimeDlyHMSM()
函数与OSTimeDly()
函数的功能类似,也是用于停止当前任务进行的运行,延时一段时间后再运行,但是OSTimeDlyHMSM()
函数会更加直观,延时多少个小时,分钟,秒,毫秒.但是,用户若要使用OSTimeDlyHMSM()
函数,必须将宏OS_CFG_TIME_DLY_HMSM_EN
(os_cfg.h)设为1#if OS_CFG_TIME_DLY_HMSM_EN > 0u//如果启用(默认启用)了OSTimeDlyHMSM()函数 2 void OSTimeDlyHMSM (CPU_INT16U hours,//小时数 CPU_INT16U minutes,//分钟数 CPU_INT16U seconds,//秒数 CPU_INT32U milli,//毫秒数 OS_OPT opt,//选项 OS_ERR *p_err)){ #if OS_CFG_ARG_CHK_EN > 0u//如果启用(默认启用)了参数检测功能 CPU_BOOLEAN opt_invalid; //声明变量用于参数检测 CPU_BOOLEAN opt_non_strict; #endif OS_OPT opt_time; OS_RATE_HZ tick_rate; OS_TICK ticks; CPU_SR_ALLOC(); #ifdef OS_SAFETY_CRITICAL//如果启用(默认禁用)了安全检测 if (p_err == (OS_ERR *)0) { //如果错误类型实参为空 OS_SAFETY_CRITICAL_EXCEPTION(); //执行安全检测异常函数 return; //返回,不执行延时操作 } #endif #if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u//如果启用(默认启用)了中断中非法调用检测 if (OSIntNestingCtr > (OS_NESTING_CTR)0u){//如果该延时函数是在中断中被调用 *p_err = OS_ERR_TIME_DLY_ISR; //错误类型为"在中断函数中延时" return; //返回,不执行延时操作 } #endif /* 当调度器被锁时任务不能延时 */ if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0u) {//如果调度器被锁 *p_err = OS_ERR_SCHED_LOCKED; //错误类型为"调度器被锁" return; //返回,不执行延时操作 } opt_time = opt & OS_OPT_TIME_MASK; //检测除选项中与延时时间性质有关的位 switch (opt_time) { //根据延时选项参数 opt 分类操作 case OS_OPT_TIME_DLY: //如果选择相对时间(从现在起延时多长时间) case OS_OPT_TIME_TIMEOUT: //如果选择超时(实际同上) case OS_OPT_TIME_PERIODIC: //如果选择周期性延时 if (milli == (CPU_INT32U)0u) { //如果毫秒数为 0 if (seconds == (CPU_INT16U)0u) { //如果秒数为 0 if (minutes == (CPU_INT16U)0u) { //如果分钟数为 0 if (hours == (CPU_INT16U)0u) { //如果小时数为 0 *p_err = OS_ERR_TIME_ZERO_DLY; //错误类型为"0 延时" return;//返回,不执行延时操作 } } } } break; case OS_OPT_TIME_MATCH: //如果选择绝对时间(把系统开始运行(OSStart() 时做为起点) break; default: //如果选项超出范围 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return; //返回,不执行延时操作 } #if OS_CFG_ARG_CHK_EN > 0u //如果启用(默认启用)了参数检测功能 opt_invalid = DEF_BIT_IS_SET_ANY(opt, ~OS_OPT_TIME_OPTS_MASK); //检测除选项位以后其他位是否被置位 if (opt_invalid == DEF_YES) {//如果除选项位以后其他位有被置位的 *p_err = OS_ERR_OPT_INVALID; //错误类型为"选项非法" return; //返回,不执行延时操作 } opt_non_strict = DEF_BIT_IS_SET(opt, OS_OPT_TIME_HMSM_NON_STRICT); //检测有关时间参数取值范围的选项位 if (opt_non_strict != DEF_YES) {//如果选项选择了 OS_OPT_TIME_HMSM_STRICT if (milli > (CPU_INT32U)999u) { //如果毫秒数>999 *p_err = OS_ERR_TIME_INVALID_MILLISECONDS; //错误类型为"毫秒数不可用" return; //返回,不执行延时操作 } if (seconds > (CPU_INT16U)59u) { //如果秒数>59 *p_err = OS_ERR_TIME_INVALID_SECONDS; //错误类型为"秒数不可用" return; //返回,不执行延时操作 } if (minutes > (CPU_INT16U)59u) { //如果分钟数>59 *p_err = OS_ERR_TIME_INVALID_MINUTES; //错误类型为"分钟数不可用" return; //返回,不执行延时操作 } if (hours > (CPU_INT16U)99u) { //如果小时数>99 *p_err = OS_ERR_TIME_INVALID_HOURS; //错误类型为"小时数不可用" return; //返回,不执行延时操作 } } else { //如果选项选择了 OS_OPT_TIME_HMSM_NON_STRICT if (minutes > (CPU_INT16U)9999u) { //如果分钟数>9999 *p_err = OS_ERR_TIME_INVALID_MINUTES; //错误类型为"分钟数不可用" return; //返回,不执行延时操作 } if (hours > (CPU_INT16U)999u) { //如果小时数>999 *p_err = OS_ERR_TIME_INVALID_HOURS; //错误类型为"小时数不可用" return; //返回,不执行延时操作 } } #endif /* 将延时时间转换成时钟节拍数 */ tick_rate = OSCfg_TickRate_Hz; //获取时钟节拍的频率 ticks = ((OS_TICK)hours * (OS_TICK)3600u + (OS_TICK)minutes * (OS_TICK)60u + (OS_TICK)seconds) * tick_rate + (tick_rate * ((OS_TICK)milli + (OS_TICK)500u / tick_rate)) / (OS_TICK)1000u;//将延时时间转换成时钟节拍数 if (ticks > (OS_TICK)0u) { //如果延时节拍数>0 OS_CRITICAL_ENTER(); //进入临界段 OSTCBCurPtr->TaskState = OS_TASK_STATE_DLY; //修改当前任务的任务状态为延时状态 OS_TickListInsert(OSTCBCurPtr, //将当前任务插入节拍列表 ticks, opt_time, p_err); if(*p_err != OS_ERR_NONE) { //如果当前任务插入节拍列表时出现错误 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) return; //返回,不执行延时操作 } OS_RdyListRemove(OSTCBCurPtr); //从就绪列表移除当前任务 OS_CRITICAL_EXIT_NO_SCHED(); //退出临界段(无调度) OSSched(); //任务切换 *p_err = OS_ERR_NONE; //错误类型为"无错误" } else { //如果延时节拍数 =0 *p_err = OS_ERR_TIME_ZERO_DLY; //错误类型为"0 延时" } } #endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125任务延时的可选选项opt
#define OS_OPT_TIME_DLY DEF_BIT_NONE 2 #define OS_OPT_TIME_TIMEOUT ((OS_OPT)DEF_BIT_01) 3 #define OS_OPT_TIME_MATCH ((OS_OPT)DEF_BIT_02) 4 #define OS_OPT_TIME_PERIODIC ((OS_OPT)DEF_BIT_03) 56 #define OS_OPT_TIME_HMSM_STRICT ((OS_OPT)DEF_BIT_NONE) 7 #define OS_OPT_TIME_HMSM_NON_STRICT ((OS_OPT)DEF_BIT_04)
1
2
3
4
5
6OS_OPT_TIME_DLY
:dly为相对时间,就是从现在起延时多长时间,到时钟节拍总计数OSTickCtr = OSTickCtr
当前+dly时延时结束.OS_OPT_TIME_TIMEOUT
:跟OS_OPT_TIME_DLY
的作用情况一样.OS_OPT_TIME_MATCH
:dly为绝对时间,就是从系统开始运行(调用OSStart()
)时到节拍总计数OSTickCtr = dly
时延时结束.OS_OPT_TIME_PERIODIC
:周期性延时, 跟OS_OPT_TIME_DLY
的作用差不多,如果是长时间延时,该选项更精准一些.延时时间取值比较严格:
– 小时数hours:(0-99)
– 分钟数minutes:(0-59)
– 秒数seconds:(0-59)
– 毫秒数milliseconds:(0-999)
延时时间取值比较宽松.
– 小时数hours:(0-999)
– 分钟数minutes:(0-9999)
– 秒数seconds:(0-65535)
– 毫秒数milliseconds:(0-4294967295)
# 任务的设计要点
我们要对自己设计的嵌入式系统要了如指掌,任务的优先级信息,任务与中断的处理,任务的运行时间,逻辑,状态等都要知道,才能设计出好的系统,所以,在设计的时候需要根据需求制定框架.在设计之初就应该考虑任务运行的上下文环境,任务的执行时间的合理性.
μC/OS中程序运行的上下文包括:
- 中断服务函数.
- 普通任务.
- 空闲任务.
中断服务函数
中断服务函数是一种需要特别注意的上下文环境,它运行在非任务的执行环境下(一般为芯片的一种特殊运行模式(也被称作特权模式)),在这个上下文环境中不能使用挂起当前任务的操作,不允许调用任何会阻塞运行的API函数接口.另外需要注意的是,中断服务程序最好保持精简短小,快进快出,一般在中断服务函数中只做标记事件的发生,然后通知任务,让对应任务去执行相关处理,因为中断服务函数的优先级高于任何优先级的任务,如果中断处理时间过长,将会导致整个系统的任务无法正常运行.所以在设计的时候必须考虑中断的频率,中断的处理时间等重要因素,以便配合对应中断处理任务的工作.
μC/OS支持中断延迟发布,使得原本在中断中发布的信息变成任务级发布,这样子会使得中断服务函数的处理更加快速,屏蔽中断的时间更短,这样子能快速响应其他的中断,真正称得上实时操作系统.
任务
任务看似没有什么限制程序执行的因素,似乎所有的操作都可以执行.但是做为一个优先级明确的实时系统,如果一个任务中的程序出现了死循环操作(此处的死循环是指没有阻塞机制的任务循环体),那么比这个任务优先级低的任务都将无法执行,当然也包括了空闲任务,因为死循环的时候,任务不会主动让出CPU,低优先级的任务是不可能得到CPU的使用权的,而高优先级的任务就可以抢占CPU.这个情况在实时操作系统中是必须注意的一点,所以在任务中不允许出现死循环.如果一个任务只有就绪态而无阻塞态,势必会影响到其他低优先级任务的执行,所以在进行任务设计时,就应该保证任务在不活跃的时候,任务可以进入阻塞态以交出CPU使用权,这就需要我们自己明确知道什么情况下让任务进入阻塞态,保证低优先级任务可以正常运行.在实际设计中,一般会将紧急的处理事件的任务优先级设置得高一些.
空闲任务
空闲任务(idle 任务)是μC/OS系统中没有其他工作进行时自动进入的系统任务.因为处理器总是需要代码来执行,所以至少要有一个任务处于运行态.μC/OS为了保证这一点,当调用
OSInit()
函数进行系统初始化时,系统会自动创建一个空闲任务,空闲任务是一个非常短小的循环.用户可以通过空闲任务钩子方式,在空闲任务上钩入自己的功能函数.通常这个空闲任务钩子能够完成一些额外的特殊功能,例如系统运行状态的指示,系统省电模式等.空闲任务是唯一一个不允许出现阻塞情况的任务,因为μC/OS需要保证系统永远都有一个可运行的任务.对于空闲任务钩子上挂接的空闲钩子函数,它应该满足以下的条件:
- 永远不会挂起空闲任务.
- 不应该陷入死循环,需要留出部分时间用于统计系统的运行状态等.
任务的执行时间
任务的执行时间一般是指两个方面,一是任务从开始到结束的时间,二是任务的周期.在系统设计的时候这两个时间候我们都需要考虑,例如,对于事件A对应的服务任务Ta,系统要求的实时响应指标是10ms,而,Ta 的最大运行时间是1ms,那么10ms就是任务Ta的周期了,1ms则是任务的运行时间,简单来说任务Ta在10ms内完成对事件A的响应即可.此时,系统中还存在着以50ms为周期的另一任务Tb,它每次运行的最大时间长度是,100us.在这种情况下,即使把任务 Tb的优先级设置比Ta更高,对系统的实时性指标也没什么影响,因为即使在Ta的运行过程中,Tb抢占了Ta 的资源,等到Tb执行完毕,消耗的时间也只不过是100us,还是在事件A规定的响应时间内(10ms),Ta能够安全完成对事件A的响应.但是假如系统中还存在任务Tc,其运行时间为20ms,假如将Tc的优先级设置比Ta更高,那么在Ta运行的时候,突然间被Tc打断,等到Tc 执行完毕,那Ta已经错过对事件A(10ms)的响应了,这是不允许的.所以在我们设计的时候,必须考虑任务的时间,一般来说处理时间更短的任务优先级应设置更高一些.