少女祈祷中...

​ 在对操作系统的概念有一定的了解后,再进行嵌入式操作系统的学习。结合电子教材,和课上老师所讲内容,整理归纳出来的内容。

​ 在学习MicroCOS这门课时,一个比较好的点是,这门课偏于实操而不是概念,这是我在操作系统概念里没有体会到的。通过看书实验能看到很多具体的例子,而不是书上抽象的文字。

临界断代码

这一章比较简单。主要介绍临界段代码的相关知识:什么是临界段代码?如何实现临界段代码?等等

临界段代码的定义

临界段代码,又称临界区,是指必须完整连续运行,不可被中断的代码。访问内核数据结构的代码一般都不能被中断,所以是临界区。

像我们创建任务时,需要创建TCB,将TCB插入就绪队列,这就属于是临界段代码。

我们有两种方法保护临界区代码不被中断:

  • 关中断
  • 锁调度器

有相应的宏:

OS_CFG_ISR_POST_DEFFERRED_ED:OS配置ISR提交信号量延迟使能
PS:意思就是,如果在ISR中来要发送信号量,那么是否进行延迟发布,这个延处发布就是通过锁住调度器,将信号量缓存到ISR队列中去实现;也就是说en=1进行延迟,反之,就不延迟信号量,而是直接关闭中断。

  • 0,采用关中断保护临界区
  • 1,采用锁调度器保护临界区

OS_CRITICAL_ENTER():进入临界区前使用。
OS_CRITICAL_EXIT()/**OS_CRITICAL_EXIT_NO_SCHED()**:出临界区时使用

关中断

简单讲下关中断是怎么实现的。

我们可以另宏OS_CFG_ISR_POST_DEFERRED_EN=0,那么在进入临界区前,会关闭中断,出临界区时,会重新打开中断。

而宏**OS_CRITICAL_ENTER(),会调用另一个宏CPU_CRITICAL_ENTER(),最终调用宏CPU_SR_SAVE()**。所以直接解释CPU_SR_SAVE()即可:

CPU_SR_SAVE()使用汇编写的,会将当前中断的状态保留起来,然后关闭所有中断,将暂存的中断状态保留在调用函数的局部变量CPU_SR(被动态分配在任务栈上)中。

而OS_CRITICAL_EXIT()和OS_CRITICAL_EXIT_NO_SCHED(),同理都调用了另一个宏**CPU_CRITICAL_EXIT(),最终调用CPU_SR_RESTORE()**,顾名思义,就是根据CPU_SR,来恢复之前暂存的中断。

测量关中断的时间

使能CPU_CFG_INT_DIS_MEAS_EN=1即可

调度器上锁

最简单的,使能宏OS_CFG_ISR_POST_DEFERRED_EN=1。然后进入临界段前锁调度器,出临界段时调度器解锁。

OS_CRITIAL_ENTER(),递增全局变量OSSchedLockNestingCtr,如果该全局变量的值不为0,那么说明调度器上锁。

相反的OS_CRITICAL_EXIT(),则是递减全局变量OSSchedLockCtr,如果为0,会启动任务调度器。

类似的**OS_CRITICAL_EXIT_NO_SCHED()**,也会递减全局变量,但是为0时不会启动调度器。

测量锁调度器的时间

使能OS_CFG_SCHED_LOCK_TIME_MEAS_EN=1即可。

补充

很多宏都是在os.cfg.h中,下面瞅瞅

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
#ifndef OS_CFG_H
#define OS_CFG_H


/* ---------------------------- MISCELLANEOUS -------------------------- */
#define OS_CFG_APP_HOOKS_EN 1u /* Enable (1) or Disable (0) application specific hooks */
#define OS_CFG_ARG_CHK_EN 1u /* Enable (1) or Disable (0) argument checking */
#define OS_CFG_CALLED_FROM_ISR_CHK_EN 1u /* Enable (1) or Disable (0) check for called from ISR */
#define OS_CFG_DBG_EN 1u /* Enable (1) debug code/variables */
#define OS_CFG_ISR_POST_DEFERRED_EN 1u /* Enable (1) or Disable (0) Deferred ISR posts ͨ¹ýµ÷¶ÈÆ÷ÉÏËøÀ´·ÃÎÊÁÙ½ç¶Î*/
#define OS_CFG_OBJ_TYPE_CHK_EN 1u /* Enable (1) or Disable (0) object type checking */
#define OS_CFG_TS_EN 1u /* Enable (1) or Disable (0) time stamping */

#define OS_CFG_PEND_MULTI_EN 0u /* Enable (1) or Disable (0) code generation for multi-pend feature */

#define OS_CFG_PRIO_MAX 64u /* Defines the maximum number of task priorities (see OS_PRIO data type) */

#define OS_CFG_SCHED_LOCK_TIME_MEAS_EN 1u /* Include code to measure scheduler lock time */
#define OS_CFG_SCHED_ROUND_ROBIN_EN 1u /* Include code for Round-Robin scheduling */
#define OS_CFG_STK_SIZE_MIN 64u /* Minimum allowable task stack size */


/* ----------------------------- EVENT FLAGS --------------------------- */
#define OS_CFG_FLAG_EN 1u /* Enable (1) or Disable (0) code generation for EVENT FLAGS */
#define OS_CFG_FLAG_DEL_EN 1u /* Include code for OSFlagDel() */
#define OS_CFG_FLAG_MODE_CLR_EN 1u /* Include code for Wait on Clear EVENT FLAGS */
#define OS_CFG_FLAG_PEND_ABORT_EN 1u /* Include code for OSFlagPendAbort() */


/* -------------------------- MEMORY MANAGEMENT ------------------------ */
#define OS_CFG_MEM_EN 1u /* Enable (1) or Disable (0) code generation for MEMORY MANAGER */


/* --------------------- MUTUAL EXCLUSION SEMAPHORES ------------------- */
#define OS_CFG_MUTEX_EN 1u /* Enable (1) or Disable (0) code generation for MUTEX */
#define OS_CFG_MUTEX_DEL_EN 1u /* Include code for OSMutexDel() */
#define OS_CFG_MUTEX_PEND_ABORT_EN 1u /* Include code for OSMutexPendAbort() */


/* --------------------------- MESSAGE QUEUES -------------------------- */
#define OS_CFG_Q_EN 1u /* Enable (1) or Disable (0) code generation for QUEUES */
#define OS_CFG_Q_DEL_EN 1u /* Include code for OSQDel() */
#define OS_CFG_Q_FLUSH_EN 1u /* Include code for OSQFlush() */
#define OS_CFG_Q_PEND_ABORT_EN 1u /* Include code for OSQPendAbort() */


/* ----------------------------- SEMAPHORES ---------------------------- */
#define OS_CFG_SEM_EN 1u /* Enable (1) or Disable (0) code generation for SEMAPHORES */
#define OS_CFG_SEM_DEL_EN 1u /* Include code for OSSemDel() */
#define OS_CFG_SEM_PEND_ABORT_EN 1u /* Include code for OSSemPendAbort() */
#define OS_CFG_SEM_SET_EN 1u /* Include code for OSSemSet() */


/* -------------------------- TASK MANAGEMENT -------------------------- */
#define OS_CFG_STAT_TASK_EN 1u /* Enable (1) or Disable(0) the statistics task */
#define OS_CFG_STAT_TASK_STK_CHK_EN 1u /* Check task stacks from statistic task */

#define OS_CFG_TASK_CHANGE_PRIO_EN 1u /* Include code for OSTaskChangePrio() */
#define OS_CFG_TASK_DEL_EN 1u /* Include code for OSTaskDel() */
#define OS_CFG_TASK_Q_EN 1u /* Include code for OSTaskQXXXX() */
#define OS_CFG_TASK_Q_PEND_ABORT_EN 1u /* Include code for OSTaskQPendAbort() */
#define OS_CFG_TASK_PROFILE_EN 1u /* Include variables in OS_TCB for profiling */
#define OS_CFG_TASK_REG_TBL_SIZE 1u /* Number of task specific registers */
#define OS_CFG_TASK_SEM_PEND_ABORT_EN 1u /* Include code for OSTaskSemPendAbort() */
#define OS_CFG_TASK_SUSPEND_EN 1u /* Include code for OSTaskSuspend() and OSTaskResume() */


/* -------------------------- TIME MANAGEMENT -------------------------- */
#define OS_CFG_TIME_DLY_HMSM_EN 1u /* Include code for OSTimeDlyHMSM() */
#define OS_CFG_TIME_DLY_RESUME_EN 1u /* Include code for OSTimeDlyResume() */


/* ------------------- TASK LOCAL STORAGE MANAGEMENT ------------------- */
#define OS_CFG_TLS_TBL_SIZE 0u /* Include code for Task Local Storage (TLS) registers */


/* ------------------------- TIMER MANAGEMENT -------------------------- */
#define OS_CFG_TMR_EN 1u /* Enable (1) or Disable (0) code generation for TIMERS */
#define OS_CFG_TMR_DEL_EN 1u /* Enable (1) or Disable (0) code generation for OSTmrDel() */

#endif

任务管理

概述

在MicroCOS中,最小的调度单元不叫线程或者进程,而叫做任务,但可以理解为进程。任务是MicroCOS的CPU调度的最小单元。MicroCOS支持多任务,理论上支持无限的任务,但事实上被存储空间大小所限制。

任务的类型:

  • 无限循环型(endless loop):一个while1死循环,其中,必须要有将自身挂起的操作,不然优先级比他低的任务都无法运行(优先级不同时采用的优先级调度法)
  • 运行至完成型(run to end):跑一遍就结束了

一般都是endless loop类型的任务。

任务很像一个C函数,这是正常的,像Java中的线程的创建:

1
2
3
new Thread(() -> {
System.out.println(0x11);
}).start();

然后再看看任务的创建:

C函数创建:任务代码,任务的功能

这个图中有将自身挂起操作的列举。
image-20220526102822447
这是实际中写的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void led2_task(void *p_arg)
{
OS_ERR err_pend, err_post, err_dly;
CPU_TS ts;
p_arg = p_arg;
while(1)
{
//LED2=!LED2;
//OSTimeDlyHMSM(0,0,0,400,OS_OPT_TIME_HMSM_STRICT,&err); //ÑÓdelay 400ms
OSSemPend(&mySem, 0, OS_OPT_PEND_BLOCKING, &ts, &err_pend);
switch(err_pend) {
case OS_ERR_NONE:
LED2 = !LED2;
OSSemPost(&mySem, OS_OPT_POST_ALL, &err_post);
break;
default:
break;
}
OSTimeDlyHMSM(0,0,0,200,OS_OPT_TIME_PERIODIC,&err_dly);
}
}

任务的创建:把任务由休眠态->就绪态,也就是从外存(code区)加载内存(ram区)

这是创建任务的函数,看看里面需要传入的参数。

image-20220526103032191

这是实际创建任务的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
OSTaskCreate((OS_TCB 	* )&Led2TaskTCB,		
(CPU_CHAR * )"led2 task",
(OS_TASK_PTR )led2_task,
(void * )0,
(OS_PRIO )LED2_TASK_PRIO,
(CPU_STK * )&LED2_TASK_STK[0],
(CPU_STK_SIZE)LED2_STK_SIZE/10,
(CPU_STK_SIZE)LED2_STK_SIZE,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void * )0,
(OS_OPT )OS_OPT_TASK_STK_CHK|OS_OPT_TASK_STK_CLR,
(OS_ERR * )&err);

提一下可重入函数

image-20220526102401491

可重入就是能被中断的意思,像上面提到的**strtok()**函数,假设其是可重入的,如果调用一次后发生调度(时间片用完or更高优先级的任务来了),然后下一个任务也调用这个函数,那么第一次调度记住的指针就会被覆盖,出现了BUG,所以该函数是不可重入的。

接着聊任务,任务创建完毕后,它的TCB会被加入到任务就绪表中去,然后启动任务调度器,决定是否进行任务的切换(根据优先级)。

任务优先级的分配

主要讨论任务优先级该怎么分配。有一个单调执行率调度法(RMS,Rate Monitonic Scheduling)。其核心思想是:让执行频率最高的任务得到最高的优先级:其假设:

image-20220526104007271

栈空间大小确定

任务栈空间大小的确定取决一下因素:

  • CPU中的寄存器个数(包括FPU浮点运算单元)
  • 函数嵌套层数
  • ISR嵌套层数

当IRQ来时,保存当前任务的现场,然后执行对应的ISR代码(使用的堆栈还是任务堆栈)。如果ISR嵌套多层的话,那么会发生堆栈溢出,直接寄。

任务栈溢出检测

使用MPU or MMU

这个取决于CPU的种类,有的CPU中集成了MPU(存储保护单元)和MMU(存储管理单元),就能通过硬件来进行存储保护,也能防止堆栈溢出。

堆栈指针溢出检测寄存器

有的CPU能够检测堆栈溢出。具体就是,有的CPU内部有一个堆栈指针溢出检测寄存器,其中有个值,当SP(堆栈指针)高于或低于(跟堆栈生长的方向有关)该寄存器的值时,就会出异常,报警告或者直接停止程序的运行。

image-20220526105949631

image-20220526110343823

image-20220526110700820

当发生任务切换的时候,保护现场后,需要恢复下一个任务的现场,CPU的堆栈溢出检测寄存器使用的.stackLimitPtr也需要修改:

  • 首先修改.stackLimitPtr指向NULL,也就是0,一个永远不可能溢出的位置
  • 然后改变CPU堆栈指针寄存器的值(SP)
  • 最后修改硬件使用的指针.stackLimitPtr,使其等于当前任务TCB中的stackLimitPtr的值

如果不这样做,先修改SP,会产生异常。所以先使得硬件使用的stackLimitPtr指向一个无效的,不可能溢出的值

软件检测方法

在硬件不提供堆栈溢出的检测时,可以使用软件的方法设置一个stackLimitPtr,使得SP不高于其值,在一个叫介入函数的地方设置

计算空闲堆栈空间的数量

image-20220526112027946

任务管理函数

image-20220526112133334

任务管理的内部原理

任务状态

两张状态图

image-20220526112824466

image-20220526112848088

TCB

图太长了,直接copy代码过来

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
struct os_tcb {
CPU_STK *StkPtr; /* Pointer to current top of stack */

void *ExtPtr; /* Pointer to user definable data for TCB extension */

CPU_STK *StkLimitPtr; /* Pointer used to set stack 'watermark' limit */

OS_TCB *NextPtr; /* Pointer to next TCB in the TCB list */
OS_TCB *PrevPtr; /* Pointer to previous TCB in the TCB list */

OS_TCB *TickNextPtr;
OS_TCB *TickPrevPtr;

OS_TICK_SPOKE *TickSpokePtr; /* Pointer to tick spoke if task is in the tick list */

CPU_CHAR *NamePtr; /* Pointer to task name */

CPU_STK *StkBasePtr; /* Pointer to base address of stack */

#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS TLS_Tbl[OS_CFG_TLS_TBL_SIZE];
#endif

OS_TASK_PTR TaskEntryAddr; /* Pointer to task entry point address */
void *TaskEntryArg; /* Argument passed to task when it was created */

OS_PEND_DATA *PendDataTblPtr; /* Pointer to list containing objects pended on */
OS_STATE PendOn; /* Indicates what task is pending on */
OS_STATUS PendStatus; /* Pend status */

OS_STATE TaskState; /* See OS_TASK_STATE_xxx */
OS_PRIO Prio; /* Task priority (0 == highest) */
CPU_STK_SIZE StkSize; /* Size of task stack (in number of stack elements) */
OS_OPT Opt; /* Task options as passed by OSTaskCreate() */

OS_OBJ_QTY PendDataTblEntries; /* Size of array of objects to pend on */

CPU_TS TS; /* Timestamp */

OS_SEM_CTR SemCtr; /* Task specific semaphore counter */

/* DELAY / TIMEOUT */
OS_TICK TickCtrPrev; /* Previous time when task was ready */
OS_TICK TickCtrMatch; /* Absolute time when task is going to be ready */
OS_TICK TickRemain; /* Number of ticks remaining for a match (updated at ... */
/* ... run-time by OS_StatTask() */
OS_TICK TimeQuanta;
OS_TICK TimeQuantaCtr;

#if OS_MSG_EN > 0u
void *MsgPtr; /* Message received */
OS_MSG_SIZE MsgSize;
#endif

#if OS_CFG_TASK_Q_EN > 0u
OS_MSG_Q MsgQ; /* Message queue associated with task */
#if OS_CFG_TASK_PROFILE_EN > 0u
CPU_TS MsgQPendTime; /* Time it took for signal to be received */
CPU_TS MsgQPendTimeMax; /* Max amount of time it took for signal to be received */
#endif
#endif

#if OS_CFG_TASK_REG_TBL_SIZE > 0u
OS_REG RegTbl[OS_CFG_TASK_REG_TBL_SIZE]; /* Task specific registers */
#endif

#if OS_CFG_FLAG_EN > 0u
OS_FLAGS FlagsPend; /* Event flag(s) to wait on */
OS_FLAGS FlagsRdy; /* Event flags that made task ready to run */
OS_OPT FlagsOpt; /* Options (See OS_OPT_FLAG_xxx) */
#endif

#if OS_CFG_TASK_SUSPEND_EN > 0u
OS_NESTING_CTR SuspendCtr; /* Nesting counter for OSTaskSuspend() */
#endif

#if OS_CFG_TASK_PROFILE_EN > 0u
OS_CPU_USAGE CPUUsage; /* CPU Usage of task (0.00-100.00%) */
OS_CPU_USAGE CPUUsageMax; /* CPU Usage of task (0.00-100.00%) - Peak */
OS_CTX_SW_CTR CtxSwCtr; /* Number of time the task was switched in */
CPU_TS CyclesDelta; /* value of OS_TS_GET() - .CyclesStart */
CPU_TS CyclesStart; /* Snapshot of cycle counter at start of task resumption */
OS_CYCLES CyclesTotal; /* Total number of # of cycles the task has been running */
OS_CYCLES CyclesTotalPrev; /* Snapshot of previous # of cycles */

CPU_TS SemPendTime; /* Time it took for signal to be received */
CPU_TS SemPendTimeMax; /* Max amount of time it took for signal to be received */
#endif

#if OS_CFG_STAT_TASK_STK_CHK_EN > 0u
CPU_STK_SIZE StkUsed; /* Number of stack elements used from the stack */
CPU_STK_SIZE StkFree; /* Number of stack elements free on the stack */
#endif

#ifdef CPU_CFG_INT_DIS_MEAS_EN
CPU_TS IntDisTimeMax; /* Maximum interrupt disable time */
#endif
#if OS_CFG_SCHED_LOCK_TIME_MEAS_EN > 0u
CPU_TS SchedLockTimeMax; /* Maximum scheduler lock time */
#endif

#if OS_CFG_DBG_EN > 0u
OS_TCB *DbgPrevPtr;
OS_TCB *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
};

具体含义见注释

系统内部任务

空闲任务

又称闲逛任务,闲逛进程,IDLE,其任务代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void  OS_IdleTask (void  *p_arg)
{
CPU_SR_ALLOC();



p_arg = p_arg; /* Prevent compiler warning for not using 'p_arg' */

while (DEF_ON) {
CPU_CRITICAL_ENTER();
OSIdleTaskCtr++;
#if OS_CFG_STAT_TASK_EN > 0u
OSStatTaskCtr++;
#endif
CPU_CRITICAL_EXIT();

OSIdleTaskHook(); /* Call user definable HOOK */
}
}

这是个真正的无限循环,不会自身挂起,其存在的意义就是,当没有可以运行的任务时,就运行闲逛进程。闲逛进程的优先级最低,

当闲逛任务运行时,就会使OSIdleTaskCtr(这是i32类型)自增,该值增长越快,代表CPU处理我们写的任务的时候处理的越快;OSStatTaskCtr则是测量CPU利用率的。

OSIdleTaskHook是一个函数,可以让IDLE在空闲时做一些额外的任务,如将电源设置为低功耗模式

1
2
3
4
5
6
7
8
void  OSIdleTaskHook (void)
{
#if OS_CFG_APP_HOOKS_EN > 0u
if (OS_AppIdleTaskHookPtr != (OS_APP_HOOK_VOID)0) {
(*OS_AppIdleTaskHookPtr)();
}
#endif
}

时钟节拍任务

时钟节拍任务的目的是什么:为操作系统提供一个周期性的时钟源,用于跟踪任务等待,任务延时,任务超时等情况。一般将其优先级设置成比我们最高优先级的任务低一点,下面看OS_TickTask的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
OSTaskCreate((OS_TCB     *)&OSTickTaskTCB,
(CPU_CHAR *)((void *)"uC/OS-III Tick Task"),
(OS_TASK_PTR )OS_TickTask,
(void *)0,
(OS_PRIO )OSCfg_TickTaskPrio,
(CPU_STK *)OSCfg_TickTaskStkBasePtr,
(CPU_STK_SIZE)OSCfg_TickTaskStkLimit,
(CPU_STK_SIZE)OSCfg_TickTaskStkSize,
(OS_MSG_QTY )0u,
(OS_TICK )0u,
(void *)0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS),
(OS_ERR *)p_err);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void  OS_TickTask (void  *p_arg)
{
OS_ERR err;
CPU_TS ts;


p_arg = p_arg; /* Prevent compiler warning */

while (DEF_ON) {
(void)OSTaskSemPend((OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(CPU_TS *)&ts,
(OS_ERR *)&err); /* Wait for signal from tick interrupt */
if (err == OS_ERR_NONE) {
if (OSRunning == OS_STATE_OS_RUNNING) {
OS_TickListUpdate(); /* Update all tasks waiting for time */
}
}
}
}

我们再去看优先级:

可以看到优先级设置为1,无符号数,在任务里面是最高优先级了。

然后再关注任务代码,其中,OS_TickTask是个endless loop,其在不停的等待一个信号量,这个信号由时钟节拍的ISR发出,如果没有错误,那么就更新所有在等待的任务,也就是OS_TickListUpdate()。

下面看看时钟节拍的ISR,如果允许中断,那么当接收到时钟节拍中断(硬件发出)时,会进入到时钟节拍的ISR中,然后再时钟节拍的ISR中,会调用一个OS_TimeTick函数

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
void  OSTimeTick (void)
{
OS_ERR err;
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
CPU_TS ts;
#endif


OSTimeTickHook(); /* Call user definable hook */

#if OS_CFG_ISR_POST_DEFERRED_EN > 0u /* 关调度器 */

ts = OS_TS_GET(); /* Get timestamp */
OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK, /* Post to ISR queue */
(void *)&OSRdyList[OSPrioCur],
(void *) 0,
(OS_MSG_SIZE) 0u,
(OS_FLAGS ) 0u,
(OS_OPT ) 0u,
(CPU_TS ) ts,
(OS_ERR *)&err);

#else /* 关中断 */

(void)OSTaskSemPost((OS_TCB *)&OSTickTaskTCB, /* Signal tick task */
(OS_OPT ) OS_OPT_POST_NONE,
(OS_ERR *)&err);


#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u /* 开启时间片轮转 */
OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);
#endif

#if OS_CFG_TMR_EN > 0u /*定时器启用*/
OSTmrUpdateCtr--;
if (OSTmrUpdateCtr == (OS_CTR)0u) {
OSTmrUpdateCtr = OSTmrUpdateCnt;
OSTaskSemPost((OS_TCB *)&OSTmrTaskTCB, /* Signal timer task */
(OS_OPT ) OS_OPT_POST_NONE,
(OS_ERR *)&err);
}
#endif

#endif
}

可以看到,刚开始就可以调用一个Hook函数,也就是钩子函数(这是用户自己编写的,会在特定的事件到来之际,被调用执行),然后就是向一些任务发送信号量了。

分析一下上面代码的结构,有宏看不懂

下面搜索了下宏

  • OS_CFG_ISR_POST_DEFERRED_EN:为0时关中断,为1时锁定调度器。
  • OS_CFG_SCHED_ROUND_ROBIN_EN:为1时可以使用时间片轮转
  • OS_CFG_TMR_EN:为1时启用定时器

于是可以分析代码了:

  • 对OS_CFG_ISR_POST_DEFERRED_EN的判断:
    • 若是0:关中断时,唤醒时钟节拍任务
    • 若是1:锁调度器时,把信号量和信号量的接收者的指针暂存到一个队列中去
  • 对OS_CFG_SCHED_ROUND_ROBIN_EN的判断:
    • 1:进行时间片轮转
  • 对 OS_CFG_TMR_EN的判断:
    • 1:唤醒定时任务

统计任务

进行一些统计工作,如计算CPU的利用率和任务堆栈的利用率。要想使用统计任务,需要使能一个配置。

定时任务

定时任务也是可选的,需要使能配置才行。定时任务的目的是:向用户提供定时服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
OSTaskCreate((OS_TCB     *)&OSTmrTaskTCB,
(CPU_CHAR *)((void *)"uC/OS-III Timer Task"),
(OS_TASK_PTR )OS_TmrTask,
(void *)0,
(OS_PRIO )OSCfg_TmrTaskPrio,
(CPU_STK *)OSCfg_TmrTaskStkBasePtr,
(CPU_STK_SIZE)OSCfg_TmrTaskStkLimit,
(CPU_STK_SIZE)OSCfg_TmrTaskStkSize,
(OS_MSG_QTY )0,
(OS_TICK )0,
(void *)0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR | OS_OPT_TASK_NO_TLS),
(OS_ERR *)p_err);
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
void  OS_TmrTask (void  *p_arg)
{
CPU_BOOLEAN done;
OS_ERR err;
OS_TMR_CALLBACK_PTR p_fnct;
OS_TMR_SPOKE *p_spoke;
OS_TMR *p_tmr;
OS_TMR *p_tmr_next;
OS_TMR_SPOKE_IX spoke;
CPU_TS ts;
CPU_TS ts_start;
CPU_TS ts_end;



p_arg = p_arg; /* Not using 'p_arg', prevent compiler warning */
while (DEF_ON) {
(void)OSTaskSemPend((OS_TICK )0, /* Wait for signal indicating time to update tmrs */
(OS_OPT )OS_OPT_PEND_BLOCKING,
(CPU_TS *)&ts,
(OS_ERR *)&err);

OSSchedLock(&err);
ts_start = OS_TS_GET();
OSTmrTickCtr++; /* Increment the current time */
spoke = (OS_TMR_SPOKE_IX)(OSTmrTickCtr % OSCfg_TmrWheelSize);
p_spoke = &OSCfg_TmrWheel[spoke];
p_tmr = p_spoke->FirstPtr;
done = DEF_FALSE;
while (done == DEF_FALSE) {
if (p_tmr != (OS_TMR *)0) {
p_tmr_next = (OS_TMR *)p_tmr->NextPtr; /* Point to next tmr to update because current ... */
/* ... timer could get unlinked from the wheel. */
if (OSTmrTickCtr == p_tmr->Match) { /* Process each timer that expires */
OS_TmrUnlink(p_tmr); /* Remove from current wheel spoke */
if (p_tmr->Opt == OS_OPT_TMR_PERIODIC) {
OS_TmrLink(p_tmr,
OS_OPT_LINK_PERIODIC); /* Recalculate new position of timer in wheel */
} else {
p_tmr->State = OS_TMR_STATE_COMPLETED; /* Indicate that the timer has completed */
}
p_fnct = p_tmr->CallbackPtr; /* Execute callback function if available */
if (p_fnct != (OS_TMR_CALLBACK_PTR)0) {
(*p_fnct)((void *)p_tmr,
p_tmr->CallbackPtrArg);
}
p_tmr = p_tmr_next; /* See if next timer matches */
} else {
done = DEF_TRUE;
}
} else {
done = DEF_TRUE;
}
}
ts_end = OS_TS_GET() - ts_start; /* Measure execution time of timer task */
OSSchedUnlock(&err);
if (OSTmrTaskTimeMax < ts_end) {
OSTmrTaskTimeMax = ts_end;
}
}
}

定时任务,也称定时器,是一个递减的计数器,递减到0时,就会调用用户的回调函数。

我们看代码,发现定时任务的任务函数里面,开始也会的等待一个信号量,他所等待的信号量和时钟节拍任务是一样的,但是信号量的频率好像略慢。

中断服务管理任务

中断管理任务,OS_IntQTask(),也是一个可选项(使能配置),其目的是:容易得知,进入临界段时,可以选择关中断或者锁调度器,这里就是后者。当锁调度器时,OS会创建一个OS_IntQTask任务,优先级最高,这玩意后面会处理一个队列。比如在ISR中进入临界段,锁调度器,然后使用post发送信号量,这个时候信号量不可达(因为要禁止调度,发信号量可能会唤醒别的任务),然后将信号量sem及目标加入一个特殊的队列中去,当所有的ISR结束后,会启动OS_IntQTask(),将信号量按照顺序发给对应的任务。

为什么要有这么一步操作呢?因为要求在中断中的时间要尽可能短,如果在中断中发信号量唤醒任务,那么需要做以下操作:

  • 将任务从等待列表中移除
  • 将任务加入就序列表,进行等待
  • 还有其它操作

这就非常麻烦了,与我们要求中断时间尽可能短相悖。

所以,OS_IntQTask()比较重要,所以其优先级为最高任务优先级0,只低于中断。下面看源码:

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
void  OS_IntQTask (void  *p_arg)
{
CPU_BOOLEAN done;
CPU_TS ts_start;
CPU_TS ts_end;
CPU_SR_ALLOC();



p_arg = p_arg; /* Not using 'p_arg', prevent compiler warning */
while (DEF_ON) {
done = DEF_FALSE;
while (done == DEF_FALSE) {
CPU_CRITICAL_ENTER();
if (OSIntQNbrEntries == (OS_OBJ_QTY)0u) {
OSRdyList[0].NbrEntries = (OS_OBJ_QTY)0u; /* Remove from ready list */
OSRdyList[0].HeadPtr = (OS_TCB *)0;
OSRdyList[0].TailPtr = (OS_TCB *)0;
OS_PrioRemove(0u); /* Remove from the priority table */
CPU_CRITICAL_EXIT();
OSSched();
done = DEF_TRUE; /* No more entries in the queue, we are done */
} else {
CPU_CRITICAL_EXIT();
ts_start = OS_TS_GET();
OS_IntQRePost();
ts_end = OS_TS_GET() - ts_start; /* Measure execution time of tick task */
if (OSIntQTaskTimeMax < ts_end) {
OSIntQTaskTimeMax = ts_end;
}
CPU_CRITICAL_ENTER();
OSIntQOutPtr = OSIntQOutPtr->NextPtr; /* Point to next item in the ISR queue */
OSIntQNbrEntries--;
CPU_CRITICAL_EXIT();
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
OSTaskCreate((OS_TCB     *)&OSIntQTaskTCB,
(CPU_CHAR *)((void *)"uC/OS-III ISR Queue Task"),
(OS_TASK_PTR )OS_IntQTask,
(void *)0,
(OS_PRIO )0u, /* This task is ALWAYS at priority '0' (i.e. highest) */
(CPU_STK *)OSCfg_IntQTaskStkBasePtr,
(CPU_STK_SIZE)OSCfg_IntQTaskStkLimit,
(CPU_STK_SIZE)OSCfg_IntQTaskStkSize,
(OS_MSG_QTY )0u,
(OS_TICK )0u,
(void *)0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *)p_err);

额外补充

在阅读源码时,有个很基本的位置:关于信号量的,不能以下看懂,查书中信号量中的章节也无。遂Google一下:OSTaskSemPend

任务信号量,查到了两张很好的图:

其功能分别是:任务信号量的发布,等待,终止。

任务就绪表

所有已经等待就绪的任务,其TCB都会放到任务就绪表中去,任务就绪表类似于OS中的就绪队列。

任务就虚表,ready list,包含两个部分:

  • 就绪优先级位映射表(OSPrioTbl[]):里面存着就绪任务的优先级排列,是一个1×(MaxProi-1)的位向量
  • 就绪任务列表(OSReyList[]):是一个1×(MaxProi-1)的指针数组,指针指向的是对应优先级的就绪任务。

就绪优先级位映射表

这一节看着讲优先级,但是优先级位映射表更多点。我们看看优先级位映射表OSPrioTbl[]长什么样子。

上图的OSPrioTbl的元素的位宽是8位,也就是一个字节。每一位代表的都是对应优先级下是否有任务在就绪队列中。

位宽取决于一个叫CPU_DATA的数据元素,在实验代码中我找到了OSPrioTbl的定义:

1
2
3
CPU_DATA   OSPrioTbl[OS_PRIO_TBL_SIZE];                     /* Declare the array local to this file to allow for  ... */
/* ... optimization. In other words, this allows the ... */
/* ... table to be located in fast memory */

还有数据位宽为16为,32为的OSPrioTbl,如下:

是8位32位还是64位取决于CPU的类型。

介绍完优先级位映射表,再看看在其之上有哪些操作:

OS_PrioGetHighest()的源码:

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
/*
************************************************************************************************************************
* GET HIGHEST PRIORITY TASK WAITING
*
* Description: This function is called by other uC/OS-III services to determine the highest priority task
* waiting on the event.
*
* Arguments : none
*
* Returns : The priority of the Highest Priority Task (HPT) waiting for the event
*
* Note(s) : 1) This function is INTERNAL to uC/OS-III and your application MUST NOT call it.
************************************************************************************************************************
*/

OS_PRIO OS_PrioGetHighest (void)
{
CPU_DATA *p_tbl;
OS_PRIO prio;


prio = (OS_PRIO)0;
p_tbl = &OSPrioTbl[0];
while (*p_tbl == (CPU_DATA)0) { /* Search the bitmap table for the highest priority */
prio += DEF_INT_CPU_NBR_BITS; /* Compute the step of each CPU_DATA entry */
p_tbl++;
}
prio += (OS_PRIO)CPU_CntLeadZeros(*p_tbl); /* Find the position of the first bit set at the entry */
return (prio);
}

上面的代码分析,首先找到第一个不为0的OSPrioTbl元素,因为IDLE的存在,至少有一个是不为0的,所以总有一个最高优先级;然后,找到对应索引下的tbl,找前导零即可:

前导零的计算和CPU有关,可能和硬件有关,所以就不看细节了。

就绪任务列表

首先看它的数据结构。

1
2
3
4
5
6
7
typedef  struct  os_rdy_list          OS_RDY_LIST;

struct os_rdy_list {
OS_TCB *HeadPtr; /* Pointer to task that will run at selected priority */
OS_TCB *TailPtr; /* Pointer to last task at selected priority */
OS_OBJ_QTY NbrEntries; /* Number of entries at selected priority */
};

Entries表示的是对应优先级下有几个就绪的任务。

下面看看基于os_rdy_list的一些操作:

首先得到一个空的任务就序列表:

然后调用OSInit()进行初始化:

优先级从高到低依次是:

  • 中断服务管理任务
  • 时钟节拍任务
  • 定时器任务
  • 统计任务
  • 空闲任务

向任务就绪列表中添加任务的过程如下:

任务调度

这一章的主要任务是看调度器的实现代码。

可剥夺型调度

当来一个中断时,任务先将自身挂起,然后进入到相应中断的ISR中,最后退出ISR,并调用相关的系统服务(?猜测是调度程序),最后运行优先级最高的任务。

其过程如下:

分析下上面这张图:
首先一个low priority的任务在跑,然后这时来了一个外设中断(假设是以太网适配器在接收数据帧,产生了一个中断)。这时进入到外设的ISR中,然后ISR完成相应的外设服务(?maybe响应一下,然后可以准备接收数据帧了),最后ISR向一个高优先级的服务该外设的任务发送信号量或者消息,唤醒任务,使其进入就绪态。然后退出ISR,调用系统服务(对应图中的(5),应该是发生了中断调度),然后服务外设的任务上处理机运行。最后外设服务任务完成后,再次调用系统服务(再次调度,任务调度,对应如中(9)),外设服务任务将自身挂起,等待下次为该外设服务。然后处理机回到了我们最开始的低优先级任务。

上面说的是直接发布,如果是延迟发布,略有不同。下面分析延时发布:

延时发布是指在ISR总中通过关调度器的方式保护临界区。通过这种方式,在ISR中,我们发布信号量就是延迟发布,也就是把信号量缓存到一个ISR Queue中去,并且使得ISR hander task(中断服务任务)就绪,然后等到退出ISR后,再调用系统服务(对应(2),发生中断调度),然后由于ISR hander task优先级最高,所以他上处理机运行。然后再对被延迟的信号量进行发布并删除(注意这时是在任务态进行的,对应(5)),当ISR hander task运行完后,再调用系统服务进行调度。

调度点

这一节列举了调度服务会发生的场合,做个简单的归纳

  • 任务发送信号量/消息
  • 延时函数
  • 任务等待信号量
  • 任务取消等待
  • 创建任务
  • 删除任务
  • 删除内核对象
  • 任务改变自身或其它的优先级
  • 任务自己主动挂起
  • 任务解除挂起
  • 退出所有的ISR Nesting
  • 给调度器解锁
  • 任务放弃时间片
  • 用户通过手动调用调度器

具体API查书。

时间片轮转调度

  1. 目的

时间片轮转调度,当同一优先级的任务有多个时,允许这几个任务一次运行一段时间(也就是一个时间片),然后再轮到下一个。如果任务的时间片没用完,但是任务已经完成,那么可以调用yield来放弃剩余的时间片。

  1. 条件

时间片轮转需要多个任务优先级相同时才发生。同时,需要通过配置一个宏来使能时间片轮转。

调度的实现细节

调度属于一种系统服务。

通过两个函数实现,函数的使用情况分别是任务级和ISR中。

OSSched()

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
void  OSSched (void)
{
CPU_SR_ALLOC();



if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* ISRs still nested? */
return; /* Yes ... only schedule when no nested ISRs */
}

if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Scheduler locked? */
return; /* Yes */
}

CPU_INT_DIS();
OSPrioHighRdy = OS_PrioGetHighest(); /* Find the highest priority ready */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr;
if (OSTCBHighRdyPtr == OSTCBCurPtr) { /* Current task is still highest priority task? */
CPU_INT_EN(); /* Yes ... no need to context switch */
return;
}

#if OS_CFG_TASK_PROFILE_EN > 0u
OSTCBHighRdyPtr->CtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSTaskCtxSwCtr++; /* Increment context switch counter */

#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS_TaskSw();
#endif

OS_TASK_SW(); /* Perform a task level context switch */
CPU_INT_EN();
}

教材上的截图:

总的来说差不多:

  • 如果OSSched发生在中断或其嵌套中,return
  • 如果调度器被锁,return
  • 如果当前任务不是优先级最高的任务,切换

OSIntExit()

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
void  OSIntExit (void)
{
CPU_SR_ALLOC();



if (OSRunning != OS_STATE_OS_RUNNING) { /* Has the OS started? */
return; /* No */
}

CPU_INT_DIS();
if (OSIntNestingCtr == (OS_NESTING_CTR)0) { /* Prevent OSIntNestingCtr from wrapping */
CPU_INT_EN();
return;
}
OSIntNestingCtr--;
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* ISRs still nested? */
CPU_INT_EN(); /* Yes */
return;
}

if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Scheduler still locked? */
CPU_INT_EN(); /* Yes */
return;
}

OSPrioHighRdy = OS_PrioGetHighest(); /* Find highest priority */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr; /* Get highest priority task ready-to-run */
if (OSTCBHighRdyPtr == OSTCBCurPtr) { /* Current task still the highest priority? */
CPU_INT_EN(); /* Yes */
return;
}

#if OS_CFG_TASK_PROFILE_EN > 0u
OSTCBHighRdyPtr->CtxSwCtr++; /* Inc. # of context switches for this new task */
#endif
OSTaskCtxSwCtr++; /* Keep track of the total number of ctx switches */

#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS_TaskSw();
#endif

OSIntCtxSw(); /* Perform interrupt level ctx switch */
CPU_INT_EN();
}

  • 若中断嵌套为0,表示在任务态,直接return
  • 若中断嵌套大于1,直接return
  • 如果当前任务不是优先级最高的任务,切换

OSIntSched发生在最后一层中断嵌套,从ISR恢复到任务态的过程。

OSSchedRoundRobin()

当当前任务的时间片用完,并且同一优先级下有多个任务时,运行时间片轮转调度。


任务切换

任务切换(context switch,也就是CPU运行环境切换)。

任务切换会带来系统开销,上下文切换,需要保存CPU当前的运行环境,也就是各个寄存器的值。每个寄存器都是32bit位宽的存储器。有三个特殊的,如下:

  • R14_1:TSP,task stack pointer
  • R14_2:ISP,ISR stack pointer
  • R15:PC
  • SR:status register

这一章的概念中提及了中断堆栈和任务堆栈,其中ISR就是指向中断堆栈的栈顶的。当CPU发生中断时,会自动切换到中断堆栈中去。另外,中断堆栈和任务堆栈是可以互相访问的。

一个就绪的任务看上去就好像刚发生中断一样,其任务堆栈的栈顶看着全部都是寄存器(就算是刚创建的,进入就绪态的任务,也是这样的,是通过软件来进行堆栈的初始化的。)

PC和SR是在发生中断时,是自动(硬件)保存的,也就是最先压入寄存器的,而另外几个都是软件方式压入的,而R14(SP,stack pointer)则不同,他不用压入堆栈,而是直接保存到TCB中去

下面讨论任务级的切换和中断级的切换的具体细节。

  • 任务及切换:OSCtxSw()
  • 中断级切换:OSIntCtxSw()

OSCtxSw()

通过汇编实现:

1
2
3
4
5
6
7
8
9
10
11
12
;********************************************************************************************************
; PERFORM A CONTEXT SWITCH (From task level) - OSCtxSw()
;
; Note(s) : 1) OSCtxSw() is called when OS wants to perform a task context switch. This function
; triggers the PendSV exception which is where the real work is done.
;********************************************************************************************************

OSCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR

下面看看当发生任务切换时的过程。

首先是当操作系统知道有一个优先级更高的任务在就绪队列,然后这是这两个任务及CPU的状态:

然后进行任务切换后的图:

可以看到,在保存第一个任务的现场时,首先讲SR和PC压入任务堆栈,然后再压入R0~R13,最后将TCB中的.StkPtr指向R13,也就是我们任务堆栈的栈顶,这时任务1的上下文保护结束。

然后可以开始恢复高优先级的任务2了,首先根据TCB中的StkPtr,找到任务堆栈中的栈顶(因为任务堆栈是RAM中的一块随机的区域,只能通过TCB,才能知道堆栈的相关信息,并进行堆栈的恢复),然后开始弹出寄存器,进行恢复现场。

OSIntCtxSw()

1
2
3
4
5
6
7
8
9
10
11
12
13
;********************************************************************************************************
; PERFORM A CONTEXT SWITCH (From interrupt level) - OSIntCtxSw()
;
; Note(s) : 1) OSIntCtxSw() is called by OSIntExit() when it determines a context switch is needed as
; the result of an interrupt. This function simply triggers a PendSV exception which will
; be handled when there are no more interrupts active and interrupts are enabled.
;********************************************************************************************************

OSIntCtxSw
LDR R0, =NVIC_INT_CTRL ; Trigger the PendSV exception (causes context switch)
LDR R1, =NVIC_PENDSVSET
STR R1, [R0]
BX LR

当OSIntExit()(按字面意思是OS中断退出,确实如此,但我们翻译为中断调度器),确定有一个更高优先级的任务就绪时,就会调用OSCtxSw()(os context switch)

下面看过程图:

由于低优先级任务在进入ISR前,已经保存了各个寄存器的状态,所以本次上下文切换,无需保存低优先级任务的现场,只需要恢复高优先级的现场即可。

中断管理

这一章讲中断,中断是一种硬件机制,对比之前无OS时的轮询,中断这种机制能够大大增加系统的实时性。当来一个中断时,系统需要响应这个中断,具体就是先保存当前任务的现场,然后进入到一个特殊的函数中去,响应中断,也就是所谓的ISR,如果还需进行一些额外的任务,那么就需要在退出ISR前,唤醒一个较高优先级的服务任务,然后在任务中去进行操作,这样能够减少中断的复杂性,把复杂的任务留给让你无。

中断的流程:

中断有几个性能指标:

  • 中断响应时间:中断被识别到CPU到开始执行ISR中的代码
  • 中断恢复时间:ISR结束时,到开始下一个任务之间的gap(主要取决于系统服务,也就是调度器的执行效率),下一个任务可以是被中断的任务,也可以是被调度器选择的优先级更高的任务
  • 中断等待时间:从进入ISR开始,到再次执行任务为止。

如上图,很多都是外设产生的中断,这个时候,ISR负责响应中断,和唤醒响应的外设服务任务。

并且,真正首先接收到硬件中断请求的是“中断控制器”,中断控制器负责接收不同优先级的中断请求,并将优先级最高的中断请求输出出去。

然后就是中断开关了,若用户禁止中断,那么CPU就接收不到中断了。

典型的ISR

由于ISR在开发板的Rom中,所以我们看不到源码,这里截取书上的伪代码分析:

进入中断前,调用了OSIntEnter,先进行一些健壮性的判断,然后再自增OSIntNestingCtr,嵌套层数加一。可以看到,最多只支持250层的嵌套。

1
2
3
4
5
6
7
8
9
10
11
12
void  OSIntEnter (void)
{
if (OSRunning != OS_STATE_OS_RUNNING) { /* Is OS running? */
return; /* No */
}

if (OSIntNestingCtr >= (OS_NESTING_CTR)250u) { /* Have we nested past 250 levels? */
return; /* Yes */
}

OSIntNestingCtr++; /* Increment ISR nesting level */
}

而出中断前,也会调用对应的OSIntExit,也就是我们之前看过的“调度的实现细节”中的中断调度,源码如下:

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
void  OSIntExit (void)
{
CPU_SR_ALLOC();



if (OSRunning != OS_STATE_OS_RUNNING) { /* Has the OS started? */
return; /* No */
}

CPU_INT_DIS();
if (OSIntNestingCtr == (OS_NESTING_CTR)0) { /* Prevent OSIntNestingCtr from wrapping */
CPU_INT_EN();
return;
}
OSIntNestingCtr--;
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* ISRs still nested? */
CPU_INT_EN(); /* Yes */
return;
}

if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Scheduler still locked? */
CPU_INT_EN(); /* Yes */
return;
}

OSPrioHighRdy = OS_PrioGetHighest(); /* Find highest priority */
OSTCBHighRdyPtr = OSRdyList[OSPrioHighRdy].HeadPtr; /* Get highest priority task ready-to-run */
if (OSTCBHighRdyPtr == OSTCBCurPtr) { /* Current task still the highest priority? */
CPU_INT_EN(); /* Yes */
return;
}

#if OS_CFG_TASK_PROFILE_EN > 0u
OSTCBHighRdyPtr->CtxSwCtr++; /* Inc. # of context switches for this new task */
#endif
OSTaskCtxSwCtr++; /* Keep track of the total number of ctx switches */

#if defined(OS_CFG_TLS_TBL_SIZE) && (OS_CFG_TLS_TBL_SIZE > 0u)
OS_TLS_TaskSw();
#endif

OSIntCtxSw(); /* Perform interrupt level ctx switch */
CPU_INT_EN();
}

下面分析下这个伪代码的整个执行流程:

  • 关中断
  • 保护现场(注意,有的CPU支持中断堆栈,保护现场后就切换到中断堆栈,这样可以节省宝贵的任务堆栈空间。但MicroC/OS不支持中断堆栈,所以ISR还是使用的任务的堆栈空间)。
  • 然后中断嵌套层数自增,若是第一层嵌套,还需要修改当前任务的TCB的指针(把TSP保存到TCB中去,这步以后,当前任务的现场保护完毕)
  • 再清除中断请求
  • 开中断(若支持中断嵌套,需要打开这个)
  • 然后可以调用用户自己写的中断处理程序(ISR hander),来完成一些用户自定的工作,要求是越精简越好
  • 最后整个系统的中断服务程序就结束了,调用OSIntExit,来选择是否调度还是退到上一层的嵌套中
  • 最后恢复现场
  • 退出中断

无需内核参与的ISR

这是一个短的ISR,如果一个中断对应的ISR很短的话,需要处理的东西比较简单,可以考虑这样做,把其优先级设置的很高,这样就优先响应这个中断,并且中断的持续时间很短。

保留了:关中断,上下文切换,调用用户的ISR。

强调了一定不能开中断。

多中断优先级的处理器

中断的优先级是数值越大越好,和任务的优先级相反。

比较特殊的是,中断优先级为0~11的全部关了(仅在此例中。)因为这些中断需要发消息信号量等操作,为了保护临界区,需要关掉这些中断。

所有中断源共用中断服务程序

大概就是写一个C函数,是一个while循环,作为所有中断源共用的中断服务程序。当中断控制器中有中断请求时(中断控制器能够对到来的中断进行排序),这个共用中断服务函数会得到这个中断请求设备的interrupt hander的入口地址(也就是向量表中的地址),或者是向量表的索引,然后执行interrupt handler。

一旦中断控制器中无中断时,循环结束。

这种机制有缺点,会使所有的interrupt的等待时间达到最长。

每个中断源都有专门的中断服务程序

每个interrupt对应一个中断服务程序。

当来interrupt时,根据预设的向量表,跳到对应的中断服务程序,再跳到ISR handler中去。

直接发布和延迟发布

直接发布

是否直接发布取决于关中断的事件,若是关中断的事件过长,那么中断就长时间得不到响应。

具体流程如下:

  • hardware devices产生中断(电信号)
  • 中断控制器接收到中断信号,并且CPU是开中断,这时进入中断服务程序中
  • 中断服务程序在做必要的检查,以及保护现场后,提供中断服务(use interrupt handler),这时会唤醒一个为hardware devices提供服务的任务
  • 根据唤醒的任务的优先级来选择是否进行调度

当宏OS_CFG_ISR_POST_DEFERRED_EN为0时,表示通过关中断保护临界区,这时使用的是直接发布,在ISR中就唤醒了任务(将TCB从挂起队列删除,从就绪队列添加等操作,麻烦!)

延迟发布

延迟发布是当调度器上锁时使用,也就是设置宏OS_CFG_ISR_POST_DEFERRED_EN=1时。

下面还是先分析具体过程:

  • hardware devices产生中断信号
  • 中断控制器接收到信号
  • 进入中断服务程序
  • 发现锁了调度器,所以是延迟发布,那么该ISR的提供的interrupt handler就不是通过发信号量唤醒外设服务任务了,而是调用系统的发布服务函数向interrupt queue缓存这两个东西:1.发布信号量的发布函数调用。2.发布函数调用中需要传入的相关参数。
  • 然后ISR结束,结束前,会调用OSIntExit(),如果是最后一层nesting,那么就会进行一次调度,即退出中断前的调度。
  • 然后是短暂的系统服务(还未知是干嘛)
  • 接下来就是优先级最高的任务OSIntQTask得到处理机运行,分别调用interrupt queue中的发布函数调用,并传入相关参数,来进行发布信号量或者消息,来唤醒相关任务(PS:这里仍然需要关中断,以防止有ISR来同时对interrupt queue进行修改,这里就巧妙的在任务里面完成了对临界区的访问)。
  • 最后OSIntQTask发布完毕后,进行一次调度。

PS:书上在调度的具体实现里,将OSIntQTask翻译成中断服务管理任务,我觉得不太准确,还是翻译成“系统中断队列(管理)任务”比较符合它的功能

  • 优点:减小了中断延迟,响应和恢复的时间
  • 缺点:增大了(外设服务)任务延时时间

两种发布的对比

时钟节拍

时钟节拍是系统的心跳,时钟节拍频率越高,系统的额外开销越大。

一般而言需要给系统提供一个10~1000M的硬件定时器产生周期性的定时中断,作为所需要的时钟节拍。

时钟中断的ISR中必须调用一个OSTimeTick函数,直译过来就是“系统时钟节拍”函数

这在系统任务的时钟节拍任务里也有所提及,前面已经分析过了,这里就不做分析了。

但是我们看一下OS_IntQPost这个函数(中断队列发布):

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
void  OS_IntQPost (OS_OBJ_TYPE   type,
void *p_obj,
void *p_void,
OS_MSG_SIZE msg_size,
OS_FLAGS flags,
OS_OPT opt,
CPU_TS ts,
OS_ERR *p_err)
{
CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif

CPU_CRITICAL_ENTER();
if (OSIntQNbrEntries < OSCfg_IntQSize) { /* Make sure we haven't already filled the ISR queue */
OSIntQNbrEntries++;

if (OSIntQNbrEntriesMax < OSIntQNbrEntries) {
OSIntQNbrEntriesMax = OSIntQNbrEntries;
}

OSIntQInPtr->Type = type; /* Save object type being posted */
OSIntQInPtr->ObjPtr = p_obj; /* Save pointer to object being posted */
OSIntQInPtr->MsgPtr = p_void; /* Save pointer to message if posting to a message queue */
OSIntQInPtr->MsgSize = msg_size; /* Save the message size if posting to a message queue */
OSIntQInPtr->Flags = flags; /* Save the flags if posting to an event flag group */
OSIntQInPtr->Opt = opt; /* Save post options */
OSIntQInPtr->TS = ts; /* Save time stamp */

OSIntQInPtr = OSIntQInPtr->NextPtr; /* Point to the next interrupt handler queue entry */

OSRdyList[0].NbrEntries = (OS_OBJ_QTY)1; /* Make the interrupt handler task ready to run */
OSRdyList[0].HeadPtr = &OSIntQTaskTCB;
OSRdyList[0].TailPtr = &OSIntQTaskTCB;
OS_PrioInsert(0u); /* Add task priority 0 in the priority table */
if (OSPrioCur != 0) { /* Chk if OSIntQTask is not running */
OSPrioSaved = OSPrioCur; /* Save current priority */
}

*p_err = OS_ERR_NONE;
} else {
OSIntQOvfCtr++; /* Count the number of ISR queue overflows */
*p_err = OS_ERR_INT_Q_FULL;
}
CPU_CRITICAL_EXIT();
}

浅层次分析一波:

  • 首先关中断,避免出现ISR修改interrupt queue
  • 然后在确保队列没有满
  • 再才是对队列进行插入操作
  • 然后比较“奇葩”的一点,现在是处于ISR中,直接手动的将我们的OSIntQTask的TCB插入到就绪队列中去了,也就是说手动唤醒“OSIntQTask”

任务挂起表

这一章了解一下任务挂起表,pend lists 或者 wait lists

任务挂起表有很多个,任务的挂起可能是由于多个任务:

  • 等待信号量sem
  • 等待互斥信号量mutex
  • 等待标志组
  • 等待消息队列

由于有多个任务,于是就需要进行一个基本的排队操作,有pend lists这种数据结构完成。

任务挂起表的数据结构是:OS_PEND_LIST

含有三个字段:

  • NbrEntries:等待表中的表项数目
  • TailPtr:指向优先级最低的任务
  • HeadPtr:指向优先级最高的任务

pend list和 ready list不同,他指向的不是任务的TCB,而是另一种数据结构OS_PEND_DATA

原因是,其不止需要指明pend task的TCB,还需要知道task等待的内核对象,并且有的对象可能已经就绪,还有消息等。

还是挺重要的,这里还是列举一下:

  • PrevPtr
  • NextPtr
  • TCBPtr
  • PendObjPtr
  • RdyObjPtr
  • RdyMsgPtr
  • RdyMsgSize
  • RdyTS

时间管理

OSTimeDly()

函数功能:调用该函数的任务会延时指定时间,有三种模式

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
void  OSTimeDly (OS_TICK   dly, // 指定延时的时间片
OS_OPT opt, // 指定调用这个函数的模式
OS_ERR *p_err) // 返回错误代码
{
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) { /* Not allowed to call from an ISR */
*p_err = OS_ERR_TIME_DLY_ISR;
return;
}
#endif

if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0u) { /* Can't delay when the scheduler is locked */
*p_err = OS_ERR_SCHED_LOCKED;
return;
}

switch (opt) {
case OS_OPT_TIME_DLY: // 相对模式
case OS_OPT_TIME_TIMEOUT: // 绝对模式
case OS_OPT_TIME_PERIODIC: // 周期模式
if (dly == (OS_TICK)0u) { /* 0 means no delay! */
*p_err = OS_ERR_TIME_ZERO_DLY;
return;
}
break;

case OS_OPT_TIME_MATCH:
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); /* Remove current task from ready list */
OS_CRITICAL_EXIT_NO_SCHED();
OSSched(); /* Find next task to run! */
*p_err = OS_ERR_NONE;
}

下面是相对模式下的运行流程:

具体分析一下:(Tick时钟中断,ALL HPTs值得是所有高优先级的任务,LPT是我们的低优先级任务)

  • 首先是一个时间片来到(1),然后产生时钟中断,然后进入到时钟节拍的ISR中去,不管是关中断还是锁调度器(出ISR会运行中断队列服务函数),最终都会将信号量发送给时钟节拍任务(较高优先级),然后然后节拍任务进行延时,超时任务的更新。然后再是其它低优先级的任务的运行。
  • 我们的LPT得到处理机(3),并且运行一段时间后,想要延时2个Tick,调用了OSTimeDly,以相对模式延时两个Tick,这时LPT将会将自己放入一个pend list里面,等待2个时钟节拍。
  • 然后过了一段时间,第一个时钟节拍来了,一些HPT得到执行(指OSTickTask,OSIntQTask等),OSTickTask对pend list里的任务进行更新,此时LPT还需等待一个Tick(注意,LPT其实并没有延时Tick,比一个Tick要短)
  • 然后再过了一个完整的Tick(7),其实还是有一些HPT得到运行,只不过图中没有强调,而是特意指出了:LPT等到了最后一个Tick(也是OSTickTask进行更新的,若是最后一个Tick,应该将自己从pend list里移除,然后加入rdy list,再进行一次OSSche调度),然后得到了处理机,延时结束!

从上面看出,在相对模式下,并不能精确的延时两个时钟周期。

然后是周期模式:

周期模式可能在刚开始的第一个周期不准,但是后面都是准的。比较容易理解

绝对模式用的少,上电后10s关闭某个灯,没有具体的分析。

OSTimeDlyHMSM()

延时具体的时分秒毫秒。

该函数的工作模式仅有相对模式。

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
126
void  OSTimeDlyHMSM (CPU_INT16U   hours,
CPU_INT16U minutes,
CPU_INT16U seconds,
CPU_INT32U milli,
OS_OPT opt,
// OS_OPT_TIME_HMSM_STRICT,将检查延时参数的有效性,H: 0-99; M: 0-59; S: 0-59; M: 0-999
// OS_OPT_TIME_HMSM_NON_STRICT,不检查延时参数,只要不超过限制即可。
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) { /* Not allowed to call from an ISR */
*p_err = OS_ERR_TIME_DLY_ISR;
return;
}
#endif

if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0u) { /* Can't delay when the scheduler is locked */
*p_err = OS_ERR_SCHED_LOCKED;
return;
}

opt_time = opt & OS_OPT_TIME_MASK; /* Retrieve time options only. */
switch (opt_time) {
case OS_OPT_TIME_DLY:
case OS_OPT_TIME_TIMEOUT:
case OS_OPT_TIME_PERIODIC:
if (milli == (CPU_INT32U)0u) { /* Make sure we didn't specify a 0 delay */
if (seconds == (CPU_INT16U)0u) {
if (minutes == (CPU_INT16U)0u) {
if (hours == (CPU_INT16U)0u) {
*p_err = OS_ERR_TIME_ZERO_DLY;
return;
}
}
}
}
break;

case OS_OPT_TIME_MATCH:
break;

default:
*p_err = OS_ERR_OPT_INVALID;
return;
}

#if OS_CFG_ARG_CHK_EN > 0u /* Validate arguments to be within range */
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) {
if (milli > (CPU_INT32U)999u) {
*p_err = OS_ERR_TIME_INVALID_MILLISECONDS;
return;
}
if (seconds > (CPU_INT16U)59u) {
*p_err = OS_ERR_TIME_INVALID_SECONDS;
return;
}
if (minutes > (CPU_INT16U)59u) {
*p_err = OS_ERR_TIME_INVALID_MINUTES;
return;
}
if (hours > (CPU_INT16U)99u) {
*p_err = OS_ERR_TIME_INVALID_HOURS;
return;
}
} else {
if (minutes > (CPU_INT16U)9999u) {
*p_err = OS_ERR_TIME_INVALID_MINUTES;
return;
}
if (hours > (CPU_INT16U)999u) {
*p_err = OS_ERR_TIME_INVALID_HOURS;
return;
}
}
#endif

/* Compute the total number of clock ticks required.. */
/* .. (rounded to the nearest tick) */
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) {
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); /* Remove current task from ready list */
OS_CRITICAL_EXIT_NO_SCHED();
OSSched(); /* Find next task to run! */
*p_err = OS_ERR_NONE;
} else {
*p_err = OS_ERR_TIME_ZERO_DLY;
}
}

其opt中的限制模式说明下,小时的延时为啥最大是999小时。原因是:通常设置一个32位的计数器来跟踪时钟节拍,如果时钟节拍的频率是1000HZ,那么32位的整数可以记录:2^32/1000=4294967s=1193H,所以将H的限制设置在999,也是合理的。

OSTimeDlyResume()

使用该函数可以恢复其它调用了OSTimeDly或OSTimeDlyHMSM的任务。并且:延时的任务并不知道他是被其它任务恢复的,他以为自己到了延时的时间了

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
void  OSTimeDlyResume (OS_TCB  *p_tcb,
OS_ERR *p_err)
{
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) { /* Not allowed to call from an ISR */
*p_err = OS_ERR_TIME_DLY_RESUME_ISR;
return;
}
#endif

#if OS_CFG_ARG_CHK_EN > 0u
if (p_tcb == (OS_TCB *)0) { /* Not possible for the running task to be delayed! */
*p_err = OS_ERR_TASK_NOT_DLY;
return;
}
#endif

CPU_CRITICAL_ENTER();
if (p_tcb == OSTCBCurPtr) { /* Not possible for the running task to be delayed! */
*p_err = OS_ERR_TASK_NOT_DLY;
CPU_CRITICAL_EXIT();
return;
}

switch (p_tcb->TaskState) {
case OS_TASK_STATE_RDY: /* Cannot Abort delay if task is ready */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_TASK_NOT_DLY;
break;

case OS_TASK_STATE_DLY:
OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
p_tcb->TaskState = OS_TASK_STATE_RDY;
OS_TickListRemove(p_tcb); /* Remove task from tick list */
OS_RdyListInsert(p_tcb); /* Add to ready list */
OS_CRITICAL_EXIT_NO_SCHED();
*p_err = OS_ERR_NONE;
break;

case OS_TASK_STATE_PEND:
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_TASK_NOT_DLY;
break;

case OS_TASK_STATE_PEND_TIMEOUT:
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_TASK_NOT_DLY;
break;

case OS_TASK_STATE_SUSPENDED:
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_TASK_NOT_DLY;
break;

case OS_TASK_STATE_DLY_SUSPENDED:
OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
p_tcb->TaskState = OS_TASK_STATE_SUSPENDED;
OS_TickListRemove(p_tcb); /* Remove task from tick list */
OS_CRITICAL_EXIT_NO_SCHED();
*p_err = OS_ERR_TASK_SUSPENDED;
break;

case OS_TASK_STATE_PEND_SUSPENDED:
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_TASK_NOT_DLY;
break;

case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_TASK_NOT_DLY;
break;

default:
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_STATE_INVALID;
break;
}

OSSched();
}

像金手指一样,只需要传进待恢复任务的TCB即可。

看看实现的细节switch的那一部分:

  • 不允许在ISR中恢复任务
  • 首先检查TCB,如果是0代表当前任务,当前运行的任务肯定不是延时的,所以直接err
  • 然后检查TCB的state,什么状态执行什么操作,细节看代码
  • 最后发起任务调度OSSched

明确下,任务delay后,进入的不是pend list,而是tick list,下面看看具体的tick list

我们先得看下从tick list中移除TCB的操作:

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
void  OS_TickListRemove (OS_TCB  *p_tcb)
{
OS_TICK_SPOKE *p_spoke;
OS_TCB *p_tcb1;
OS_TCB *p_tcb2;



p_spoke = p_tcb->TickSpokePtr; // 指向时基列表数组的指针,即用来指示该任务 TCB 属于哪条双向链表。
if (p_spoke != (OS_TICK_SPOKE *)0) { /* Confirm that task is in tick list */
p_tcb->TickRemain = (OS_TICK)0u;
if (p_spoke->FirstPtr == p_tcb) { /* Is timer to remove at the beginning of list? */
p_tcb1 = (OS_TCB *)p_tcb->TickNextPtr; /* Yes */
p_spoke->FirstPtr = p_tcb1;
if (p_tcb1 != (OS_TCB *)0) {
p_tcb1->TickPrevPtr = (OS_TCB *)0;
}
} else {
p_tcb1 = p_tcb->TickPrevPtr; /* No, remove timer from somewhere in the list */
p_tcb2 = p_tcb->TickNextPtr;
p_tcb1->TickNextPtr = p_tcb2;
if (p_tcb2 != (OS_TCB *)0) {
p_tcb2->TickPrevPtr = p_tcb1;
}
}
p_tcb->TickNextPtr = (OS_TCB *)0;
p_tcb->TickPrevPtr = (OS_TCB *)0;
p_tcb->TickSpokePtr = (OS_TICK_SPOKE *)0;
p_tcb->TickCtrMatch = (OS_TICK )0u;
p_spoke->NbrEntries--;
}
}

从上面可以看出,并没有一个数据结构叫Tick list,那么上面说的双向链表是怎么来的呢?

TCB中维护着相关的字段:TickNextPtr,TickPrevPtr,TickSpokePtr,TickCtrMatch,看下Spoke的定义:

1
2
3
4
5
struct  os_tick_spoke {
OS_TCB *FirstPtr; /* Pointer to list of tasks in tick spoke */
OS_OBJ_QTY NbrEntries; /* Current number of entries in the tick spoke */
OS_OBJ_QTY NbrEntriesMax; /* Peak number of entries in the tick spoke */
};

Spoke里定义了链表最开始的TCB是在哪,以及任务的数目,任务数目的最大值。

OSTimeSet和OSTimeGet

获取和改变时钟节拍计数器的内容。

OSTimeTick

Tick ISR中会调用的函数,之前系统任务里也看过,这里再搬过来看看。

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
void  OSTimeTick (void)
{
OS_ERR err;
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
CPU_TS ts;
#endif


OSTimeTickHook(); /* Call user definable hook */

#if OS_CFG_ISR_POST_DEFERRED_EN > 0u // 延迟发布

ts = OS_TS_GET(); /* Get timestamp */
OS_IntQPost((OS_OBJ_TYPE) OS_OBJ_TYPE_TICK, /* Post to ISR queue */
(void *)&OSRdyList[OSPrioCur],
(void *) 0,
(OS_MSG_SIZE) 0u,
(OS_FLAGS ) 0u,
(OS_OPT ) 0u,
(CPU_TS ) ts,
(OS_ERR *)&err);

#else // 关中断

(void)OSTaskSemPost((OS_TCB *)&OSTickTaskTCB, /* Signal tick task */
(OS_OPT ) OS_OPT_POST_NONE,
(OS_ERR *)&err);


#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
OS_SchedRoundRobin(&OSRdyList[OSPrioCur]);
#endif

#if OS_CFG_TMR_EN > 0u // 使用定时器
OSTmrUpdateCtr--;
if (OSTmrUpdateCtr == (OS_CTR)0u) {
OSTmrUpdateCtr = OSTmrUpdateCnt;
OSTaskSemPost((OS_TCB *)&OSTmrTaskTCB, /* Signal timer task */
(OS_OPT ) OS_OPT_POST_NONE,
(OS_ERR *)&err);
}
#endif

#endif
}

下先分析关调度器的情况:

OS_CFG_ISR_POST_DEFERRED_EN=1

会将函数调用和参数发给ISR Queue:OS_IntQPost

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

void OS_IntQPost (OS_OBJ_TYPE type,
void *p_obj, // 内核对象
void *p_void, // 消息
OS_MSG_SIZE msg_size, //
OS_FLAGS flags,
OS_OPT opt,
CPU_TS ts,
OS_ERR *p_err)
{
CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif

CPU_CRITICAL_ENTER();
if (OSIntQNbrEntries < OSCfg_IntQSize) { /* Make sure we haven't already filled the ISR queue */
OSIntQNbrEntries++;

if (OSIntQNbrEntriesMax < OSIntQNbrEntries) {
OSIntQNbrEntriesMax = OSIntQNbrEntries;
}

OSIntQInPtr->Type = type; /* Save object type being posted */
OSIntQInPtr->ObjPtr = p_obj; /* Save pointer to object being posted */
OSIntQInPtr->MsgPtr = p_void; /* Save pointer to message if posting to a message queue */
OSIntQInPtr->MsgSize = msg_size; /* Save the message size if posting to a message queue */
OSIntQInPtr->Flags = flags; /* Save the flags if posting to an event flag group */
OSIntQInPtr->Opt = opt; /* Save post options */
OSIntQInPtr->TS = ts; /* Save time stamp */

OSIntQInPtr = OSIntQInPtr->NextPtr; /* Point to the next interrupt handler queue entry */

OSRdyList[0].NbrEntries = (OS_OBJ_QTY)1; /* Make the interrupt handler task ready to run */
OSRdyList[0].HeadPtr = &OSIntQTaskTCB;
OSRdyList[0].TailPtr = &OSIntQTaskTCB;
OS_PrioInsert(0u); /* Add task priority 0 in the priority table */
if (OSPrioCur != 0) { /* Chk if OSIntQTask is not running */
OSPrioSaved = OSPrioCur; /* Save current priority */
}

*p_err = OS_ERR_NONE;
} else {
OSIntQOvfCtr++; /* Count the number of ISR queue overflows */
*p_err = OS_ERR_INT_Q_FULL;
}
CPU_CRITICAL_EXIT();
}

需要看一下参数的意思,把注释也搬过来:

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
/*$PAGE*/
/*
************************************************************************************************************************
* POST TO ISR QUEUE
*
* Description: This function places contents of posts into an intermediate queue to help defer processing of interrupts
* at the task level.
*
* Arguments : type is the type of kernel object the post is destined to:
*
* OS_OBJ_TYPE_SEM
* OS_OBJ_TYPE_Q
* OS_OBJ_TYPE_FLAG
* OS_OBJ_TYPE_TASK_MSG
* OS_OBJ_TYPE_TASK_SIGNAL
*
* p_obj is a pointer to the kernel object to post to. This can be a pointer to a semaphore,
* ----- a message queue or a task control clock.
*
* p_void is a pointer to a message that is being posted. This is used when posting to a message
* queue or directly to a task.
*
* msg_size is the size of the message being posted
*
* flags if the post is done to an event flag group then this corresponds to the flags being
* posted
*
* ts is a timestamp as to when the post was done
*
* opt this corresponds to post options and applies to:
*
* OSFlagPost()
* OSSemPost()
* OSQPost()
* OSTaskQPost()
*
* p_err is a pointer to a variable that will contain an error code returned by this function.
*
* OS_ERR_NONE if the post to the ISR queue was successful
* OS_ERR_INT_Q_FULL if the ISR queue is full and cannot accepts any further posts. This
* generally indicates that you are receiving interrupts faster than you
* can process them or, that you didn't make the ISR queue large enough.
*
* Returns : none
*
* Note(s) : none
************************************************************************************************************************
*/

再下面是interrupt queue结构的定义:

1
2
3
4
5
6
7
8
9
10
struct  os_int_q {
OS_OBJ_TYPE Type; /* Type of object placed in the circular list */
OS_INT_Q *NextPtr; /* Pointer to next OS_INT_Q in circular list */
void *ObjPtr; /* Pointer to object placed in the queue */
void *MsgPtr; /* Pointer to message if posting to a message queue */
OS_MSG_SIZE MsgSize; /* Message Size if posting to a message queue */
OS_FLAGS Flags; /* Value of flags if posting to an event flag group */
OS_OPT Opt; /* Post Options */
CPU_TS TS; /* Timestamp */
};

看来interrupt queue是个单链表,所以当我们进行update时应该也是首先post头部,下面看看当,退出ISR后,我们的OSIntQTask被唤醒就绪,其执行过程:

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
void  OS_IntQTask (void  *p_arg)
{
CPU_BOOLEAN done;
CPU_TS ts_start;
CPU_TS ts_end;
CPU_SR_ALLOC();



p_arg = p_arg; /* Not using 'p_arg', prevent compiler warning */
while (DEF_ON) {
done = DEF_FALSE;
while (done == DEF_FALSE) {
CPU_CRITICAL_ENTER();
if (OSIntQNbrEntries == (OS_OBJ_QTY)0u) { // 如果interrupt queue里没有TCB,那么就将自己从就绪队列中移除,并且开始一次调度
OSRdyList[0].NbrEntries = (OS_OBJ_QTY)0u; /* Remove from ready list */
OSRdyList[0].HeadPtr = (OS_TCB *)0;
OSRdyList[0].TailPtr = (OS_TCB *)0;
OS_PrioRemove(0u); /* Remove from the priority table */
CPU_CRITICAL_EXIT();
OSSched();
done = DEF_TRUE; /* No more entries in the queue, we are done */
} else {
CPU_CRITICAL_EXIT();
ts_start = OS_TS_GET();
OS_IntQRePost(); // 这里是关键
ts_end = OS_TS_GET() - ts_start; /* Measure execution time of tick task */
if (OSIntQTaskTimeMax < ts_end) {
OSIntQTaskTimeMax = ts_end;
}
CPU_CRITICAL_ENTER();
OSIntQOutPtr = OSIntQOutPtr->NextPtr; /* Point to next item in the ISR queue */
OSIntQNbrEntries--;
CPU_CRITICAL_EXIT();
}
}
}
}

我们可以看到,若是interrupt queue为空时,直接让出CPU,并将退出循环;若是有的话,就调用OS_IntQRePost函数,注意,是在while循环中,一直调用,直到interrupt queue为空为止。

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
void  OS_IntQRePost (void)
{
CPU_TS ts;
OS_ERR err;


switch (OSIntQOutPtr->Type) { /* Re-post to task */
case OS_OBJ_TYPE_FLAG:
#if OS_CFG_FLAG_EN > 0u
(void)OS_FlagPost((OS_FLAG_GRP *) OSIntQOutPtr->ObjPtr,
(OS_FLAGS ) OSIntQOutPtr->Flags,
(OS_OPT ) OSIntQOutPtr->Opt,
(CPU_TS ) OSIntQOutPtr->TS,
(OS_ERR *)&err);
#endif
break;

case OS_OBJ_TYPE_Q:
#if OS_CFG_Q_EN > 0u
OS_QPost((OS_Q *) OSIntQOutPtr->ObjPtr,
(void *) OSIntQOutPtr->MsgPtr,
(OS_MSG_SIZE) OSIntQOutPtr->MsgSize,
(OS_OPT ) OSIntQOutPtr->Opt,
(CPU_TS ) OSIntQOutPtr->TS,
(OS_ERR *)&err);
#endif
break;

case OS_OBJ_TYPE_SEM:
#if OS_CFG_SEM_EN > 0u
(void)OS_SemPost((OS_SEM *) OSIntQOutPtr->ObjPtr,
(OS_OPT ) OSIntQOutPtr->Opt,
(CPU_TS ) OSIntQOutPtr->TS,
(OS_ERR *)&err);
#endif
break;

case OS_OBJ_TYPE_TASK_MSG:
#if OS_CFG_TASK_Q_EN > 0u
OS_TaskQPost((OS_TCB *) OSIntQOutPtr->ObjPtr,
(void *) OSIntQOutPtr->MsgPtr,
(OS_MSG_SIZE) OSIntQOutPtr->MsgSize,
(OS_OPT ) OSIntQOutPtr->Opt,
(CPU_TS ) OSIntQOutPtr->TS,
(OS_ERR *)&err);
#endif
break;

case OS_OBJ_TYPE_TASK_RESUME:
#if OS_CFG_TASK_SUSPEND_EN > 0u
(void)OS_TaskResume((OS_TCB *) OSIntQOutPtr->ObjPtr,
(OS_ERR *)&err);
#endif
break;

case OS_OBJ_TYPE_TASK_SIGNAL:
(void)OS_TaskSemPost((OS_TCB *) OSIntQOutPtr->ObjPtr,
(OS_OPT ) OSIntQOutPtr->Opt,
(CPU_TS ) OSIntQOutPtr->TS,
(OS_ERR *)&err);
break;

case OS_OBJ_TYPE_TASK_SUSPEND:
#if OS_CFG_TASK_SUSPEND_EN > 0u
(void)OS_TaskSuspend((OS_TCB *) OSIntQOutPtr->ObjPtr,
(OS_ERR *)&err);
#endif
break;

case OS_OBJ_TYPE_TICK:
#if OS_CFG_SCHED_ROUND_ROBIN_EN > 0u
OS_SchedRoundRobin(&OSRdyList[OSPrioSaved]);
#endif

(void)OS_TaskSemPost((OS_TCB *)&OSTickTaskTCB, /* Signal tick task */
(OS_OPT ) OS_OPT_POST_NONE,
(CPU_TS ) OSIntQOutPtr->TS,
(OS_ERR *)&err);
#if OS_CFG_TMR_EN > 0u
OSTmrUpdateCtr--;
if (OSTmrUpdateCtr == (OS_CTR)0u) {
OSTmrUpdateCtr = OSTmrUpdateCnt;
ts = OS_TS_GET(); /* Get timestamp */
(void)OS_TaskSemPost((OS_TCB *)&OSTmrTaskTCB, /* Signal timer task */
(OS_OPT ) OS_OPT_POST_NONE,
(CPU_TS ) ts,
(OS_ERR *)&err);
}
#endif
break;

default:
break;
}
}

根据OSIntQOutPtr的类型来判断怎么去post,具体情况很多,细节看代码。

等待都唤醒完后,可能就是轮到OSTickTask运行了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void  OS_TickTask (void  *p_arg)
{
OS_ERR err;
CPU_TS ts;


p_arg = p_arg; /* Prevent compiler warning */

while (DEF_ON) {
(void)OSTaskSemPend((OS_TICK )0,
(OS_OPT )OS_OPT_PEND_BLOCKING,
(CPU_TS *)&ts,
(OS_ERR *)&err); /* Wait for signal from tick interrupt */
if (err == OS_ERR_NONE) {
if (OSRunning == OS_STATE_OS_RUNNING) {
OS_TickListUpdate(); /* Update all tasks waiting for time */
}
}
}
}

其会运行update函数:

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
void  OS_TickListUpdate (void)
{
CPU_BOOLEAN done;
OS_TICK_SPOKE *p_spoke;
OS_TCB *p_tcb;
OS_TCB *p_tcb_next;
OS_TICK_SPOKE_IX spoke;
CPU_TS ts_start;
CPU_TS ts_end;
CPU_SR_ALLOC();


OS_CRITICAL_ENTER();
ts_start = OS_TS_GET();
OSTickCtr++; /* Keep track of the number of ticks */
spoke = (OS_TICK_SPOKE_IX)(OSTickCtr % OSCfg_TickWheelSize);
p_spoke = &OSCfg_TickWheel[spoke];
p_tcb = p_spoke->FirstPtr;
done = DEF_FALSE;
while (done == DEF_FALSE) {
if (p_tcb != (OS_TCB *)0) {
p_tcb_next = p_tcb->TickNextPtr; /* Point to next TCB to update */
switch (p_tcb->TaskState) {
case OS_TASK_STATE_RDY:
case OS_TASK_STATE_PEND:
case OS_TASK_STATE_SUSPENDED:
case OS_TASK_STATE_PEND_SUSPENDED:
break;

case OS_TASK_STATE_DLY:
p_tcb->TickRemain = p_tcb->TickCtrMatch /* Compute time remaining of current TCB */
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) { /* Process each TCB that expires */
p_tcb->TaskState = OS_TASK_STATE_RDY;
OS_TaskRdy(p_tcb); /* Make task ready to run */
} else {
done = DEF_TRUE; /* Don't find a match, we're done! */
}
break;

case OS_TASK_STATE_PEND_TIMEOUT:
p_tcb->TickRemain = p_tcb->TickCtrMatch /* Compute time remaining of current TCB */
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) { /* Process each TCB that expires */
#if (OS_MSG_EN > 0u)
p_tcb->MsgPtr = (void *)0;
p_tcb->MsgSize = (OS_MSG_SIZE)0u;
#endif
p_tcb->TS = OS_TS_GET();
OS_PendListRemove(p_tcb); /* Remove from wait list */
OS_TaskRdy(p_tcb);
p_tcb->TaskState = OS_TASK_STATE_RDY;
p_tcb->PendStatus = OS_STATUS_PEND_TIMEOUT; /* Indicate pend timed out */
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; /* Indicate no longer pending */
} else {
done = DEF_TRUE; /* Don't find a match, we're done! */
}
break;

case OS_TASK_STATE_DLY_SUSPENDED:
p_tcb->TickRemain = p_tcb->TickCtrMatch /* Compute time remaining of current TCB */
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) { /* Process each TCB that expires */
p_tcb->TaskState = OS_TASK_STATE_SUSPENDED;
OS_TickListRemove(p_tcb); /* Remove from current wheel spoke */
} else {
done = DEF_TRUE; /* Don't find a match, we're done! */
}
break;

case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
p_tcb->TickRemain = p_tcb->TickCtrMatch /* Compute time remaining of current TCB */
- OSTickCtr;
if (OSTickCtr == p_tcb->TickCtrMatch) { /* Process each TCB that expires */
#if (OS_MSG_EN > 0u)
p_tcb->MsgPtr = (void *)0;
p_tcb->MsgSize = (OS_MSG_SIZE)0u;
#endif
p_tcb->TS = OS_TS_GET();
OS_PendListRemove(p_tcb); /* Remove from wait list */
OS_TickListRemove(p_tcb); /* Remove from current wheel spoke */
p_tcb->TaskState = OS_TASK_STATE_SUSPENDED;
p_tcb->PendStatus = OS_STATUS_PEND_TIMEOUT; /* Indicate pend timed out */
p_tcb->PendOn = OS_TASK_PEND_ON_NOTHING; /* Indicate no longer pending */
} else {
done = DEF_TRUE; /* Don't find a match, we're done! */
}
break;

default:
break;
}
p_tcb = p_tcb_next;
} else {
done = DEF_TRUE;
}
}
ts_end = OS_TS_GET() - ts_start; /* Measure execution time of tick task */
if (OSTickTaskTimeMax < ts_end) {
OSTickTaskTimeMax = ts_end;
}
OS_CRITICAL_EXIT();
}

比较复杂。。。后面再看。

定时器管理

优先级低,先看信号量相关

资源管理

本章讲述的是OS中的资源管理,有这几种资源:

  • 变量
  • 结构体
  • 内存中的表格
  • IO设备中的寄存器
  • 等等

书上举了个例子,是时钟同步的内容。在进行时钟同步时,时分秒累加。假设刚好满60min,分针清零,时针准备加一,然后来中断/高优先级的任务,然后会获取当前时间,这时获取到的时间就会和真正的时间偏差一小时。

和老师上课举的例子差不多,都是要进行更新的时候被打断了,只不过老师课上讲的例子里面我们不仅打断了,还会修改其值。

上面说的这一类操作,可以总的归结起来为:“原子操作”,也就是不可被打断的操作

资源是可以被共享的,所有的任务都可以访问,但是,如何保证资源的独占,避免资源的竞争,是OS设计者需要考虑的事情。

有几个方式来保证资源的独占:

  • 关中断:对于RTOS而言,关中断意味着放弃了部分实时性,所以需要谨慎使用!
  • 锁调度器:锁调度器就不能进行任务的实时发布了,延迟发布会带来一定的延时,还是损失了部分实时性!同时,UCOS是基于优先级调度的,若是锁了调度器,高优先级的任务得不到响应,反而是低优先级的任务仍在运行,这违背了我们的优先级原则
  • 使用sem:使用sem:可以保证实时性(中断,HPTs得到CPU),但是会出现:若高优先级的任务后pend sem,那么就算得到CPU,也会主动放弃,将自身挂起。称之为优先级反转
  • 使用mutex:能够暂时提升LPT的优先级,解决了优先级反转的问题

下面是具体情况下的选取:

关中断

具体操作可以随便看一端代码,看OS_TaskSuspend的一部分:

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
void   OS_TaskSuspend (OS_TCB  *p_tcb,
OS_ERR *p_err)
{
CPU_SR_ALLOC(); //(1)



CPU_CRITICAL_ENTER(); // (2)
if (p_tcb == (OS_TCB *)0) { /* See if specified to suspend self */
p_tcb = OSTCBCurPtr;
}

if (p_tcb == OSTCBCurPtr) {
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Can't suspend when the scheduler is locked */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SCHED_LOCKED;
return;
}
}

*p_err = OS_ERR_NONE;
switch (p_tcb->TaskState) {
case OS_TASK_STATE_RDY:
OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
p_tcb->TaskState = OS_TASK_STATE_SUSPENDED;
p_tcb->SuspendCtr = (OS_NESTING_CTR)1;
OS_RdyListRemove(p_tcb);
OS_CRITICAL_EXIT_NO_SCHED(); // (3)
break;
......

然后分析代码中的(1)(2)(3)

  • (1)分配存储空间来存储当前CPU的中断状态。
  • (2)将CPU的中断标志位存储在上一步分配的空间中,并屏蔽所有可屏蔽中断。
  • (3)恢复局部变量中存储的CPU中断标志位

再说两点:

  • 上面的着三个函数,都是CPU的函数,而不是OS函数
  • 然后,关中断的时间越短越好

锁调度器

我们说UCOS是一个实时操作系统,可剥夺型的内核。刚刚上面关中断损失了实时性,下面看看锁调度器:

  • (2)关闭调度器
  • (3)访问临界资源,这时,即使来了高优先级的任务也不会进行调度;如果interrupt来了,那么也没关系,执行完ISR后,还是回到了我们原来被中断的任务,所有的HPTs都需要等待(看上去是不是很像不可剥夺型内核)
  • (4)开调度器

关调度损失的就是可剥夺这个特性了。

信号量

信号量最开始是一种机械机制,用在铁路系统中的。如今被用在多任务内核(CPU)中。

信号量大概有以下分类:

  • 二进制信号量
  • 计数信号量
  • 互斥量
  • 任务信号量

其中二进制型和计数型使用的是同一种数据结构,区别仅仅在于其数据结构不同。而互斥量跟优先级挂钩的。任务信号量是跟任务挂钩的。

二进制信号量/计数信号量

由于这俩的数据结构一样,区别仅在于使用,所以放在一起分析。

首先看看信号量的数据结构内部实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct  os_sem {                                            /* Semaphore                                              */
/* ------------------ GENERIC MEMBERS ------------------ */
OS_OBJ_TYPE Type; /* Should be set to OS_OBJ_TYPE_SEM */
CPU_CHAR *NamePtr; /* Pointer to Semaphore Name (NUL terminated ASCII) */
OS_PEND_LIST PendList; /* List of tasks waiting on semaphore */
// 下是关于Debug是否开启的判断
#if OS_CFG_DBG_EN > 0u
OS_SEM *DbgPrevPtr;
OS_SEM *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
/* ------------------ SPECIFIC MEMBERS ------------------ */
OS_SEM_CTR Ctr;
CPU_TS TS;
};

所以说,一般的信号量都只有四个字段:

1
2
3
4
5
6
7
8
struct  os_sem {                                           

OS_OBJ_TYPE Type; // 类型,对于信号量,必须是OS_OBJ_TYPE_SEM,好像每个内核对象都有一个具体的type,下面分析这个
CPU_CHAR *NamePtr; // 信号量的名字,便于记忆
OS_PEND_LIST PendList; // 信号量对应的 pend list
OS_SEM_CTR Ctr; // 信号量的值是多少,意味着二进制型或者计数型
CPU_TS TS; // 时间戳,初始化时为0
};

信号量是我们接触的第一个内核对象,下面看看内核对象类型OS_OBJ_TYPE,我猜测其是一个enum

但是猜错了,是一个32位数。。。

1
typedef   CPU_INT32U      OS_OBJ_TYPE;                 /* Special flag to determine object type,                   32 */

但是我找到了内核对象的定义:

1
2
3
4
5
6
7
8
9
10
11
12
#define  OS_OBJ_TYPE_NONE                    (OS_OBJ_TYPE)CPU_TYPE_CREATE('N', 'O', 'N', 'E')
#define OS_OBJ_TYPE_FLAG (OS_OBJ_TYPE)CPU_TYPE_CREATE('F', 'L', 'A', 'G')
#define OS_OBJ_TYPE_MEM (OS_OBJ_TYPE)CPU_TYPE_CREATE('M', 'E', 'M', ' ')
#define OS_OBJ_TYPE_MUTEX (OS_OBJ_TYPE)CPU_TYPE_CREATE('M', 'U', 'T', 'X')
#define OS_OBJ_TYPE_Q (OS_OBJ_TYPE)CPU_TYPE_CREATE('Q', 'U', 'E', 'U')
#define OS_OBJ_TYPE_SEM (OS_OBJ_TYPE)CPU_TYPE_CREATE('S', 'E', 'M', 'A')
#define OS_OBJ_TYPE_TASK_MSG (OS_OBJ_TYPE)CPU_TYPE_CREATE('T', 'M', 'S', 'G')
#define OS_OBJ_TYPE_TASK_RESUME (OS_OBJ_TYPE)CPU_TYPE_CREATE('T', 'R', 'E', 'S')
#define OS_OBJ_TYPE_TASK_SIGNAL (OS_OBJ_TYPE)CPU_TYPE_CREATE('T', 'S', 'I', 'G')
#define OS_OBJ_TYPE_TASK_SUSPEND (OS_OBJ_TYPE)CPU_TYPE_CREATE('T', 'S', 'U', 'S')
#define OS_OBJ_TYPE_TICK (OS_OBJ_TYPE)CPU_TYPE_CREATE('T', 'I', 'C', 'K')
#define OS_OBJ_TYPE_TMR (OS_OBJ_TYPE)CPU_TYPE_CREATE('T', 'M', 'R', ' ')

然后看看如何创建一个信号量:

1
2
3
4
5
OS_SEM key_sem;
OSSemCreate((OS_SEM *)&key_sem,
(CPU_CHAR *)"key_sem",
(OS_SEM_CTR )0,
(OS_ERR *)&err);

上面我们首先声明了一个信号量,然后使用函数进行信号量的创建(初始化)。

可以看到OSSemCreate需要的参数:

  • 声明的信号量的指针
  • 信号量的字符串名字
  • 信号量的数量
  • 创建结果存到&err指针中

看看OSSemCreat函数,附上注释

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
void  OSSemCreate (OS_SEM      *p_sem,
CPU_CHAR *p_name,
OS_SEM_CTR cnt,
OS_ERR *p_err)
{
// 创建信号量属于对内核数据结构的创建,需要实现操作的一气呵成,所以需要进入临界区
CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif

#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == DEF_TRUE) {
*p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME;
return;
}
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* Not allowed to be called from an ISR */
*p_err = OS_ERR_CREATE_ISR;
return;
}
#endif

#if OS_CFG_ARG_CHK_EN > 0u
if (p_sem == (OS_SEM *)0) { /* Validate 'p_sem' */
*p_err = OS_ERR_OBJ_PTR_NULL;
return;
}
#endif

// 开始进入临界区,关中断or关调度器
OS_CRITICAL_ENTER();
// 下面是给信号量数据结构进行赋值
p_sem->Type = OS_OBJ_TYPE_SEM; /* Mark the data structure as a semaphore */
p_sem->Ctr = cnt; /* Set semaphore value */
p_sem->TS = (CPU_TS)0;
p_sem->NamePtr = p_name; /* Save the name of the semaphore */
// 初始化一个信号量的pend list,当有任务pend这个信号量而其ctr=0时,会被加入到这个队列进行挂起等待
OS_PendListInit(&p_sem->PendList); /* Initialize the waiting list */

#if OS_CFG_DBG_EN > 0u
OS_SemDbgListAdd(p_sem);
#endif
OSSemQty++;
// 退出临界区并且不调度
OS_CRITICAL_EXIT_NO_SCHED();
*p_err = OS_ERR_NONE;
}

再看看pend一个信号量:

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
126
127
128
129
130
OS_SEM_CTR  OSSemPend (OS_SEM   *p_sem,
OS_TICK timeout,
OS_OPT opt,
CPU_TS *p_ts,
OS_ERR *p_err)
{
OS_SEM_CTR ctr;
OS_PEND_DATA pend_data;
CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return ((OS_SEM_CTR)0);
}
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* Not allowed to call from an ISR */
*p_err = OS_ERR_PEND_ISR;
return ((OS_SEM_CTR)0);
}
#endif

#if OS_CFG_ARG_CHK_EN > 0u
if (p_sem == (OS_SEM *)0) { /* Validate 'p_sem' */
*p_err = OS_ERR_OBJ_PTR_NULL;
return ((OS_SEM_CTR)0);
}
switch (opt) { /* Validate 'opt' */
case OS_OPT_PEND_BLOCKING:
case OS_OPT_PEND_NON_BLOCKING:
break;

default:
*p_err = OS_ERR_OPT_INVALID;
return ((OS_SEM_CTR)0);
}
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u
if (p_sem->Type != OS_OBJ_TYPE_SEM) { /* Make sure semaphore was created */
*p_err = OS_ERR_OBJ_TYPE;
return ((OS_SEM_CTR)0);
}
#endif

if (p_ts != (CPU_TS *)0) {
*p_ts = (CPU_TS)0; /* Initialize the returned timestamp */
}
// 从进入临界区开始分析
CPU_CRITICAL_ENTER();
if (p_sem->Ctr > (OS_SEM_CTR)0) { /* Resource available? */
// 当信号量的ctr>0时,可获取
p_sem->Ctr--; /* Yes, caller may proceed */
if (p_ts != (CPU_TS *)0) {
*p_ts = p_sem->TS; /* get timestamp of last post */
}
ctr = p_sem->Ctr;
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
// 返回了信号量剩余的值
return (ctr);
}
// 下面sem->ctr=0的情况
// 信号量不可用是否阻塞?
if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) { /* Caller wants to block if not available? */
ctr = p_sem->Ctr; /* No */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_PEND_WOULD_BLOCK;
return (ctr);
} else { /* Yes */
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Can't pend when the scheduler is locked */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SCHED_LOCKED;
return ((OS_SEM_CTR)0);
}
}
// 准备阻塞任务了
OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT(); /* Lock the scheduler/re-enable interrupts */
OS_Pend(&pend_data, /* Block task pending on Semaphore */
(OS_PEND_OBJ *)((void *)p_sem),
OS_TASK_PEND_ON_SEM,
timeout);

OS_CRITICAL_EXIT_NO_SCHED();

OSSched(); /* Find the next highest priority task ready to run */

CPU_CRITICAL_ENTER();
switch (OSTCBCurPtr->PendStatus) {
case OS_STATUS_PEND_OK: /* We got the semaphore */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_NONE;
break;

case OS_STATUS_PEND_ABORT: /* Indicate that we aborted */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_PEND_ABORT;
break;

case OS_STATUS_PEND_TIMEOUT: /* Indicate that we didn't get semaphore within timeout */
if (p_ts != (CPU_TS *)0) {
*p_ts = (CPU_TS )0;
}
*p_err = OS_ERR_TIMEOUT;
break;

case OS_STATUS_PEND_DEL: /* Indicate that object pended on has been deleted */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_OBJ_DEL;
break;

default:
*p_err = OS_ERR_STATUS_INVALID;
CPU_CRITICAL_EXIT();
return ((OS_SEM_CTR)0);
}
ctr = p_sem->Ctr;
CPU_CRITICAL_EXIT();
return (ctr);
}

然后是OSSemPost:(注意这个没有进入临界区)

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
OS_SEM_CTR  OSSemPost (OS_SEM  *p_sem,
OS_OPT opt,
OS_ERR *p_err)
{
OS_SEM_CTR ctr;
CPU_TS ts;



#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return ((OS_SEM_CTR)0);
}
#endif

#if OS_CFG_ARG_CHK_EN > 0u
if (p_sem == (OS_SEM *)0) { /* Validate 'p_sem' */
*p_err = OS_ERR_OBJ_PTR_NULL;
return ((OS_SEM_CTR)0);
}
switch (opt) { /* Validate 'opt' */
case OS_OPT_POST_1:
case OS_OPT_POST_ALL:
case OS_OPT_POST_1 | OS_OPT_POST_NO_SCHED:
case OS_OPT_POST_ALL | OS_OPT_POST_NO_SCHED:
break;

default:
*p_err = OS_ERR_OPT_INVALID;
return ((OS_SEM_CTR)0u);
}
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u
if (p_sem->Type != OS_OBJ_TYPE_SEM) { /* Make sure semaphore was created */
*p_err = OS_ERR_OBJ_TYPE;
return ((OS_SEM_CTR)0);
}
#endif

ts = OS_TS_GET(); /* Get timestamp */
// 关调度器?
#if OS_CFG_ISR_POST_DEFERRED_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* See if called from an ISR */
OS_IntQPost((OS_OBJ_TYPE)OS_OBJ_TYPE_SEM, /* Post to ISR queue */
(void *)p_sem,
(void *)0,
(OS_MSG_SIZE)0,
(OS_FLAGS )0,
(OS_OPT )opt,
(CPU_TS )ts,
(OS_ERR *)p_err);
return ((OS_SEM_CTR)0);
}
#endif
// 关中断?
ctr = OS_SemPost(p_sem, /* Post to semaphore */
opt,
ts,
p_err);

return (ctr);
}

常用操作就上面这些,简单理解下源码即可。然后下面是使用时的注意事项:

  • 计数型常用来做缓冲
  • 信号量的使用需要谨慎,一些简单的操作可以直接使用关中断来实现,使用信号量就大材小用了。

例子:如进行int型共享变量的自增,直接关中断,因为很快;而进行float运算,则需要使用信号量了,因为如果是关中断的话,那么中断的关闭时间会太久了,影响系统的实时性。

总之,牢记信号量被提出的目的:保证OS的实时性!

优先级反转

在讲互斥型信号量之前,先说明普通信号量存在的问题:优先级反转,如下图:

简单的来说,就是当LPT先得到信号量时,HPT再获取信号量,这时pend不到,将自身挂到内核对象对应的pend list中。

然后,就会出现这样的现象:所有其它高优先级的任务占据处理机,而我们最高优先级的HPT被挂起,等待LPT释放信号量,而Sem由于优先级太低得不到处理机

上面这段过程中,存在着高优先级的任务得不到处理机,而低优先级的任务在运行的现象

其根本原因是最先得到sem的LPT优先级太低了,所以,出现了mutex解决这个问题:最简单的,在HPT发现Sem被LPT获取后,将LPT的优先级暂时提升到和HPT一样。

互斥型信号量

先看定义:

1
OS_MUTEX	TEST_MUTEX;

再看结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct  os_mutex {                                          /* Mutual Exclusion Semaphore                             */
/* ------------------ GENERIC MEMBERS ------------------ */
OS_OBJ_TYPE Type; /* Should be set to OS_OBJ_TYPE_MUTEX */
CPU_CHAR *NamePtr; /* Pointer to Mutex Name (NUL terminated ASCII) */
OS_PEND_LIST PendList; /* List of tasks waiting on mutex */
#if OS_CFG_DBG_EN > 0u
OS_MUTEX *DbgPrevPtr;
OS_MUTEX *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
#endif
/* ------------------ SPECIFIC MEMBERS ------------------ */
OS_TCB *OwnerTCBPtr;
OS_PRIO OwnerOriginalPrio; // 该字段存放的是占有mutex的任务之前的优先级,为了避免出现无界优先级反转的问题
// 允许任务多次pend mutex,但是pend多少次就要post多少次;最高嵌套次数250
OS_NESTING_CTR OwnerNestingCtr; /* Mutex is available when the counter is 0 */
CPU_TS TS; /* 记录上一次被释放时的时间戳 */
};

再看看使用mutex后,上一节的优先级反转就不存在了:

注意,和信号量一样,只有task能使用mutex,而ISR不行(中断事件越短越好)

下面看看mutex的API:

OSMutexCreate

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
void  OSMutexCreate (OS_MUTEX  *p_mutex,
CPU_CHAR *p_name,
OS_ERR *p_err)
{
CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return;
}
#endif

#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == DEF_TRUE) {
*p_err = OS_ERR_ILLEGAL_CREATE_RUN_TIME;
return;
}
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* Not allowed to be called from an ISR */
*p_err = OS_ERR_CREATE_ISR;
return;
}
#endif

#if OS_CFG_ARG_CHK_EN > 0u
if (p_mutex == (OS_MUTEX *)0) { /* Validate 'p_mutex' */
*p_err = OS_ERR_OBJ_PTR_NULL;
return;
}
#endif

OS_CRITICAL_ENTER();
p_mutex->Type = OS_OBJ_TYPE_MUTEX; /* Mark the data structure as a mutex */
p_mutex->NamePtr = p_name;
p_mutex->OwnerTCBPtr = (OS_TCB *)0;
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)0; /* Mutex is available */
p_mutex->TS = (CPU_TS )0;
p_mutex->OwnerOriginalPrio = OS_CFG_PRIO_MAX;
OS_PendListInit(&p_mutex->PendList); /* Initialize the waiting list */

#if OS_CFG_DBG_EN > 0u
OS_MutexDbgListAdd(p_mutex);
#endif
OSMutexQty++;

OS_CRITICAL_EXIT_NO_SCHED();
*p_err = OS_ERR_NONE;
}

OSMutexPend:

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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
void  OSMutexPend (OS_MUTEX  *p_mutex,
OS_TICK timeout,
OS_OPT opt,
CPU_TS *p_ts,
OS_ERR *p_err)
{
OS_PEND_DATA pend_data;
OS_TCB *p_tcb;
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)0) { /* Not allowed to call from an ISR */
*p_err = OS_ERR_PEND_ISR;
return;
}
#endif

#if OS_CFG_ARG_CHK_EN > 0u
if (p_mutex == (OS_MUTEX *)0) { /* Validate arguments */
*p_err = OS_ERR_OBJ_PTR_NULL;
return;
}
switch (opt) { /* Validate 'opt' */
case OS_OPT_PEND_BLOCKING:
case OS_OPT_PEND_NON_BLOCKING:
break;

default:
*p_err = OS_ERR_OPT_INVALID;
return;
}
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u
if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) { /* Make sure mutex was created */
*p_err = OS_ERR_OBJ_TYPE;
return;
}
#endif

if (p_ts != (CPU_TS *)0) {
*p_ts = (CPU_TS )0; /* Initialize the returned timestamp */
}

CPU_CRITICAL_ENTER();
if (p_mutex->OwnerNestingCtr == (OS_NESTING_CTR)0) { /* Resource available? */
p_mutex->OwnerTCBPtr = OSTCBCurPtr; /* Yes, caller may proceed */
p_mutex->OwnerOriginalPrio = OSTCBCurPtr->Prio;
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)1;
if (p_ts != (CPU_TS *)0) {
*p_ts = p_mutex->TS;
}
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
return;
}

if (OSTCBCurPtr == p_mutex->OwnerTCBPtr) { /* See if current task is already the owner of the mutex */
p_mutex->OwnerNestingCtr++;
if (p_ts != (CPU_TS *)0) {
*p_ts = p_mutex->TS;
}
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_MUTEX_OWNER; /* Indicate that current task already owns the mutex */
return;
}

if ((opt & OS_OPT_PEND_NON_BLOCKING) != (OS_OPT)0) { /* Caller wants to block if not available? */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_PEND_WOULD_BLOCK; /* No */
return;
} else {
if (OSSchedLockNestingCtr > (OS_NESTING_CTR)0) { /* Can't pend when the scheduler is locked */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_SCHED_LOCKED;
return;
}
}

OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT(); /* Lock the scheduler/re-enable interrupts */
p_tcb = p_mutex->OwnerTCBPtr; /* Point to the TCB of the Mutex owner */
if (p_tcb->Prio > OSTCBCurPtr->Prio) { /* See if mutex owner has a lower priority than current */
switch (p_tcb->TaskState) {
case OS_TASK_STATE_RDY:
OS_RdyListRemove(p_tcb); /* Remove from ready list at current priority */
p_tcb->Prio = OSTCBCurPtr->Prio; /* Raise owner's priority */
OS_PrioInsert(p_tcb->Prio);
OS_RdyListInsertHead(p_tcb); /* Insert in ready list at new priority */
break;

case OS_TASK_STATE_DLY:
case OS_TASK_STATE_DLY_SUSPENDED:
case OS_TASK_STATE_SUSPENDED:
p_tcb->Prio = OSTCBCurPtr->Prio; /* Only need to raise the owner's priority */
break;

case OS_TASK_STATE_PEND: /* Change the position of the task in the wait list */
case OS_TASK_STATE_PEND_TIMEOUT:
case OS_TASK_STATE_PEND_SUSPENDED:
case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
OS_PendListChangePrio(p_tcb,
OSTCBCurPtr->Prio);
break;

default:
OS_CRITICAL_EXIT();
*p_err = OS_ERR_STATE_INVALID;
return;
}
}

OS_Pend(&pend_data, /* Block task pending on Mutex */
(OS_PEND_OBJ *)((void *)p_mutex),
OS_TASK_PEND_ON_MUTEX,
timeout);

OS_CRITICAL_EXIT_NO_SCHED();

OSSched(); /* Find the next highest priority task ready to run */

CPU_CRITICAL_ENTER();
switch (OSTCBCurPtr->PendStatus) {
case OS_STATUS_PEND_OK: /* We got the mutex */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_NONE;
break;

case OS_STATUS_PEND_ABORT: /* Indicate that we aborted */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_PEND_ABORT;
break;

case OS_STATUS_PEND_TIMEOUT: /* Indicate that we didn't get mutex within timeout */
if (p_ts != (CPU_TS *)0) {
*p_ts = (CPU_TS )0;
}
*p_err = OS_ERR_TIMEOUT;
break;

case OS_STATUS_PEND_DEL: /* Indicate that object pended on has been deleted */
if (p_ts != (CPU_TS *)0) {
*p_ts = OSTCBCurPtr->TS;
}
*p_err = OS_ERR_OBJ_DEL;
break;

default:
*p_err = OS_ERR_STATUS_INVALID;
break;
}
CPU_CRITICAL_EXIT();
}

OSMutexPost

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
void  OSMutexPost (OS_MUTEX  *p_mutex,
OS_OPT opt,
OS_ERR *p_err)
{
OS_PEND_LIST *p_pend_list;
OS_TCB *p_tcb;
CPU_TS ts;
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)0) { /* Not allowed to call from an ISR */
*p_err = OS_ERR_POST_ISR;
return;
}
#endif

#if OS_CFG_ARG_CHK_EN > 0u
if (p_mutex == (OS_MUTEX *)0) { /* Validate 'p_mutex' */
*p_err = OS_ERR_OBJ_PTR_NULL;
return;
}
switch (opt) { /* Validate 'opt' */
case OS_OPT_POST_NONE:
case OS_OPT_POST_NO_SCHED:
break;

default:
*p_err = OS_ERR_OPT_INVALID;
return;
}
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u
if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) { /* Make sure mutex was created */
*p_err = OS_ERR_OBJ_TYPE;
return;
}
#endif

CPU_CRITICAL_ENTER();
if (OSTCBCurPtr != p_mutex->OwnerTCBPtr) { /* Make sure the mutex owner is releasing the mutex */
CPU_CRITICAL_EXIT();
*p_err = OS_ERR_MUTEX_NOT_OWNER;
return;
}

OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
ts = OS_TS_GET(); /* Get timestamp */
p_mutex->TS = ts;
p_mutex->OwnerNestingCtr--; /* Decrement owner's nesting counter */
if (p_mutex->OwnerNestingCtr > (OS_NESTING_CTR)0) { /* Are we done with all nestings? */
OS_CRITICAL_EXIT(); /* No */
*p_err = OS_ERR_MUTEX_NESTING;
return;
}

p_pend_list = &p_mutex->PendList;
if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0) { /* Any task waiting on mutex? */
p_mutex->OwnerTCBPtr = (OS_TCB *)0; /* No */
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)0;
OS_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
return;
}
/* Yes */
if (OSTCBCurPtr->Prio != p_mutex->OwnerOriginalPrio) {
OS_RdyListRemove(OSTCBCurPtr);
OSTCBCurPtr->Prio = p_mutex->OwnerOriginalPrio; /* Lower owner's priority back to its original one */
OS_PrioInsert(OSTCBCurPtr->Prio);
OS_RdyListInsertTail(OSTCBCurPtr); /* Insert owner in ready list at new priority */
OSPrioCur = OSTCBCurPtr->Prio;
}
/* Get TCB from head of pend list */
p_tcb = p_pend_list->HeadPtr->TCBPtr;
p_mutex->OwnerTCBPtr = p_tcb; /* Give mutex to new owner */
p_mutex->OwnerOriginalPrio = p_tcb->Prio;
p_mutex->OwnerNestingCtr = (OS_NESTING_CTR)1;
/* Post to mutex */
OS_Post((OS_PEND_OBJ *)((void *)p_mutex),
(OS_TCB *)p_tcb,
(void *)0,
(OS_MSG_SIZE )0,
(CPU_TS )ts);

OS_CRITICAL_EXIT_NO_SCHED();

if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0) {
OSSched(); /* Run the scheduler */
}

*p_err = OS_ERR_NONE;
}

OSMutexDel

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
126
127
128
129
130
131
132
#if OS_CFG_MUTEX_DEL_EN > 0u
OS_OBJ_QTY OSMutexDel (OS_MUTEX *p_mutex,
OS_OPT opt,
OS_ERR *p_err)
{
OS_OBJ_QTY cnt;
OS_OBJ_QTY nbr_tasks;
OS_PEND_DATA *p_pend_data;
OS_PEND_LIST *p_pend_list;
OS_TCB *p_tcb;
OS_TCB *p_tcb_owner;
CPU_TS ts;
CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return ((OS_OBJ_QTY)0);
}
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0) { /* Not allowed to delete a mutex from an ISR */
*p_err = OS_ERR_DEL_ISR;
return ((OS_OBJ_QTY)0);
}
#endif

#if OS_CFG_ARG_CHK_EN > 0u
if (p_mutex == (OS_MUTEX *)0) { /* Validate 'p_mutex' */
*p_err = OS_ERR_OBJ_PTR_NULL;
return ((OS_OBJ_QTY)0);
}
switch (opt) { /* Validate 'opt' */
case OS_OPT_DEL_NO_PEND:
case OS_OPT_DEL_ALWAYS:
break;

default:
*p_err = OS_ERR_OPT_INVALID;
return ((OS_OBJ_QTY)0);
}
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u
if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) { /* Make sure mutex was created */
*p_err = OS_ERR_OBJ_TYPE;
return ((OS_OBJ_QTY)0);
}
#endif

OS_CRITICAL_ENTER();
p_pend_list = &p_mutex->PendList;
cnt = p_pend_list->NbrEntries;
nbr_tasks = cnt;
switch (opt) {
case OS_OPT_DEL_NO_PEND: /* Delete mutex only if no task waiting */
if (nbr_tasks == (OS_OBJ_QTY)0) {
#if OS_CFG_DBG_EN > 0u
OS_MutexDbgListRemove(p_mutex);
#endif
OSMutexQty--;
OS_MutexClr(p_mutex);
OS_CRITICAL_EXIT();
*p_err = OS_ERR_NONE;
} else {
OS_CRITICAL_EXIT();
*p_err = OS_ERR_TASK_WAITING;
}
break;

case OS_OPT_DEL_ALWAYS: /* Always delete the mutex */
p_tcb_owner = p_mutex->OwnerTCBPtr; /* Did we had to change the prio of owner? */
if ((p_tcb_owner != (OS_TCB *)0) &&
(p_tcb_owner->Prio != p_mutex->OwnerOriginalPrio)) {
switch (p_tcb_owner->TaskState) { /* yes */
case OS_TASK_STATE_RDY:
OS_RdyListRemove(p_tcb_owner);
p_tcb_owner->Prio = p_mutex->OwnerOriginalPrio; /* Lower owner's prio back */
OS_PrioInsert(p_tcb_owner->Prio);
OS_RdyListInsertTail(p_tcb_owner); /* Insert owner in ready list at new prio */
break;

case OS_TASK_STATE_DLY:
case OS_TASK_STATE_SUSPENDED:
case OS_TASK_STATE_DLY_SUSPENDED:
p_tcb_owner->Prio = p_mutex->OwnerOriginalPrio; /* Not in any pend list, change the prio */
break;

case OS_TASK_STATE_PEND:
case OS_TASK_STATE_PEND_TIMEOUT:
case OS_TASK_STATE_PEND_SUSPENDED:
case OS_TASK_STATE_PEND_TIMEOUT_SUSPENDED:
OS_PendListChangePrio(p_tcb_owner, /* Owner is pending on another object */
p_mutex->OwnerOriginalPrio);
break;

default:
OS_CRITICAL_EXIT();
*p_err = OS_ERR_STATE_INVALID;
return ((OS_OBJ_QTY)0);
}
}

ts = OS_TS_GET(); /* Get timestamp */
while (cnt > 0u) { /* Remove all tasks from the pend list */
p_pend_data = p_pend_list->HeadPtr;
p_tcb = p_pend_data->TCBPtr;
OS_PendObjDel((OS_PEND_OBJ *)((void *)p_mutex),
p_tcb,
ts);
cnt--;
}
#if OS_CFG_DBG_EN > 0u
OS_MutexDbgListRemove(p_mutex);
#endif
OSMutexQty--;
OS_MutexClr(p_mutex);
OS_CRITICAL_EXIT_NO_SCHED();
OSSched(); /* Find highest priority task ready to run */
*p_err = OS_ERR_NONE;
break;

default:
OS_CRITICAL_EXIT();
*p_err = OS_ERR_OPT_INVALID;
break;
}
return (nbr_tasks);
}

OSMutexPendAbort

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
#if OS_CFG_MUTEX_PEND_ABORT_EN > 0u
OS_OBJ_QTY OSMutexPendAbort (OS_MUTEX *p_mutex,
OS_OPT opt,
OS_ERR *p_err)
{
OS_PEND_LIST *p_pend_list;
OS_TCB *p_tcb;
CPU_TS ts;
OS_OBJ_QTY nbr_tasks;
CPU_SR_ALLOC();



#ifdef OS_SAFETY_CRITICAL
if (p_err == (OS_ERR *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
return ((OS_OBJ_QTY)0u);
}
#endif

#if OS_CFG_CALLED_FROM_ISR_CHK_EN > 0u
if (OSIntNestingCtr > (OS_NESTING_CTR)0u) { /* Not allowed to Pend Abort from an ISR */
*p_err = OS_ERR_PEND_ABORT_ISR;
return ((OS_OBJ_QTY)0u);
}
#endif

#if OS_CFG_ARG_CHK_EN > 0u
if (p_mutex == (OS_MUTEX *)0) { /* Validate 'p_mutex' */
*p_err = OS_ERR_OBJ_PTR_NULL;
return ((OS_OBJ_QTY)0u);
}
switch (opt) { /* Validate 'opt' */
case OS_OPT_PEND_ABORT_1:
case OS_OPT_PEND_ABORT_ALL:
case OS_OPT_PEND_ABORT_1 | OS_OPT_POST_NO_SCHED:
case OS_OPT_PEND_ABORT_ALL | OS_OPT_POST_NO_SCHED:
break;

default:
*p_err = OS_ERR_OPT_INVALID;
return ((OS_OBJ_QTY)0u);
}
#endif

#if OS_CFG_OBJ_TYPE_CHK_EN > 0u
if (p_mutex->Type != OS_OBJ_TYPE_MUTEX) { /* Make sure mutex was created */
*p_err = OS_ERR_OBJ_TYPE;
return ((OS_OBJ_QTY)0u);
}
#endif

CPU_CRITICAL_ENTER();
p_pend_list = &p_mutex->PendList;
if (p_pend_list->NbrEntries == (OS_OBJ_QTY)0u) { /* Any task waiting on mutex? */
CPU_CRITICAL_EXIT(); /* No */
*p_err = OS_ERR_PEND_ABORT_NONE;
return ((OS_OBJ_QTY)0u);
}

OS_CRITICAL_ENTER_CPU_CRITICAL_EXIT();
nbr_tasks = 0u;
ts = OS_TS_GET(); /* Get local time stamp so all tasks get the same time */
while (p_pend_list->NbrEntries > (OS_OBJ_QTY)0u) {
p_tcb = p_pend_list->HeadPtr->TCBPtr;
OS_PendAbort((OS_PEND_OBJ *)((void *)p_mutex),
p_tcb,
ts);
nbr_tasks++;
if (opt != OS_OPT_PEND_ABORT_ALL) { /* Pend abort all tasks waiting? */
break; /* No */
}
}
OS_CRITICAL_EXIT_NO_SCHED();

if ((opt & OS_OPT_POST_NO_SCHED) == (OS_OPT)0u) {
OSSched(); /* Run the scheduler */
}

*p_err = OS_ERR_NONE;
return (nbr_tasks);
}

死锁

或抱死(deadly embrace),具体例子很熟悉了不阐述。

而解决死锁,教材上提到的是加入timeout。其原理是:破坏了死锁产生的几个必要条件中的请求保持和释放,不会一直保持pend状态,超时后就会返回错误,恢复到就绪态或运行态。

任务同步

信号量

在前面一张已经讲过一些信号量(通过信号量进行资源管理),然后这一章是通过信号量进行任务同步(我们OS的所有任务都是异步执行的,而要想实现任务的顺序执行,也就是同步,那么可以采用信号量这种方式)

看下面这张图:

我们看到有两种方式:

  • 任务-任务-sem:任务可以post,也可以pend,灵活性很大
  • ISR-任务-sem:ISR只能post,不能pend,原因也很简单,若ISR进行pend等待,那么ISR的优先级总是高于任务,这样所有的任务都跑不了了。(?)

消息传递