实时系统复习
作者:互联网
实时系统复习
基础
教材
- Girogio C. Buttazzo, Hard Real-Time Computing Systems, Third
edition, Springer, 2011. - 卢有亮, 嵌入式实时操作系统-μC/OS, 第二版, 电子工业出版社,
2014
讨论内容
中文教材:
-
基本概念:硬件(不考),任务和约束,定义调度的问题,调度的异常
-
实时任务调度和调度分析:非周期任务调度,周期任务调度
-
μC/OS:任务调度,中断和时间管理,事件管理,消息管理,内存管理
英文教材:
-
资源控制协议:优先级反转,非抢占协议,优先级继承协议,优先级封顶协议,对协议进行分析
-
过载处理:处理非周期过载,处理溢出,处理恒定过载
概念
软实时,硬实时,准实时
嵌入式应用
实时:定量(quantitative),定性(qualitative)
实时系统:给定的时间约束下面完成任务的计算机系统
实时操作系统:对外界输入有限时响应,根据实时进行设计,帮助任务满足时间要求
安全与可用(safety & reliability)
失败安全状态(fail safe state)
安全关键系统,任何失败都会对系统造成严重损坏,没有失败安全状态,只能通过可靠性提高来保证安全
设计高可靠系统:
- 错误避免
- 错误检测和移除
- 容错
- 硬件
- built in self test 自测试电路
- 三模冗余:投票
- 软件
- N版本程序:三模冗余的思想
- 恢复模块
- 硬件
课程目标
- 研究学习时间关键计算系统用得到的算法
- 设计软件使得控制应用可预测
任务模型
任务Task:到达时间ai,开始时间si,计算时间ci,结束时间fi,响应时间fi - si
就绪队列ready queue
抢占preemption
调度schedule
任务状态task states
实时任务real-time task
- 截止时间deadline:绝对时间di
- 可行feasible:fi < di
- 裕量slack:di - fi or 延迟lateness:fi - di
作业job:任务的实例
启动模式Activation Mode :
- 时间驱动time driven:周期任务periodic tasks
- 事件驱动event driven:非周期任务aperiodic tasks,通常是通过系统调用
周期任务:
- 利用率因子Ui :\(U_i = \frac{C_i}{T_i}\)
- 启动时间:\(a_{i,k} = \Phi_i +(k-1)T_i\)
- 绝对截止时间:\(d_{i,k} = a_{i,k} + D_i\),often Di = Ti
非周期任务:\(a_{i,k+1} > a_{i,k}\)
零星任务Sporadic:\(a_{i,k+1} \ge a_{i,k} + Ti\)
重要程度:硬实时HARD task,准实时FIRMtask,软实时SOFT task
抖动Jitter:衡量周期事件上时间的变化
-
绝对absolute:\(\underset k \max(t_k - a_k) - \underset k \min(t_k - a_k)\)
-
相对relative:\(\underset k \max | (t_k - a_k) - (t_{k-1} - a_{k-1})|\)
-
类型:结束时间抖动finishing-time jitter,启动时间抖动start-time jitter, 完成时间抖动completion-time jitter(I/O jitter)
参数分类parameters:
- 指定参数known offline:计算时间Ci,周期Ti,相对截止时间Di
- 运行时参数known at run time:到达时间ai, 开始时间si,结束时间fi,响应时间Ri,裕量或延迟,抖动
空闲IDLE
任务约束
类型
- 时间约束Timing constraints
- 次序约束Precedence constraints
- 资源约束Resource constraints
并发Concurrency
并发与I/O
周期任务:
- FIFO
- 单调速率RM。周期越短,频率越高,优先级越高。
冲突:互斥信号量,信号量
时间异常
先后次序
处理器速度快!=实时性能好 fast !=realtime
延迟delay
Semaphores Semaphores
实时操作系统基础
- 实时操作系统首先是多任务操作系统
- 多级中断
- 优先级调度机制
任务
多任务
任务状态
可重入与不可重入
函数可重入是指一个函数可以被多个任务调用, 而不需要担心在
任务切换的过程中, 代码的执行会产生错误的结果
内核
内核是操作系统最核心的部分, 其主要功能就是进行任务调度
基于优先级的调度算法
在μC/OS-II中, 可以同时有64个就绪任务, 每个任务都有各自的优先级。 优先级用无符号整数来表示, 从0到63,数字越大则优先级越低。
μC/OS总是调度就绪了的、 优先级最高的任务获得CPU的控制权, 不管这个任务是什么, 执行什么样的功能, 也不管该任务是否已经等了很久。
不可剥夺和可剥夺内核
其实就是任务是否可抢占
同步和通信
同步
有时候一个任务完成的前提是需要另一个任务给出一个结果, 任务之间的这种制约性的合作运行机制叫做任务间的同步。
互斥
共享资源称为临界资源(Critical Resource) 。这种访问共享资源的排他性就是互斥。
临界区
每个任务中访问共享资源的那段程序称为临界区(Critical Section) , 因为共享资源的访问是要互斥的。 在临界区不允许任务切换, 这是最根本的原则。
任务事件
信号量:建立create,请求pend,释放post
互斥信号量
事件标志组
事件标志组管理的条件组合可以是多个事件都发生,也可以是多个事件中有任何一个事件发生。 尤其特别的是, 还可以是多个事件都没有发生或多个事件中有任何一个事件没有发生。
消息邮箱和消息队列
消息队列实质上是消息邮箱的队列
中断和时钟
内存管理
μC/OS-II中, 采用分区的方式管理内存, 即将连续的大块内存按分区来管理, 每个系统中有数个这样的分区,每个分区又包含数个内存块, 每个内存块大小相同。
1.什么是操作系统, 什么是实时操作系统, 实时操作系统
应该具有哪些特性?
§ 2.什么是任务, 任务和程序有什么区别? 任务都有哪些状
态?
§ 3.编写一个可重入函数, 实现将整数转换字符串。 说明为
什么该函数是可重入的。
§ 4.什么是不可剥夺内核和可剥夺内核, μC/OS为什么采用
可剥夺内核?
§ 5.操作系统中的事件管理都包括哪些, 并一一加以论述。
任务管理
任务管理数据结构
任务管理的数据结构包括任务控制块, 任务空闲链表和任务就绪链表, 任务优先级指针表, 任务堆栈等, 是μC/OS-II内核的核心部分之一。
任务控制块
任务控制块是任务管理的核心数据结构, 操作系统在启动的时候, 首先要在内存中创建一定是数量的任务控制块。 任务控制块的最大数量等于操作系统能同时管理的最多任务数 。
typedef struct os_tcb {
OS_STK *OSTCBStkPtr; /* Pointer to current top of stack */
#if OS_TASK_CREATE_EXT_EN > 0u
void *OSTCBExtPtr; /* Pointer to user definable data for TCB extension */
OS_STK *OSTCBStkBottom; /* Pointer to bottom of stack */
INT32U OSTCBStkSize; /* Size of task stack (in number of stack elements) */
INT16U OSTCBOpt; /* Task options as passed by OSTaskCreateExt() */
INT16U OSTCBId; /* Task ID (0..65535) */
#endif
struct os_tcb *OSTCBNext; /* Pointer to next TCB in the TCB list */
struct os_tcb *OSTCBPrev; /* Pointer to previous TCB in the TCB list */
#if (OS_EVENT_EN)
OS_EVENT *OSTCBEventPtr; /* Pointer to event control block */
#endif
#if (OS_EVENT_EN) && (OS_EVENT_MULTI_EN > 0u)
OS_EVENT **OSTCBEventMultiPtr; /* Pointer to multiple event control blocks */
#endif
#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
void *OSTCBMsg; /* Message received from OSMboxPost() or OSQPost() */
#endif
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
#if OS_TASK_DEL_EN > 0u
OS_FLAG_NODE *OSTCBFlagNode; /* Pointer to event flag node */
#endif
OS_FLAGS OSTCBFlagsRdy; /* Event flags that made task ready to run */
#endif
INT32U OSTCBDly; /* Nbr ticks to delay task or, timeout waiting for event */
INT8U OSTCBStat; /* Task status */
INT8U OSTCBStatPend; /* Task PEND status */
INT8U OSTCBPrio; /* Task priority (0 == highest) */
INT8U OSTCBX; /* 任务优先级低3位 */
INT8U OSTCBY; /* 任务优先级右移3位,相当于优先级除以8 */
OS_PRIO OSTCBBitX; /* 任务优先级在对应的任务就绪表中的位置 */
OS_PRIO OSTCBBitY; /* 任务在优先级组表中的位置 */
#if OS_TASK_DEL_EN > 0u
INT8U OSTCBDelReq; /* Indicates whether a task needs to delete itself */
#endif
#if OS_TASK_PROFILE_EN > 0u
INT32U OSTCBCtxSwCtr; /* Number of time the task was switched in */
INT32U OSTCBCyclesTot; /* Total number of clock cycles the task has been running */
INT32U OSTCBCyclesStart; /* Snapshot of cycle counter at start of task resumption */
OS_STK *OSTCBStkBase; /* Pointer to the beginning of the task stack */
INT32U OSTCBStkUsed; /* Number of bytes used from the stack */
#endif
#if OS_TASK_NAME_EN > 0u
INT8U *OSTCBTaskName;
#endif
#if OS_TASK_REG_TBL_SIZE > 0u
INT32U OSTCBRegTbl[OS_TASK_REG_TBL_SIZE];
#endif
INT32U deadline;
struct os_tcb *rdyList;
} OS_TCB;
任务控制块实体的声明如下:
OS_TCB OSTCBTbl[OS_MAX_TASKS + OS_N_SYS_TASKS]
OS_MAX_TASKS为最多的用户任务数,OS_N_SYS_TASKS为系统任务数, 一般情况下为2。ucos_ii最多可以有64个任务
空闲链表和就绪链表
μC/OS将任务控制块划分为两个链表, 就绪链表和空闲链表
系统有一个OSTCBFreeList指针指向第一个空闲链表,空闲链表只用到next,是单向链表。每当取走一个空闲链表项,指针后移。空闲表项加入OSTCBList且prev指向之前OSTCBList,OSTCBList指向的是最后一个表项。
任务优先级指针表
任务优先级指针表也就是任务优先级指针数组, 在μC/OS-II任务管理中频繁使用,代码中随处可见。 它是用来获取某优先级的任务的任务控制块地址。 它的定义为:
OS_TCB *OSTCBPrioTbl[OS_LOWEST_PRIO + 1]
OS_LOWEST_PRIO为最低优先级的任务的优先级, 因为低优先级的任务数值最大, 而任务优先级是从0开始的, 所以其实OS_LOWEST_PRIO + 1就是任务的数量。
任务堆栈
如果堆栈是向下增长, 也就是从高地址向低地址增长, 那么在任务刚开始创建后, 堆栈是空的。 相反, 如果堆栈是向上增长的, 栈顶在为TaskStk[0]。
任务就续表和就绪组
简单的对应关系,空闲任务63,统计任务62。
设置任务就绪:
OSRdyGrp |= OSMapTbl[prio>>3];
OSRdyTbl[prio>>3] |= OSMapTbl[prio&0x07];
INT8U const OSMapTbl[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};
获取最高优先级
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)(y<<3) + OSUnMapTbl[OSRdyTbl[y]]);
INT8U const OSUnMapTbl[] = {...};
任务控制块初始化
INT8U OS_TCBInit (INT8U prio, //被创建任务的优先级
OS_STK *ptos, //任务堆栈栈顶的地址
OS_STK *pbos, //任务堆栈栈底的地址,如果不使用扩展,即不进行堆栈检查,就传NULL
INT16U id, //任务的ID,16位
INT32U stk_size, //堆栈的大小
void *pext, //任务控制块的扩展块的地址
INT16U opt); //其他选项
INT8U OS_TCBInit (INT8U prio,
OS_STK *ptos,
OS_STK *pbos,
INT16U id,
INT32U stk_size,
void *pext,
INT16U opt)
{
OS_TCB *ptcb;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_TASK_REG_TBL_SIZE > 0u
INT8U i;
#endif
OS_ENTER_CRITICAL(); //进入临界区,关中断
ptcb = OSTCBFreeList; /* Get a free TCB from the free TCB list */
if (ptcb != (OS_TCB *)0) {
OSTCBFreeList = ptcb->OSTCBNext; /* Update pointer to free TCB list */
OS_EXIT_CRITICAL();
ptcb->OSTCBStkPtr = ptos; /* Load Stack pointer in TCB */
ptcb->OSTCBPrio = prio; /* Load task priority into TCB */
ptcb->OSTCBStat = OS_STAT_RDY; /* Task is ready to run */
ptcb->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
ptcb->OSTCBDly = 0u; /* Task is not delayed */
#if OS_TASK_CREATE_EXT_EN > 0u
ptcb->OSTCBExtPtr = pext; /* Store pointer to TCB extension */
ptcb->OSTCBStkSize = stk_size; /* Store stack size */
ptcb->OSTCBStkBottom = pbos; /* Store pointer to bottom of stack */
ptcb->OSTCBOpt = opt; /* Store task options */
ptcb->OSTCBId = id; /* Store task ID */
#else
pext = pext; /* Prevent compiler warning if not used */
stk_size = stk_size;
pbos = pbos;
opt = opt;
id = id;
#endif
#if OS_TASK_DEL_EN > 0u
ptcb->OSTCBDelReq = OS_ERR_NONE;
#endif
#if OS_LOWEST_PRIO <= 63u /* Pre-compute X, Y */
ptcb->OSTCBY = (INT8U)(prio >> 3u);
ptcb->OSTCBX = (INT8U)(prio & 0x07u);
#else /* Pre-compute X, Y */
ptcb->OSTCBY = (INT8U)((INT8U)(prio >> 4u) & 0xFFu);
ptcb->OSTCBX = (INT8U) (prio & 0x0Fu);
#endif
/* Pre-compute BitX and BitY */
ptcb->OSTCBBitY = (OS_PRIO)(1uL << ptcb->OSTCBY);
ptcb->OSTCBBitX = (OS_PRIO)(1uL << ptcb->OSTCBX);
#if (OS_EVENT_EN)
ptcb->OSTCBEventPtr = (OS_EVENT *)0; /* Task is not pending on an event */
#if (OS_EVENT_MULTI_EN > 0u)
ptcb->OSTCBEventMultiPtr = (OS_EVENT **)0; /* Task is not pending on any events */
#endif
#endif
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u) && (OS_TASK_DEL_EN > 0u)
ptcb->OSTCBFlagNode = (OS_FLAG_NODE *)0; /* Task is not pending on an event flag */
#endif
#if (OS_MBOX_EN > 0u) || ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u))
ptcb->OSTCBMsg = (void *)0; /* No message received */
#endif
#if OS_TASK_PROFILE_EN > 0u
ptcb->OSTCBCtxSwCtr = 0uL; /* Initialize profiling variables */
ptcb->OSTCBCyclesStart = 0uL;
ptcb->OSTCBCyclesTot = 0uL;
ptcb->OSTCBStkBase = (OS_STK *)0;
ptcb->OSTCBStkUsed = 0uL;
#endif
#if OS_TASK_NAME_EN > 0u
ptcb->OSTCBTaskName = (INT8U *)(void *)"?";
#endif
#if OS_TASK_REG_TBL_SIZE > 0u /* Initialize the task variables */
for (i = 0u; i < OS_TASK_REG_TBL_SIZE; i++) {
ptcb->OSTCBRegTbl[i] = 0u;
}
#endif
OSTCBInitHook(ptcb);
OSTaskCreateHook(ptcb); /* Call user defined hook */
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = ptcb;
ptcb->OSTCBNext = OSTCBList; /* Link into TCB chain */
ptcb->OSTCBPrev = (OS_TCB *)0;
if (OSTCBList != (OS_TCB *)0) {
OSTCBList->OSTCBPrev = ptcb;
}
OSTCBList = ptcb;
OSRdyGrp |= ptcb->OSTCBBitY; /* Make task ready to run */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OSTaskCtr++; /* Increment the #tasks counter */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NO_MORE_TCB);
}
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = ptcb;
ptcb->OSTCBNext = OSTCBList; /* Link into TCB chain */
ptcb->OSTCBPrev = (OS_TCB *)0;
if (OSTCBList != (OS_TCB )0) {
OSTCBList->OSTCBPrev = ptcb;
}
OSTCBList = ptcb;
OSRdyGrp |= ptcb->OSTCBBitY; / Make task ready to run /
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OSTaskCtr++; / Increment the #tasks counter */
OS_EXIT_CRITICAL();
操作系统初始化
OSInit()
操作系统初始化函数OS_INIT是操作系统在开始运行的最初, 对全局变量、 任务控制块、 就绪表、 事件及消息队列等重要数据结构进行的初始化操作,并创建空闲任务、 统计任务等系统任务。 该函数必须在创建用户对象及调用OSStart()启动实时任务调度之前运行
void OSInit (void)
{
OSInitHookBegin(); /* Call port specific initialization code */
OS_InitMisc(); /* Initialize miscellaneous variables */
OS_InitRdyList(); /* Initialize the Ready List */
OS_InitTCBList(); /* Initialize the free list of OS_TCBs */
OS_InitEventList(); /* Initialize the free list of OS_EVENTs */
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
OS_FlagInit(); /* Initialize the event flag structures */
#endif
#if (OS_MEM_EN > 0u) && (OS_MAX_MEM_PART > 0u)
OS_MemInit(); /* Initialize the memory manager */
#endif
#if (OS_Q_EN > 0u) && (OS_MAX_QS > 0u)
OS_QInit(); /* Initialize the message queue structures */
#endif
OS_InitTaskIdle(); /* Create the Idle Task */
#if OS_TASK_STAT_EN > 0u
OS_InitTaskStat(); /* Create the Statistic Task */
#endif
/*...*/
}
OS_InitMisc()
实现对操作系统一些混杂的全局变量的初始化
static void OS_InitMisc (void)
{
#if OS_TIME_GET_SET_EN > 0u
OSTime = 0uL; /* Clear the 32-bit system clock */
#endif
OSIntNesting = 0u; /* Clear the interrupt nesting counter */
OSLockNesting = 0u; /* Clear the scheduling lock counter */
OSTaskCtr = 0u; /* Clear the number of tasks */
OSRunning = OS_FALSE; /* Indicate that multitasking not started */
OSCtxSwCtr = 0u; /* Clear the context switch counter */
OSIdleCtr = 0uL; /* Clear the 32-bit idle counter */
#if OS_TASK_STAT_EN > 0u
OSIdleCtrRun = 0uL;
OSIdleCtrMax = 0uL;
OSStatRdy = OS_FALSE; /* Statistic task is not ready */
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
OSSafetyCriticalStartFlag = OS_FALSE; /* Still allow creation of objects */
#endif
}
OS_InitRdyList()
对就绪表进行初始化的工作
static void OS_InitRdyList (void)
{
INT8U i;
OSRdyGrp = 0u; /* Clear the ready list */
for (i = 0u; i < OS_RDY_TBL_SIZE; i++) {
OSRdyTbl[i] = 0u;
}
OSPrioCur = 0u;
OSPrioHighRdy = 0u;
OSTCBHighRdy = (OS_TCB *)0;
OSTCBCur = (OS_TCB *)0;
}
OS_InitTCBList()
控制块链表初始化
static void OS_InitTCBList (void)
{
INT8U ix;
INT8U ix_next;
OS_TCB *ptcb1;
OS_TCB *ptcb2;
OS_MemClr((INT8U *)&OSTCBTbl[0], sizeof(OSTCBTbl)); /* Clear all the TCBs */
OS_MemClr((INT8U *)&OSTCBPrioTbl[0], sizeof(OSTCBPrioTbl)); /* Clear the priority table */
for (ix = 0u; ix < (OS_MAX_TASKS + OS_N_SYS_TASKS - 1u); ix++) { /* Init. list of free TCBs */
ix_next = ix + 1u;
ptcb1 = &OSTCBTbl[ix];
ptcb2 = &OSTCBTbl[ix_next];
ptcb1->OSTCBNext = ptcb2;
#if OS_TASK_NAME_EN > 0u
ptcb1->OSTCBTaskName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
}
ptcb1 = &OSTCBTbl[ix];
ptcb1->OSTCBNext = (OS_TCB *)0; /* Last OS_TCB */
#if OS_TASK_NAME_EN > 0u
ptcb1->OSTCBTaskName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
OSTCBList = (OS_TCB *)0; /* TCB lists initializations */
OSTCBFreeList = &OSTCBTbl[0];
}
OS_InitTaskIdle()
创建操作系统空闲任务
static void OS_InitTaskIdle (void)
{
#if OS_TASK_NAME_EN > 0u
INT8U err;
#endif
#if OS_TASK_CREATE_EXT_EN > 0u
#if OS_STK_GROWTH == 1u
(void)OSTaskCreateExt(OS_TaskIdle,
(void *)0, /* No arguments passed to OS_TaskIdle() */
&OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],/* Set Top-Of-Stack */
OS_TASK_IDLE_PRIO, /* Lowest priority level */
OS_TASK_IDLE_ID,
&OSTaskIdleStk[0], /* Set Bottom-Of-Stack */
OS_TASK_IDLE_STK_SIZE,
(void *)0, /* No TCB extension */
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack */
#else
(void)OSTaskCreateExt(OS_TaskIdle,
(void *)0, /* No arguments passed to OS_TaskIdle() */
&OSTaskIdleStk[0], /* Set Top-Of-Stack */
OS_TASK_IDLE_PRIO, /* Lowest priority level */
OS_TASK_IDLE_ID,
&OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],/* Set Bottom-Of-Stack */
OS_TASK_IDLE_STK_SIZE,
(void *)0, /* No TCB extension */
OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR);/* Enable stack checking + clear stack */
#endif
#else
#if OS_STK_GROWTH == 1u
(void)OSTaskCreate(OS_TaskIdle,
(void *)0,
&OSTaskIdleStk[OS_TASK_IDLE_STK_SIZE - 1u],
OS_TASK_IDLE_PRIO);
#else
(void)OSTaskCreate(OS_TaskIdle,
(void *)0,
&OSTaskIdleStk[0],
OS_TASK_IDLE_PRIO);
#endif
#endif
#if OS_TASK_NAME_EN > 0u
OSTaskNameSet(OS_TASK_IDLE_PRIO, (INT8U *)(void *)"uC/OS-II Idle", &err);
#endif
}
任务创建
任务创建函数分两种,一种是基本的创建函数OSTaskCreate,另一种是扩展的任务创建函数OSTaskCreateExt。
任务创建代码解析
创建任务OS_TaskCreat
INT8U OSTaskCreate (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT8U prio)
{
OS_STK *psp;
INT8U err;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (prio > OS_LOWEST_PRIO) { /* Make sure priority is within allowable range */
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) { /* Make sure we don't create the task from within an ISR */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority */
OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ... */
/* ... the same thing until task is created. */
OS_EXIT_CRITICAL();
psp = OSTaskStkInit(task, p_arg, ptos, 0u); /* Initialize the task's stack */
err = OS_TCBInit(prio, psp, (OS_STK *)0, 0u, 0u, (void *)0, 0u);
if (err == OS_ERR_NONE) {
if (OSRunning == OS_TRUE) { /* Find highest priority task if multitasking has started */
OS_Sched();
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0;/* Make this priority available to others */
OS_EXIT_CRITICAL();
}
return (err);
}
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO_EXIST);
}
堆栈初始化函数OSTaskStkInit的一个版本
OS_STK *OSTaskStkInit (void (*task)(void *pd), void *pdata, OS_STK *ptos, INT16U opt)
{
INT32U *stk; //console �¼Ĵ���Ϊ32λ��
opt = opt; /* 'opt' is not used, prevent warning */
stk = (INT32U *)ptos; /* Load stack pointer */
*--stk = (INT32U)pdata; /* Simulate call to function with argument */
*--stk = (INT32U)0X00000000;
*--stk = (INT32U)task; /* Put pointer to task on top of stack */
*--stk = (INT32U)0x00000202; /* EFL = 0X00000202 */
*--stk = (INT32U)0xAAAAAAAA; /* EAX = 0xAAAAAAAA */
*--stk = (INT32U)0xCCCCCCCC; /* ECX = 0xCCCCCCCC */
*--stk = (INT32U)0xDDDDDDDD; /* EDX = 0xDDDDDDDD */
*--stk = (INT32U)0xBBBBBBBB; /* EBX = 0xBBBBBBBB */
*--stk = (INT32U)0x00000000; /* ESP = 0x00000000 esp�������⣬��Ϊ */
*--stk = (INT32U)0x11111111; /* EBP = 0x11111111 */
*--stk = (INT32U)0x22222222; /* ESI = 0x22222222 */
*--stk = (INT32U)0x33333333; /* EDI = 0x33333333 */
return ((OS_STK *)stk);
}
OS_TaskCreat的流程 :在编译环境的初始化 (虚拟机初始化),OS初始化,之后进行,创建完任务后,OSStart()
创建任务OS_TaskCreatExt
与OS_TaskCreat主要的区别就是堆栈的清理
INT8U OSTaskCreateExt (void (*task)(void *p_arg),
void *p_arg,
OS_STK *ptos,
INT8U prio,
INT16U id,
OS_STK *pbos,
INT32U stk_size,
void *pext,
INT16U opt)
{
OS_STK *psp;
INT8U err;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (prio > OS_LOWEST_PRIO) { /* Make sure priority is within allowable range */
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) { /* Make sure we don't create the task from within an ISR */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_CREATE_ISR);
}
if (OSTCBPrioTbl[prio] == (OS_TCB *)0) { /* Make sure task doesn't already exist at this priority */
OSTCBPrioTbl[prio] = OS_TCB_RESERVED;/* Reserve the priority to prevent others from doing ... */
/* ... the same thing until task is created. */
OS_EXIT_CRITICAL();
#if (OS_TASK_STAT_STK_CHK_EN > 0u)
OS_TaskStkClr(pbos, stk_size, opt); /* Clear the task stack (if needed) */
#endif
psp = OSTaskStkInit(task, p_arg, ptos, opt); /* Initialize the task's stack */
err = OS_TCBInit(prio, psp, pbos, id, stk_size, pext, opt);
if (err == OS_ERR_NONE) {
if (OSRunning == OS_TRUE) { /* Find HPT if multitasking has started */
OS_Sched();
}
} else {
OS_ENTER_CRITICAL();
OSTCBPrioTbl[prio] = (OS_TCB *)0; /* Make this priority avail. to others */
OS_EXIT_CRITICAL();
}
return (err);
}
OS_EXIT_CRITICAL();
return (OS_ERR_PRIO_EXIST);
}
OS_TaskCreatExt的流程
任务删除
删除任务是创建任务的逆过程,
任务创建设置就绪表, 就绪组, 任务删除则取消设置;
任务创建将任务控制块从空闲链表移到就绪链表; 删除操作则相反。
INT8U OSTaskDel (INT8U prio)
{
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
OS_FLAG_NODE *pnode;
#endif
OS_TCB *ptcb;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
if (OSIntNesting > 0u) { /* See if trying to delete from ISR */
return (OS_ERR_TASK_DEL_ISR);
}
if (prio == OS_TASK_IDLE_PRIO) { /* Not allowed to delete idle task */
return (OS_ERR_TASK_DEL_IDLE);
}
#if OS_ARG_CHK_EN > 0u
if (prio >= OS_LOWEST_PRIO) { /* Task priority valid ? */
if (prio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
#endif
/*$PAGE*/
OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { /* See if requesting to delete self */
prio = OSTCBCur->OSTCBPrio; /* Set priority to delete to current */
}
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { /* Task to delete must exist */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
if (ptcb == OS_TCB_RESERVED) { /* Must not be assigned to Mutex */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_DEL);
}
OSRdyTbl[ptcb->OSTCBY] &= (OS_PRIO)~ptcb->OSTCBBitX;
if (OSRdyTbl[ptcb->OSTCBY] == 0u) { /* Make task not ready */
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
#if (OS_EVENT_EN)
if (ptcb->OSTCBEventPtr != (OS_EVENT *)0) {
OS_EventTaskRemove(ptcb, ptcb->OSTCBEventPtr); /* Remove this task from any event wait list */
}
#if (OS_EVENT_MULTI_EN > 0u)
if (ptcb->OSTCBEventMultiPtr != (OS_EVENT **)0) { /* Remove this task from any events' wait lists*/
OS_EventTaskRemoveMulti(ptcb, ptcb->OSTCBEventMultiPtr);
}
#endif
#endif
#if (OS_FLAG_EN > 0u) && (OS_MAX_FLAGS > 0u)
pnode = ptcb->OSTCBFlagNode;
if (pnode != (OS_FLAG_NODE *)0) { /* If task is waiting on event flag */
OS_FlagUnlink(pnode); /* Remove from wait list */
}
#endif
ptcb->OSTCBDly = 0u; /* Prevent OSTimeTick() from updating */
ptcb->OSTCBStat = OS_STAT_RDY; /* Prevent task from being resumed */
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
if (OSLockNesting < 255u) { /* Make sure we don't context switch */
OSLockNesting++;
}
OS_EXIT_CRITICAL(); /* Enabling INT. ignores next instruc. */
OS_Dummy(); /* ... Dummy ensures that INTs will be */
OS_ENTER_CRITICAL(); /* ... disabled HERE! */
if (OSLockNesting > 0u) { /* Remove context switch lock */
OSLockNesting--;
}
OSTaskDelHook(ptcb); /* Call user defined hook */
OSTaskCtr--; /* One less task being managed */
OSTCBPrioTbl[prio] = (OS_TCB *)0; /* Clear old priority entry */
if (ptcb->OSTCBPrev == (OS_TCB *)0) { /* Remove from TCB chain */
ptcb->OSTCBNext->OSTCBPrev = (OS_TCB *)0;
OSTCBList = ptcb->OSTCBNext;
} else {
ptcb->OSTCBPrev->OSTCBNext = ptcb->OSTCBNext;
ptcb->OSTCBNext->OSTCBPrev = ptcb->OSTCBPrev;
}
ptcb->OSTCBNext = OSTCBFreeList; /* Return TCB to free TCB list */
OSTCBFreeList = ptcb;
#if OS_TASK_NAME_EN > 0u
ptcb->OSTCBTaskName = (INT8U *)(void *)"?";
#endif
OS_EXIT_CRITICAL();
if (OSRunning == OS_TRUE) {
OS_Sched(); /* Find new highest priority task */
}
return (OS_ERR_NONE);
}
#endif
OS_Dummy();
中断一定的响应时间,用于把任务控制块还回空闲链表里,把相应的就绪组和就续表去掉就绪状态。
**请求删除
通知对方任务, 告诉它要删除你了, 请任务自己删除自己是一种更好的做法。 因为这么做, 任务可以在删除自己之前先放弃自己使用的资源, 如缓冲区、 信号量、 邮箱、 队列等。
OsTaskDelReq名称虽然是请求, 却是集请求和响应于一段代码的。 该代码的功能是:
- 请求删除某任务
- 查看是否有任务要删除自己
INT8U OSTaskDelReq (INT8U prio)
{
INT8U stat;
OS_TCB *ptcb;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
if (prio == OS_TASK_IDLE_PRIO) { /* Not allowed to delete idle task */
return (OS_ERR_TASK_DEL_IDLE);
}
#if OS_ARG_CHK_EN > 0u
if (prio >= OS_LOWEST_PRIO) { /* Task priority valid ? */
if (prio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
#endif
if (prio == OS_PRIO_SELF) { /* See if a task is requesting to ... */
OS_ENTER_CRITICAL(); /* ... this task to delete itself */
stat = OSTCBCur->OSTCBDelReq; /* Return request status to caller */
OS_EXIT_CRITICAL();
return (stat);
}
OS_ENTER_CRITICAL();
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { /* Task to delete must exist */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST); /* Task must already be deleted */
}
if (ptcb == OS_TCB_RESERVED) { /* Must NOT be assigned to a Mutex */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_DEL);
}
ptcb->OSTCBDelReq = OS_ERR_TASK_DEL_REQ; /* Set flag indicating task to be DEL. */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
#endif
任务挂起和恢复
OSTaskSuspend将任务阻塞, 也就是被剥夺CPU的使用权而暂时终止运行, 转到阻塞状态。 通过OSTaskSuspend将任务转到阻塞态被称为挂起任务。被挂起的任务不能运行, 直到其他任务以该任务的优先级作为参数调用OSTaskResume来恢复它, 才能将该任务的状态重新设置为就绪状态。
suspend为阻塞态,挂起态通过中断到达
任务挂起
#if OS_TASK_SUSPEND_EN > 0u
INT8U OSTaskSuspend (INT8U prio)
{
BOOLEAN self;
OS_TCB *ptcb;
INT8U y;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (prio == OS_TASK_IDLE_PRIO) { /* Not allowed to suspend idle task */
return (OS_ERR_TASK_SUSPEND_IDLE);
}
if (prio >= OS_LOWEST_PRIO) { /* Task priority valid ? */
if (prio != OS_PRIO_SELF) {
return (OS_ERR_PRIO_INVALID);
}
}
#endif
OS_ENTER_CRITICAL();
if (prio == OS_PRIO_SELF) { /* See if suspend SELF */
prio = OSTCBCur->OSTCBPrio;
self = OS_TRUE;
} else if (prio == OSTCBCur->OSTCBPrio) { /* See if suspending self */
self = OS_TRUE;
} else {
self = OS_FALSE; /* No suspending another task */
}
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { /* Task to suspend must exist */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_SUSPEND_PRIO);
}
if (ptcb == OS_TCB_RESERVED) { /* See if assigned to Mutex */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
y = ptcb->OSTCBY;
OSRdyTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; /* Make task not ready */
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~ptcb->OSTCBBitY;
}
ptcb->OSTCBStat |= OS_STAT_SUSPEND; /* Status of task is 'SUSPENDED' */
OS_EXIT_CRITICAL();
if (self == OS_TRUE) { /* Context switch only if SELF */
OS_Sched(); /* Find new highest priority task */
}
return (OS_ERR_NONE);
}
#endif
有一个检查是否·正在·挂起自己这一步
然后清除就续表相关位,注意如果一个就绪组对应的所有就续表都清零的话,就绪组相关位也要清除,有一个判断的过程。
任务恢复
INT8U OSTaskResume (INT8U prio)
{
OS_TCB *ptcb;
#if OS_CRITICAL_METHOD == 3u /* Storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (prio >= OS_LOWEST_PRIO) { /* Make sure task priority is valid */
return (OS_ERR_PRIO_INVALID);
}
#endif
OS_ENTER_CRITICAL();
ptcb = OSTCBPrioTbl[prio];
if (ptcb == (OS_TCB *)0) { /* Task to suspend must exist */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_RESUME_PRIO);
}
if (ptcb == OS_TCB_RESERVED) { /* See if assigned to Mutex */
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST);
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) != OS_STAT_RDY) { /* Task must be suspended */
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_SUSPEND; /* Remove suspension */
if (ptcb->OSTCBStat == OS_STAT_RDY) { /* See if task is now ready */
if (ptcb->OSTCBDly == 0u) {
OSRdyGrp |= ptcb->OSTCBBitY; /* Yes, Make task ready to run */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OS_EXIT_CRITICAL();
if (OSRunning == OS_TRUE) {
OS_Sched(); /* Find new highest priority task */
}
} else {
OS_EXIT_CRITICAL();
}
} else { /* Must be pending on event */
OS_EXIT_CRITICAL();
}
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_SUSPENDED);
}
取消挂起标志,记得如果任务正常,而且没有延时,就要进入就绪了。
任务调度和多任务的启动
任务级调度执行的功能是如果正在运行的任务不是最高优先级任务或者即将被阻塞, 需要选择一个优先级最高的就绪任务运行
OS_Sched
OS_Sched中首先判断是不是具有任务调度的条件即:
- 无中断
- 判断调度器没有上锁,满足条件后, 调用OS_SchedNew() 给全局变量OSPrioHighRdy赋值, 即给最高优先级任务赋值
- 如果优先级最高的就绪任务不是当前在运行的任务, 调用OS_Task_SW() 进行任务切换
void OS_Sched (void)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
OS_ENTER_CRITICAL();
if (OSIntNesting == 0u) { /* Schedule only if all ISRs done and ... */
if (OSLockNesting == 0u) { /* ... scheduler is not locked */
OS_SchedNew();
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
#if OS_TASK_PROFILE_EN > 0u
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSCtxSwCtr++; /* Increment context switch counter */
OS_TASK_SW(); /* Perform a context switch */
}
}
}
OS_EXIT_CRITICAL();
}
OS_Schednew
static void OS_SchedNew (void)
{
INT8U y;
y = OSUnMapTbl[OSRdyGrp];
OSPrioHighRdy = (INT8U)((y << 3u) + OSUnMapTbl[OSRdyTbl[y]]);
}
***OS_TASK_SW
在OS_Task_SW中, 首先将CPU寄存器中的内容压入被换出的任务堆栈中, 然后将被换入的任务的堆栈内容放入到CPU寄存器, 在调度结束恢复时, 从控制器中取出上次保存的任务的地址, 完成了一次任务级调度。
#define OS_TASK_SW() OSCtxSw()
void OSCtxSw(void)
{
_asm{
lea eax, nextstart ;任务切换回来后从nextstart开始
push eax
pushfd ;标志寄存器的值
pushad ;保存EAX -- EDI
mov ebx, [OSTCBCur]
mov [ebx], esp ;把堆栈入口的地址保存到当前TCB结构中
}
OSTaskSwHook();
OSTCBCur = OSTCBHighRdy;
OSPrioCur = OSPrioHighRdy;
_asm{
mov ebx, [OSTCBCur]
mov esp, [ebx] ;得到OSTCBHighRdy的esp
popad ;恢复所有通用寄存器,共8个
popfd ;恢复标志寄存器
ret ;跳转到指定任务运行
}
nextstart: //任务切换回来的运行地址
return;
}
外部中断
如果正在运行的任务没有关闭中断,在中断到来的时候, 操作系统响应中断, 进入中断服务程序。这时候任务的运行环境还没有保存, 因此需要将任务的运行环境保存。这时候任务由于中断的到来而进入挂起态。
中断进入
void OSIntEnter (void)
{
if (OSRunning == OS_TRUE) {
if (OSIntNesting < 255u) {
OSIntNesting++; /* Increment ISR nesting level */
}
}
}
中断退出
void OSIntExit (void)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
if (OSRunning == OS_TRUE) {
OS_ENTER_CRITICAL();
if (OSIntNesting > 0u) { /* Prevent OSIntNesting from wrapping */
OSIntNesting--;
}
if (OSIntNesting == 0u) { /* Reschedule only if all ISRs complete ... */
if (OSLockNesting == 0u) { /* ... and not locked. */
OS_SchedNew();
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy];
if (OSPrioHighRdy != OSPrioCur) { /* No Ctx Sw if current task is highest rdy */
#if OS_TASK_PROFILE_EN > 0u
OSTCBHighRdy->OSTCBCtxSwCtr++; /* Inc. # of context switches to this task */
#endif
OSCtxSwCtr++; /* Keep track of the number of ctx switches */
OSIntCtxSw(); /* Perform interrupt level ctx switch */
}
}
}
OS_EXIT_CRITICAL();
}
}
时钟中断OSTimeTick()
通过OSTimeTick()调用中断
void OSTimeTick (void)
{
OS_TCB *ptcb;
BOOLEAN step;
OSTimeTickHook(); /*调用用户钩子函数,默认是空函数 */
#if OS_TIME_GET_SET_EN > 0u
OS_ENTER_CRITICAL(); /* Update the 32-bit tick counter */
OSTime++;
OS_EXIT_CRITICAL();
#endif
if (OSRunning == OS_TRUE) {
#if OS_TICK_STEP_EN > 0u
switch (OSTickStepState) { /* Determine whether we need to process a tick */
case OS_TICK_STEP_DIS: /* Yes, stepping is disabled */
step = OS_TRUE;
break;
case OS_TICK_STEP_WAIT: /* No, waiting for uC/OS-View to set ... */
step = OS_FALSE; /* .. OSTickStepState to OS_TICK_STEP_ONCE */
break;
case OS_TICK_STEP_ONCE: /* Yes, process tick once and wait for next ... */
step = OS_TRUE; /* ... step command from uC/OS-View */
OSTickStepState = OS_TICK_STEP_WAIT;
break;
default: /* Invalid case, correct situation */
step = OS_TRUE;
OSTickStepState = OS_TICK_STEP_DIS;
break;
}
if (step == OS_FALSE) { /* Return if waiting for step command */
return;
}
#endif
ptcb = OSTCBList; /* Point at first TCB in TCB list */
while (ptcb->OSTCBPrio != OS_TASK_IDLE_PRIO) { /* Go through all TCBs in TCB list */
OS_ENTER_CRITICAL();
if (ptcb->OSTCBDly != 0u) { /* No, Delayed or waiting for event with TO */
ptcb->OSTCBDly--; /* Decrement nbr of ticks to end of delay */
if (ptcb->OSTCBDly == 0u) { /* Check for timeout */
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= (INT8U)~(INT8U)OS_STAT_PEND_ANY; /* Yes, Clear status flag */
ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */
} else {
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */
OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
}
}
}
ptcb = ptcb->OSTCBNext; /* Point at next TCB in TCB list */
OS_EXIT_CRITICAL();
}
}
}
在OSTimeTick中, 遍历每一个任务的ptcb->OSTCBDly是否设置了时间延时或者时间等待延时,如果延时时间不为0, 则将延时时间片-1, 表明经过了一个时间片,如果时间片为0, 检查任务是在等待某一事件还是单纯地延时,如果任务不是被挂起, 则延时时间到, 更新就绪表, 将任务状态设置为就绪状态, 循环进行
多任务启动
多任务启动的代码是内核中的OSStart()函数, 在OSStart之前, 必须已经执行了系统初始化函数OSInit, 并且至少创建了1个以上的任务
- 确认操作系统多任务没有启动
- 确定优先级最高的就绪任务的优先级
- 执行高优优先级任务的代码OSStartHighRdy()
其实就是把最高优先级任务复制到当前任务,然后启动当前任务
void OSStart (void)
{
if (OSRunning == OS_FALSE) {
OS_SchedNew(); /* Find highest priority's task priority number */
OSPrioCur = OSPrioHighRdy;
OSTCBHighRdy = OSTCBPrioTbl[OSPrioHighRdy]; /* Point to highest priority task ready to run */
OSTCBCur = OSTCBHighRdy;
OSStartHighRdy(); /* Execute target specific code to start task */
}
}
void OSStartHighRdy(void)
{
OSTaskSwHook();
OSRunning = TRUE;
_asm{
mov ebx, [OSTCBCur] ;OSTCBCur结构的第一个参数就是esp
mov esp, [ebx] ;恢复堆栈
popad ;恢复所有通用寄存器,共8个
popfd ;恢复标志寄存器
ret ;ret 指令相当于pop eip 但保护模式下不容许使用eip
;永远都不返回
}
}
特殊任务
空闲任务
空闲任务是μC/OS-II 的系统任务, 因为它占据了最低优先级63, 所以只有在其他的任务都因为等待事件的发生而被阻塞的时候才能得到运行
- 从空闲任务的代码可见, 空闲任务除了不停地将空闲计数器OSIdleCtr的值加1
之外, 几乎什么都没有做。 - 当没有任何其他任务能够运行的时候, 操作系统就会执行这段代码
- 而OSTaskIdleHook默认的情况下也只是一个空函数, 没有特殊需要我们也不需要去填写它, 该函数的另一作用就是占据一点时间, 给系统足够的时间响应中断。
void OS_TaskIdle (void *p_arg)
{
p_arg = p_arg; /* Prevent compiler warning for not using 'p_arg' */
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtr++;
OS_EXIT_CRITICAL();
OSTaskIdleHook(); /* Call user definable HOOK */
}
}
统计任务
统计任务OS_TaskStat是μC/OS-II的另一个重要的系统任务, 我们可以通过宏设置取消统计任务, 但一般情况下我们不会这么做, 因为统计任务执行的统计工作是比较重要的。 统计任务的主要功能就是计算CPU的利用率。 如果没有统计任务, 我们就不可知道多任务环境下系统的运行情况是否良好
涉及到的全局变量
#if OS_TASK_STAT_EN > 0u
OS_EXT INT8U OSCPUUsage; /* Percentage of CPU used */
OS_EXT INT32U OSIdleCtrMax; /* Max. value that idle ctr can take in 1 sec. */
OS_EXT INT32U OSIdleCtrRun; /* Val. reached by idle ctr at run time in 1 sec. */
OS_EXT BOOLEAN OSStatRdy; /* Flag indicating that the statistic task is rdy */
OS_EXT OS_STK OSTaskStatStk[OS_TASK_STAT_STK_SIZE]; /* Statistics task stack */
#endif
初始化函数OSStatInit
void OSStatInit (void)
{
OSTimeDly(2u); /* Synchronize with clock tick */
OS_ENTER_CRITICAL();
OSIdleCtr = 0uL; /* Clear idle counter */
OS_EXIT_CRITICAL();
OSTimeDly(OS_TICKS_PER_SEC / 10u); /* Determine MAX. idle counter value for 1/10 second */
OS_ENTER_CRITICAL();
OSIdleCtrMax = OSIdleCtr; /* Store maximum idle counter count in 1/10 second */
//printf("空闲计数最大=%d",OSIdleCtrMax);
OSStatRdy = OS_TRUE;
OS_EXIT_CRITICAL();
}
OS_TaskStat
void OS_TaskStat (void *p_arg)
{
p_arg = p_arg; /* Prevent compiler warning for not using 'p_arg' */
while (OSStatRdy == OS_FALSE) {
OSTimeDly(2u * OS_TICKS_PER_SEC / 10u); /* Wait until statistic task is ready */
}
OSIdleCtrMax /= 100uL;
if (OSIdleCtrMax == 0uL) {
OSCPUUsage = 0u;
#if OS_TASK_SUSPEND_EN > 0u
(void)OSTaskSuspend(OS_PRIO_SELF);
#else
for (;;) {
OSTimeDly(OS_TICKS_PER_SEC);
}
#endif
}
for (;;) {
OS_ENTER_CRITICAL();
OSIdleCtrRun = OSIdleCtr; /* Obtain the of the idle counter for the past second */
OSIdleCtr = 0uL; /* Reset the idle counter for the next second */
OS_EXIT_CRITICAL();
OSCPUUsage = (INT8U)(100uL - OSIdleCtrRun / OSIdleCtrMax);
OSTaskStatHook(); /* Invoke user definable hook */
#if (OS_TASK_STAT_STK_CHK_EN > 0u) && (OS_TASK_CREATE_EXT_EN > 0u)
OS_TaskStatStkChk(); /* Check the stacks for each task */
#endif
OSTimeDly(OS_TICKS_PER_SEC / 10u); /* Accumulate OSIdleCtr for the next 1/10 second */
}
}
中断和时间管理
中断管理
同上
核心思路
如果正在运行的任务没有关闭中断,在中断到来的时候, 操作系统响应中断,进入中断服务程序。这时候任务的运行环境还没有保存, 因此需要将任务的运行环境保存。这时候任务由于中断的到来而进入挂起态。
处理流程
时钟中断服务
时钟中断服务是要依赖于中断的, 如果是单片机系统,那么就该设置为定时器中断。 用定时器中断的服务程序来完成该功能是恰当的。 对于Windows平台下的虚拟系统, 可以采用定时器触发来虚拟中断。
时间管理
时间管理的内容在代码os_time.c中, 包含了操作系统时间的设置及获取,对任务的延时, 任务按分秒延时, 取消任务的延时共5个系统调用。
主要数据结构
volatile INT32U OSTime;
任务控制块 TCB中的OSTCBDly
时间获取和设置
注意: 因为OSTime 是被保护的全局变量, 在访问的时候必须使用OS_ENTER_CRITICAL()进入临界区, 保证独占访问!
OSTimeGet
INT32U OSTimeGet (void)
{
INT32U ticks;
OS_ENTER_CRITICAL();
ticks = OSTime;
OS_EXIT_CRITICAL();
return (ticks);
}
#endif
OSTimeSet
void OSTimeSet (INT32U ticks)
{
OS_ENTER_CRITICAL();
OSTime = ticks;
OS_EXIT_CRITICAL();
}
任务延时函数OSTimeDly
任务延时函数OSTimeDly用于阻塞任务一定时间, 这个时间以参数的形式给出。 如果这个参数的值是N, 那么在N个时间片(时钟嘀嗒) 之后, 任务才能回到就绪状态获得继续运行的机会。 如果参数的值是0, 不会阻塞任务。
void OSTimeDly (INT32U ticks)
{
INT8U y;
if (OSIntNesting > 0u) { /* See if trying to call from an ISR */
return;
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked */
return;
}
if (ticks > 0u) { /* 0 means no delay! */
OS_ENTER_CRITICAL();
y = OSTCBCur->OSTCBY; /* Delay current task */
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
}
OSTCBCur->OSTCBDly = ticks; /* Load ticks in TCB */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next task to run! */
}
}
调用OSTimeDly(1) 会阻塞 <=1个时间片
任务按分秒延迟函数OSTimeDlyHMSM
OSTimeDlyHMSM从功能上来说和OSTimeDly并没有多大的差别,只是将时间单位进行了转换, 也就是说转换了以小时、 分、 秒、 毫秒为单位的时间和以时间片为单位的时间。 OSTimeDlyHMSM的参数分别是延时的小时数hours, 分钟数minutes, 秒数seconds, 和毫秒数ms
INT8U OSTimeDlyHMSM (INT8U hours,
INT8U minutes,
INT8U seconds,
INT16U ms)
{
INT32U ticks;
if (OSIntNesting > 0u) { /* See if trying to call from an ISR */
return (OS_ERR_TIME_DLY_ISR);
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked */
return (OS_ERR_SCHED_LOCKED);
}
#if OS_ARG_CHK_EN > 0u
if (hours == 0u) {
if (minutes == 0u) {
if (seconds == 0u) {
if (ms == 0u) {
return (OS_ERR_TIME_ZERO_DLY);
}
}
}
}
if (minutes > 59u) {
return (OS_ERR_TIME_INVALID_MINUTES); /* Validate arguments to be within range */
}
if (seconds > 59u) {
return (OS_ERR_TIME_INVALID_SECONDS);
}
if (ms > 999u) {
return (OS_ERR_TIME_INVALID_MS);
}
#endif
/* Compute the total number of clock ticks required.. */
/* .. (rounded to the nearest tick) */
ticks = ((INT32U)hours * 3600uL + (INT32U)minutes * 60uL + (INT32U)seconds) * OS_TICKS_PER_SEC
+ OS_TICKS_PER_SEC * ((INT32U)ms + 500uL / OS_TICKS_PER_SEC) / 1000uL;
OSTimeDly(ticks);
return (OS_ERR_NONE);
}
延时恢复函数
阻塞状态的任务即便任务的延时时间没到, 还是可以通过OSTimeDlyResume恢复该任务到就绪态。 对于因等待事件发生而阻塞的, 且设置了超时timeout时间的任务, 也可以时候OSTimeDlyResume来恢复。 对这些任务使用了OSTimeDlyResume, 就好像已经等待超时了一样! 但是, 采用OSTaskSuspend挂起的任务, 是不允许采用OSTimeDlyResume来恢复。
INT8U OSTimeDlyResume (INT8U prio)
{
OS_TCB *ptcb;
if (prio >= OS_LOWEST_PRIO) {
return (OS_ERR_PRIO_INVALID);
}
OS_ENTER_CRITICAL();
ptcb = OSTCBPrioTbl[prio]; /* Make sure that task exist */
if (ptcb == (OS_TCB *)0) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST); /* The task does not exist */
}
if (ptcb == OS_TCB_RESERVED) {
OS_EXIT_CRITICAL();
return (OS_ERR_TASK_NOT_EXIST); /* The task does not exist */
}
if (ptcb->OSTCBDly == 0u) { /* See if task is delayed */
OS_EXIT_CRITICAL();
return (OS_ERR_TIME_NOT_DLY); /* Indicate that task was not delayed */
}
ptcb->OSTCBDly = 0u; /* Clear the time delay */
if ((ptcb->OSTCBStat & OS_STAT_PEND_ANY) != OS_STAT_RDY) {
ptcb->OSTCBStat &= ~OS_STAT_PEND_ANY; /* Yes, Clear status flag */
ptcb->OSTCBStatPend = OS_STAT_PEND_TO; /* Indicate PEND timeout */
} else {
ptcb->OSTCBStatPend = OS_STAT_PEND_OK;
}
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) { /* Is task suspended? */
OSRdyGrp |= ptcb->OSTCBBitY; /* No, Make ready */
OSRdyTbl[ptcb->OSTCBY] |= ptcb->OSTCBBitX;
OS_EXIT_CRITICAL();
OS_Sched(); /* See if this is new highest priority */
} else {
OS_EXIT_CRITICAL(); /* Task may be suspended */
}
return (OS_ERR_NONE);
}
事件管理
μC/OS-II作为实时多任务操作系统, 是事件驱动的, 支持信号量, 消息等机制。
事件主要包括信号量和互斥信号量, 而事件的组合可以用事件标志组来管理 。
数据结构
事件控制块ECB
事件控制块ECB在事件管理中占据着举足轻重的作用。 虽然事件控制块ECB并没有任务控制块TCB的内容丰富, 但是在事件处理中仍然是核心的数据结构, 频繁被访问。
typedef struct os_event {
INT8U OSEventType; /* Type of event control block (see OS_EVENT_TYPE_xxxx) */
void *OSEventPtr; /* Pointer to message or queue structure */
INT16U OSEventCnt; /* Semaphore Count (not used if other EVENT type) */
OS_PRIO OSEventGrp; /* Group corresponding to tasks waiting for event to occur */
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* List of tasks waiting for event to occur */
} OS_EVENT;
OS_EVENT_TYPE
#define OS_EVENT_TYPE_UNUSED 0u
#define OS_EVENT_TYPE_MBOX 1u
#define OS_EVENT_TYPE_Q 2u
#define OS_EVENT_TYPE_SEM 3u
#define OS_EVENT_TYPE_MUTEX 4u
#define OS_EVENT_TYPE_FLAG 5u
事件等待组和事件等待表
当事件发生的时候, 操作系统会找到优先级最高的等待事件发生的任务,并将该任务就绪, 然后在事件等待组和事件等待表中取消该任务的标记。
事件控制块空闲链表
事件管理中, 将空闲的事件块链接为一个单向的链表, 事件控制块空闲链表。这个链表的形式和任务块TCB的空闲链表的形式是完全相同的。
当创建一个事件的时候, 要在事件控制块ECB空闲链表查找是否有空闲的ECB可用。 如果有, 就从链表中取出分配给事件
事件空闲链表指针OSEventFreeList的定义为:
OS_EVENT *OSEventFreeList;
管理程序
事件控制块ECB初始化
- ECB初始化函数OS_InitEventList首先清空了所有的ECB块, 也就是清空了事件表。
- 然后从0到OS_MAX_EVENTS - 1u)循环对除最后一个ECB块之外的所有ECB块进行初始化, 并顺便构建了单向的链表。
- 循环结束后最后一个ECB 块OSEventTbl[OS_MAX_EVENTS - 1]进行初始化。 最后一个事件控制块OSEventTbl[OS_MAX_EVENTS - 1]的OSEventPtr域指向空地址0, 构造完成了如图4-3所示的空闲事件控制块
链表。 - 然后将ECB空闲链表的表头地址给OSEventFreeList, 初始化完成。
static void OS_InitEventList (void)
{
#if (OS_EVENT_EN) && (OS_MAX_EVENTS > 0u)
#if (OS_MAX_EVENTS > 1u)
INT16U ix;
INT16U ix_next;
OS_EVENT *pevent1;
OS_EVENT *pevent2;
OS_MemClr((INT8U *)&OSEventTbl[0], sizeof(OSEventTbl)); /* Clear the event table */
for (ix = 0u; ix < (OS_MAX_EVENTS - 1u); ix++) { /* Init. list of free EVENT control blocks */
ix_next = ix + 1u;
pevent1 = &OSEventTbl[ix];
pevent2 = &OSEventTbl[ix_next];
pevent1->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent1->OSEventPtr = pevent2;
#if OS_EVENT_NAME_EN > 0u
pevent1->OSEventName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
}
pevent1 = &OSEventTbl[ix];
pevent1->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent1->OSEventPtr = (OS_EVENT *)0;
#if OS_EVENT_NAME_EN > 0u
pevent1->OSEventName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
OSEventFreeList = &OSEventTbl[0];
#else
OSEventFreeList = &OSEventTbl[0]; /* Only have ONE event control block */
OSEventFreeList->OSEventType = OS_EVENT_TYPE_UNUSED;
OSEventFreeList->OSEventPtr = (OS_EVENT *)0;
#if OS_EVENT_NAME_EN > 0u
OSEventFreeList->OSEventName = (INT8U *)"?"; /* Unknown name */
#endif
#endif
#endif
}
事件等待表初始化
当建立一个事件或消息, 如信号量、 邮箱、 消息队列时, 如信号量的建立函数
OSSemCreate等, 需要对事件等待表进行初始化。
OS_EventWaitListInit
pevent->OSEventGrp = 0u; /* 清空任务等待组*/
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
pevent->OSEventTbl[i] = 0u; /* 采用循环模式清空任务等待表*/
}
设置事件等待
当任务等待事件发生, 并获得事件控制块ECB后, 需要在ECB中标记任务在等待事件的发生, 才可以在事件发生时取消任务的阻塞
- 标记。 在ECB中登记本任务, 即在ECB的事件等待表中对应优先级处标记为1, 事件等待组中对应位标记为1。
- 取消标记。 在就绪表和就绪组中取消对该事件就绪的标记, 将就绪表中对应优先级处标记为0, 如果就绪表该任务所在的一组没有任务就绪, 将就绪组中的对应位标记为0。
void OS_EventTaskWait (OS_EVENT *pevent)
{
INT8U y;
OSTCBCur->OSTCBEventPtr = pevent; /* Store ptr to ECB in TCB */
pevent->OSEventTbl[OSTCBCur->OSTCBY] |= OSTCBCur->OSTCBBitX; /* Put task in waiting list */
pevent->OSEventGrp |= OSTCBCur->OSTCBBitY;
y = OSTCBCur->OSTCBY; /* Task no longer ready */
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0u) { /* Clear event grp bit if this was only task pending */
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
}
}
取消事件等待
OS_EventTaskRemove 是与OS_EventTaskWait相反的操作, 当一个任务由于某种原因不再需要等待事件需运行OS_EventTaskRemove 。
void OS_EventTaskRemove (OS_TCB *ptcb,
OS_EVENT *pevent)
{
INT8U y;
y = ptcb->OSTCBY;
pevent->OSEventTbl[y] &= (OS_PRIO)~ptcb->OSTCBBitX; /* 在事件等待表中删除事件等待标志 */
if (pevent->OSEventTbl[y] == 0u) { /*若该行已没有任务等待*/
pevent->OSEventGrp &= (OS_PRIO)~ptcb->OSTCBBitY; /*删除事件等待组的事件等待标志*/
}
}
将等待事件的任务就绪
任务因为等待事件而在ECB中登记自己的等待, 当事件发生的时候, 如果该任务是事件等待表中优先级最高的任务, 任务被取消等待而回到就绪状态。
- 在事件等待表和事件等待组中找到最高优先级的等待任务的优先级。
- 根据优先级查优先级指针表, 找到该任务的任务控制块TCB指针。
- 对任务控制块的相关参数进行赋值。
- 判断任务是否被挂起, 如果未被挂起就将任务就绪。 完成从阻塞态到就绪态的转移。
- 调用OS_EventTaskRemove在ECB的事件等待表中删除该任务。
- 返回任务的优先级
OS_EventTaskRdy
INT8U OS_EventTaskRdy (OS_EVENT *pevent,
void *pmsg,
INT8U msk,
INT8U pend_stat)
{
OS_TCB *ptcb;
INT8U y;
INT8U x;
INT8U prio;
#if OS_LOWEST_PRIO > 63u
OS_PRIO *ptbl;
#endif
#if OS_LOWEST_PRIO <= 63u
y = OSUnMapTbl[pevent->OSEventGrp]; /* Find HPT waiting for message */
x = OSUnMapTbl[pevent->OSEventTbl[y]];
prio = (INT8U)((y << 3u) + x); /* Find priority of task getting the msg */
#else
if ((pevent->OSEventGrp & 0xFFu) != 0u) { /* Find HPT waiting for message */
y = OSUnMapTbl[ pevent->OSEventGrp & 0xFFu];
} else {
y = OSUnMapTbl[(OS_PRIO)(pevent->OSEventGrp >> 8u) & 0xFFu] + 8u;
}
ptbl = &pevent->OSEventTbl[y];
if ((*ptbl & 0xFFu) != 0u) {
x = OSUnMapTbl[*ptbl & 0xFFu];
} else {
x = OSUnMapTbl[(OS_PRIO)(*ptbl >> 8u) & 0xFFu] + 8u;
}
prio = (INT8U)((y << 4u) + x); /* Find priority of task getting the msg */
#endif
ptcb = OSTCBPrioTbl[prio]; /* Point to this task's OS_TCB */
ptcb->OSTCBDly = 0u; /* Prevent OSTimeTick() from readying task */
#if ((OS_Q_EN > 0u) && (OS_MAX_QS > 0u)) || (OS_MBOX_EN > 0u)
ptcb->OSTCBMsg = pmsg; /* Send message directly to waiting task */
#else
pmsg = pmsg; /* Prevent compiler warning if not used */
#endif
ptcb->OSTCBStat &= (INT8U)~msk; /* Clear bit associated with event type */
ptcb->OSTCBStatPend = pend_stat; /* Set pend status of post or abort */
/* See if task is ready (could be susp'd) */
if ((ptcb->OSTCBStat & OS_STAT_SUSPEND) == OS_STAT_RDY) {
OSRdyGrp |= ptcb->OSTCBBitY; /* Put task in the ready to run list */
OSRdyTbl[y] |= ptcb->OSTCBBitX;
}
OS_EventTaskRemove(ptcb, pevent); /* Remove this task from event wait list */
#if (OS_EVENT_MULTI_EN > 0u)
if (ptcb->OSTCBEventMultiPtr != (OS_EVENT **)0) { /* Remove this task from events' wait lists */
OS_EventTaskRemoveMulti(ptcb, ptcb->OSTCBEventMultiPtr);
ptcb->OSTCBEventPtr = (OS_EVENT *)pevent;/* Return event as first multi-pend event ready*/
}
#endif
return (prio);
}
信号量管理
信号量的建立OSSemCreate
信号量在操作系统初始化的时候并不存在。 这时操作系统中的事件管理数据结构事件控制块ECB为全空, 所有的事件控制块都在ECB空闲链表中排队。 信号量的建立函数OSSemCreate将使用一个并配置一个ECB, 使其具备信号量的属性。
OS_EVENT *OSSemCreate (INT16U cnt)
{
OS_EVENT *pevent;
if (OSIntNesting > 0u) { /* 查看是否在中断服务程序ISR中调用本函数 */
return ((OS_EVENT *)0); /* ... can't CREATE from an ISR */
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; /* Get next free event control block */
if (OSEventFreeList != (OS_EVENT *)0) { /* See if pool of free ECB pool was empty */
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) { /* Get an event control block */
pevent->OSEventType = OS_EVENT_TYPE_SEM;
pevent->OSEventCnt = cnt; /* Set semaphore value */
pevent->OSEventPtr = (void *)0; /* Unlink from ECB free list */
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
OS_EventWaitListInit(pevent); /* Initialize to 'nobody waiting' on sem. */
}
return (pevent);
}
信号量的建立OSSemCreate
假设信号量值为5, 则赋值后的ECB应该如图4-4所示。
信号量的删除OSSemDel
信号量如果不再使用了就应该尽快删除, 否则很快系统就没有可用的事件块可用。信号量的删除函数是OSSemDel。 删除信号量比创建一个信号量更复杂 。
OS_EVENT *OSSemDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
BOOLEAN tasks_waiting;
OS_EVENT *pevent_return;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指针是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return (pevent);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制块类型是否有效 */
*perr = OS_ERR_EVENT_TYPE;
return (pevent);
}
if (OSIntNesting > 0u) { /* 查看是否在中断服务程序ISR中调用本函数 */
*perr = OS_ERR_DEL_ISR; /* ... can't DELETE from an ISR */
return (pevent);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any tasks waiting on semaphore */
tasks_waiting = OS_TRUE; /* Yes */
} else {
tasks_waiting = OS_FALSE; /* No */
}
switch (opt) {
case OS_DEL_NO_PEND: /* Delete semaphore only if no task waiting */
if (tasks_waiting == OS_FALSE) {
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* Get next free event control block */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Semaphore has been deleted */
} else {
OS_EXIT_CRITICAL();
*perr = OS_ERR_TASK_WAITING;
pevent_return = pevent;
}
break;
case OS_DEL_ALWAYS: /* Always delete the semaphore */
while (pevent->OSEventGrp != 0u) { /* Ready ALL tasks waiting for semaphore */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK);
}
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* Get next free event control block */
OS_EXIT_CRITICAL();
if (tasks_waiting == OS_TRUE) { /* Reschedule only if task(s) were waiting */
OS_Sched(); /* Find highest priority task ready to run */
}
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Semaphore has been deleted */
break;
default:
OS_EXIT_CRITICAL();
*perr = OS_ERR_INVALID_OPT;
pevent_return = pevent;
break;
}
return (pevent_return);
}
请求信号量OSSemPend
请求信号量也称为等待信号量。 等待信号量的参数为3个, 分别是ECB的指针pevent, 32位无符号整数超时时间timeout, 和用来返回结果的指向整型的指针perr
void OSSemPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
{
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指针是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return;
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制块类型是否有效 */
*perr = OS_ERR_EVENT_TYPE;
return;
}
if (OSIntNesting > 0u) { /* 查看是否在中断服务程序ISR中调用本函数 */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return;
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return;
}
OS_ENTER_CRITICAL();
if (pevent->OSEventCnt > 0u) { /* If sem. is positive, resource available ... */
pevent->OSEventCnt--; /* ... decrement semaphore only if positive. */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return;
}
/* Otherwise, must wait until event occurs */
OSTCBCur->OSTCBStat |= OS_STAT_SEM; /* Resource not available, pend on semaphore */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* Store pend timeout in TCB */
OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next highest priority task ready */
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */
case OS_STAT_PEND_OK:
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
*perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get event within TO */
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */
OS_EXIT_CRITICAL();
}
OS_EventTaskWait(pevent);
作用就是标记与取消标记,在事件等待表中进行标记,在就续表、组进行取消标记。
提交信号量
当任务A获得信号量之后将信号量数字减1, 然后就可以访问资源R。 这时, 如果信号量的值为0, 任务B如果也要访问资源R, 必须等待信号量, 因此将任务B阻塞。 任务A在对资源的访问完成之后, 应将信号量唤醒。 因为资源已经可以被其他的任务访问了, 因此应该将任务B唤醒, 使任务B就绪。
当访问资源的任务有2个以上, 资源R可同时被N个任务访问, 因此信号量的值在最开始创建的时候应该等于N。 当任务A访问信号量, 信号量值变为N-1,任务B又访问, 信号量等于N-2, 当第M个任务访问, 信号量等于N-M。 当N-M=0的时候,也就是当N=M的时候, 当第N+1也要访问该资源R,第N+1个任务必须等待。
当任何一个任务(例如第2个)访问资源完成, 应该唤醒第N+1个任务让其访问资源。 当第N+1个任务访问完成之后, 因为没有其他的任务等待信号量, 只需简单地将信号量值加 1
INT8U OSSemPost (OS_EVENT *pevent)
{
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指针是否有效 */
return (OS_ERR_PEVENT_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制块类型是否有效 */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any task waiting for semaphore */
/* Ready HPT waiting on event */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_OK);
OS_EXIT_CRITICAL();
OS_Sched(); /* Find HPT ready to run */
return (OS_ERR_NONE);
}
if (pevent->OSEventCnt < 65535u) { /* Make sure semaphore will not overflow */
pevent->OSEventCnt++; /* Increment semaphore count to register event */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
OS_EXIT_CRITICAL(); /* Semaphore value has reached its maximum */
return (OS_ERR_SEM_OVF);
}
无等待请求信号量
在中断服务程序和有些用户任务中, 需要无等待的请求信号量。 也就是说, 使用信号量请求资源, 当没有可用的资源, 信号量为0的时候, 并不阻塞自己, 而是继续执行其他代码。 OSSemAccept就是无等待的请求信号量函数 。
- 进行参数检查
- 将信号量的值赋值给局部变量cnt, 如果cnt > 0说明资源有效或信号量有效, 因此将信号量的值减1然后返回cnt, 可以执行访问资源的代码了。 如果函数返回值为0,不能执行访问资源的代码。
INT16U OSSemAccept (OS_EVENT *pevent)
{
INT16U cnt;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指针是否有效 */
return (0u);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制块类型是否有效 */
return (0u);
}
OS_ENTER_CRITICAL();
cnt = pevent->OSEventCnt;
if (cnt > 0u) { /* See if resource is available */
pevent->OSEventCnt--; /* Yes, decrement semaphore and notify caller */
}
OS_EXIT_CRITICAL();
return (cnt); /* Return semaphore count */
}
放弃等待信号量
放弃等待信号量并非放弃本任务对信号量的等待。如果是放弃本任务对信号量的等待, 那么本任务在等待信号量, 那么本任务应该处于阻塞状态, 一个处于阻塞状态的任务得不到运行, 怎么能执行放弃等待信号量的代码呢? 因此, 一定是放弃其他任务对一个信号量的等待。
- 参数检查, 如果ECB指针无效或ECB的类型不是信号量类型, 返回参数检查错误信息。
- 如果pevent->OSEventGrp为0说明没有任务等待信号量, 返回0。
- 否则根据参数opt(选项)进行分支转移, 如果为OS_PEND_OPT_BROADCAST,使用while语句循环地将等待该信号量的每个任务用OS_EventTaskRdy来取消等待并使其就绪( 除非任务还被挂起) ; 如果为其他值则只将最高优先级的任务取消等待并就绪之。 两种情况下都返回取消等待信号量的任务数
INT8U OSSemPendAbort (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
INT8U nbr_tasks;
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指针是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return (0u);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制块类型是否有效 */
*perr = OS_ERR_EVENT_TYPE;
return (0u);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any task waiting on semaphore? */
nbr_tasks = 0u;
switch (opt) {
case OS_PEND_OPT_BROADCAST: /* Do we need to abort ALL waiting tasks? */
while (pevent->OSEventGrp != 0u) { /* Yes, ready ALL tasks waiting on semaphore */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_ABORT);
nbr_tasks++;
}
break;
case OS_PEND_OPT_NONE:
default: /* No, ready HPT waiting on semaphore */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_SEM, OS_STAT_PEND_ABORT);
nbr_tasks++;
break;
}
OS_EXIT_CRITICAL();
OS_Sched(); /* Find HPT ready to run */
*perr = OS_ERR_PEND_ABORT;
return (nbr_tasks);
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (0u); /* No tasks waiting on semaphore */
}
信号量的设置
操作系统提供了直接设置信号量值的函数OSSemSet。 一般情况下无需使用该函数设置信号量的值, 应该在信号量创建的时候初始化信号量的值。当一个信号量的值在创建之后为N, 每次有任务请求信号量就将该值减1, 反之将该值加1, 一般情况下是不允许随便修改的。
但是, 在极其特殊的情况下, 因为某种特殊的需要, 例如突然增加了其他的资源,需要修改资源数N, 可采用OSSemSet直接对信号量赋值, 但条件是这时没有任务在等待该信号量。
void OSSemSet (OS_EVENT *pevent,
INT16U cnt,
INT8U *perr)
{
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指针是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return;
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制块类型是否有效 */
*perr = OS_ERR_EVENT_TYPE;
return;
}
OS_ENTER_CRITICAL();
*perr = OS_ERR_NONE;
if (pevent->OSEventCnt > 0u) { /* See if semaphore already has a count */
pevent->OSEventCnt = cnt; /* Yes, set it to the new value specified. */
} else { /* No */
if (pevent->OSEventGrp == 0u) { /* See if task(s) waiting? */
pevent->OSEventCnt = cnt; /* No, OK to set the value */
} else {
*perr = OS_ERR_TASK_WAITING;
}
}
OS_EXIT_CRITICAL();
}
查询信号量状态
信号量状态查询将ECB中关于信号量的信息拷贝到另一个数据结构信号量数据OS_SEM_DATA
OS_SEM_DATA
typedef struct os_sem_data {
INT16U OSCnt; /* Semaphore count */
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* List of tasks waiting for event to occur */
OS_PRIO OSEventGrp; /* Group corresponding to tasks waiting for event to occur */
} OS_SEM_DATA;
OSSemQuery
INT8U OSSemQuery (OS_EVENT *pevent,
OS_SEM_DATA *p_sem_data)
{
INT8U i;
OS_PRIO *psrc;
OS_PRIO *pdest;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* 事件控制指针是否有效 */
return (OS_ERR_PEVENT_NULL);
}
if (p_sem_data == (OS_SEM_DATA *)0) { /* Validate 'p_sem_data' */
return (OS_ERR_PDATA_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_SEM) { /* 事件控制块类型是否有效 */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
p_sem_data->OSEventGrp = pevent->OSEventGrp; /* Copy message mailbox wait list */
psrc = &pevent->OSEventTbl[0];
pdest = &p_sem_data->OSEventTbl[0];
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++;
}
p_sem_data->OSCnt = pevent->OSEventCnt; /* Get semaphore count */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
互斥信号量管理
事件标志组管理
调度算法
概念
定义
调度\(\sigma\)
任务集\(\varGamma\)
算法A
可行性feasible
可调度性shedulable
约束constraints
复杂性
NP hard
分类
可抢占/不可抢占
静态/动态
在线/离线
最佳的/直觉的
Graham's Notation
\(\alpha \;| \;\beta \;|\; \gamma\)
\(\alpha\) 处理器数 number of processors
\(\beta\) 任务约束 constraints on tasks
\(\gamma\) 遵循优化准则optimality criterion
经典调度策略
FCFS(First Come First Served)
响应时间取决到达时间
SJF(Shortest Job First)
静态
不适合实时
Priority Scheduling
不适合实时
会产生饥饿,可以通过aging解决
Round Robin
时间片轮转
不适合实时
Multi-Level Scheduling
不适合实时
实时调度算法
静态:相对截止时间Di
动态:绝对截止时间di
Li = fi - di 延迟时间 = 截止时间 - 结束时间
Earliest Due Date
1 | sync | Lmax
同时到达,非抢占,静态
Earliest Deadline First
1 | preem. | Lmax
抢占式,非同时到达,动态
如果一个任务集不能被一个最优算法调度,那么这个任务集不能被其他算法调度
非抢占的EDF在不能空闲的算法里是最佳的
Bratley‘s Algorithm
1 | no-preem | Lmax
对调度树进行修剪,找到Lmax最小化
Heuristic Search
改为找直觉函数H最小化
H = ri => FCFS
H = Ci => SJF
H = Di => DM
H = di => EDF
Composite heuristic functions:
H =w1ri + w2Di
H = w1Ci+ w2di
H = w1ri + w2di
通过直觉函数有可能找不到最优,但并不代表不存在最优,有可能是是被前序直觉计算时忽略了
Latest Deadline First
1 | prec, sync | Lmax
先计算截止时间最晚的,放在最后执行
EDF*
1 | prec, preem | Lmax
将先后次序改为时间约束,然后用EDF调度
先将后续任务的开始时间延后,再将前序任务的截止时间提前
周期任务调度
按比例分享算法Proportional Share Algorithm
概念
Ui = required feeding fraction
\(\Delta\) = GCD(T1, T2)
\(\delta _i = U_i\Delta\)
可行性测试feasibility test:\(\Sigma\delta_i \le \Delta\) i.e. \(\Sigma U_i \le 1\)
问题
有可能GCD公约数取得太小,导致分割的块太多,会有很多转换的开销,增加运行的时间
循环调度算法
大周期里有小周期
不需要实时系统内核,开销比较小
过载后不是很鲁棒,不容易扩展
最佳优先级调度
优先级调度
- 分配优先级
- 验证可行性
- 执行任务
Rate Monotonic
Pi 正比 1/Ti (static)
在所有固定优先级算法中T=D(周期=相对截止时间)时是最佳的
Deadline Monotonic
Pi 正比 1/Di (static)
在所有固定优先级算法中D<=T时是最佳的
Earliest Deadline First
Pi 正比 1/dik (dynamic)
di,k = ri,k + Di
在所有算法中是最佳的
关键时刻
高优先级任务到来时刻
可行性计算
RM
Up <= 1 是可行性调度的必要不充分条件
最低上界Least Upper Bound
Up <= Ulub 是RM调度的充分条件
并非必要条件,因为在Ulub到1之间,可能存在一部分可行,比如Ulub < A <1 在Ulub到A之间可行,A到1之间不可行。
RM的上界Ulub : \(U_{lub}^{Rm} = n(2^{\frac{1}{n}} - 1)\)
双曲线上界 Hyperbolic Bound
\(\prod_{i=1}^{n}(U_i + 1) \le 2\)
响应时间分析
\(I_i = \sum_{D_k < D_i}C_k\)
响应时间Ri = Ci + Ii
验证Ri <= Di
干扰时间分析
\(I_ik = \lceil \frac{R_i}{T_k} \rceil C_k\)
\(I_i = \sum_{k=1}^{i-1} \lceil \frac{R_i}{T_k} \rceil C_k\)
EDF
因为EDF跟截止时间相关,是动态的,所以不能用响应时间分析。
处理器需求准则
processor demand criterion
\(\forall L > 0, \; g(0,L) \le L\)
\(g(0,L) = \sum_{i=1}^n \lfloor \frac{L-D_i+T_i}{T_i}\rfloor C_i\)
含义:gi(o,L) = 计算次数(时长/周期) * 计算时间,g(o,L) =
-Di+Ti为了保证整数运算,效果就是产生溢出,再向下取整
边界测试点
bounding test points
-
由于g函数只会在截止时间到来产生跳变,所以只要测试边界点就可以了
-
如果任务同时到达,而且Up < 1,经过超周期后,任务都是重复出现的,最小的超周期为
H = lcm(T1,...,Tn)
-
\(G(0,L) = \sum_{i=1}^n(\frac{L+T_i-D_i}{T_i})C_i = LU + \sum_{i=1}^n(T_i - D_i)U_i\)
从图像上可以看出来,我们只需要计算到L*
综合以上,得到处理器需求测试(Processor Demand Test)
\(\forall L \in D, \; g(0,L) \le L\)
\(D = {d_k | d_k \le \min(H,L^*)}\)
消息管理
本章的消息管理中包括消息邮箱和消息队列两方面的内容, 适用于任务之间的信息交流和同步。从原理上讲, 消息管理也应该属于事件管理的范畴
消息邮箱
本章的消息管理中包括消息邮箱和消息队列两方面的内容, 适用于任务之间的信息交流和同步。从原理上讲, 消息管理也应该属于事件管理的范畴
建立
在系统初始化之后, 并不存在一个消息邮箱。 这时操作系统中的事件管理数据结构事件控制块ECB为全空, 所有的事件控制块都在ECB空闲链表中排队。 消息邮箱的建立函数OSMboxCreate将使用一个并配置一个ECB,使其具备消息邮箱的属性。
OS_EVENT *OSMboxCreate (void *pmsg)
{
OS_EVENT *pevent;
if (OSIntNesting > 0u) { /* 查看是否在中断服务程序ISR中调用本函数*/
return ((OS_EVENT *)0); /* 不允许在中断服务程序ISR中调用本函数*/
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; /* 取得空闲的事件控制块ECB*/
if (OSEventFreeList != (OS_EVENT *)0) { /* 是否没有可用的事件控制块ECB? */
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) {
pevent->OSEventType = OS_EVENT_TYPE_MBOX;
pevent->OSEventCnt = 0u;
pevent->OSEventPtr = pmsg; /* 在ECB中存储消息地址*/
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
OS_EventWaitListInit(pevent);
}
return (pevent); /* 返回事件控制块地址*/
}
- 检查是否这中断服务程序中创建消息邮箱。
同不允许在中断服务程序中创建信号量一样, 操作系统μC/OS-II同样不允许在中断服务程序中创建消息邮箱。 - 检查是否有空闲的事件控制块。
将OSEventFreeList赋值给pevent, 如果pevent为空指针, 表示没有空闲的事件控制块, 函数返回。 - 在事件控制块空闲链表中取下表头。
因为pevent现在已经是用于邮箱的事件控制块, 读者可以直接把他理解为一个邮箱。 那么, 需要执行的操作显然就是在事件控制块空闲链表中将他删除,这时候OSEventFreeList应该指向第二个ECB。 - 对事件控制块赋值, 赋值后的ECB应该如图5-1所示。
- 返回ECB地址。
等待
等消息也称为请求消息。 含义是当消息存在的时候获取消息, 当消息不存在的时候就放弃对CPU的占有, 直到有消息的时候才被唤醒。 当任务后续的操作离不开消息, 这时任务就不该死死占着CPU不让其他的任务运行, 就应该去休息,而当消息到来的时候系统会将消息唤醒回就绪态, 任务获得消息后继续运行。
void *OSMboxPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
{
void *pmsg;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制块指针是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return ((void *)0);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制块类型是否有效 */
*perr = OS_ERR_EVENT_TYPE;
return ((void *)0);
}
if (OSIntNesting > 0u) { /* 查看是否在中断服务程序ISR中调用本函数 */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return ((void *)0);
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return ((void *)0);
}
OS_ENTER_CRITICAL();
pmsg = pevent->OSEventPtr;
if (pmsg != (void *)0) { /* See if there is already a message */
pevent->OSEventPtr = (void *)0; /* 清邮箱 */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (pmsg); /* 返回获得的消息(或空指针) */
}
OSTCBCur->OSTCBStat |= OS_STAT_MBOX; /* Message not available, task will pend */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* Load timeout in TCB */
OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next highest priority task ready to run */
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */
case OS_STAT_PEND_OK:
pmsg = OSTCBCur->OSTCBMsg;
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
pmsg = (void *)0;
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
pmsg = (void *)0;
*perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get event within TO */
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */
#if (OS_EVENT_MULTI_EN > 0u)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OSTCBCur->OSTCBMsg = (void *)0; /* 清除获得的消息 */
OS_EXIT_CRITICAL();
return (pmsg); /* 返回获得的消息 */
}
发送
当一个任务因为等待消息而被阻塞的时候, 只有当其他任务发出了消息, 被阻塞的任务才能被恢复到就绪态, 从而获得消息后继续运行。 阻塞的函数在前一节分析过了, 发消息的函数为OSMboxPost, 参数是消息类型的ECB的指针,以及消息的地址
INT8U OSMboxPost (OS_EVENT *pevent,
void *pmsg)
{
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制块指针是否有效 */
return (OS_ERR_PEVENT_NULL);
}
if (pmsg == (void *)0) { /* Make sure we are not posting a NULL pointer */
return (OS_ERR_POST_NULL_PTR);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制块类型是否有效 */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any task pending on mailbox */
/* Ready HPT waiting on event */
(void)OS_EventTaskRdy(pevent, pmsg, OS_STAT_MBOX, OS_STAT_PEND_OK);
OS_EXIT_CRITICAL();
OS_Sched(); /* Find highest priority task ready to run */
return (OS_ERR_NONE);
}
if (pevent->OSEventPtr != (void *)0) { /* Make sure mailbox doesn't already have a msg */
OS_EXIT_CRITICAL();
return (OS_ERR_MBOX_FULL);
}
pevent->OSEventPtr = pmsg; /* Place message in mailbox */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
删除
当消息邮箱不再使用了, 就应该尽快归还给系统, 即将消息占用的ECB归还给ECB空闲链表以备它用。 消息邮箱的删除函数是OSMboxDel。 删除一个消息也要涉及方方面面, 因为可能有任务正在等待这个邮箱中的消息。
#if OS_MBOX_DEL_EN > 0u
OS_EVENT *OSMboxDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
BOOLEAN tasks_waiting;
OS_EVENT *pevent_return;
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制块指针是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return (pevent);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制块类型是否有效*/
*perr = OS_ERR_EVENT_TYPE;
return (pevent);
}
if (OSIntNesting > 0u) { /* 查看是否在中断服务程序ISR中调用本函数 */
*perr = OS_ERR_DEL_ISR; /* ... can't DELETE from an ISR */
return (pevent);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any tasks waiting on mailbox */
tasks_waiting = OS_TRUE; /* Yes */
} else {
tasks_waiting = OS_FALSE; /* No */
}
switch (opt) {
case OS_DEL_NO_PEND: /* Delete mailbox only if no task waiting */
if (tasks_waiting == OS_FALSE) {
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* 取得空闲的事件控制块ECB */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Mailbox has been deleted */
} else {
OS_EXIT_CRITICAL();
*perr = OS_ERR_TASK_WAITING;
pevent_return = pevent;
}
break;
case OS_DEL_ALWAYS: /* Always delete the mailbox */
while (pevent->OSEventGrp != 0u) { /* Ready ALL tasks waiting for mailbox */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MBOX, OS_STAT_PEND_OK);
}
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* 取得空闲的事件控制块ECB */
OS_EXIT_CRITICAL();
if (tasks_waiting == OS_TRUE) { /* Reschedule only if task(s) were waiting */
OS_Sched(); /* Find highest priority task ready to run */
}
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Mailbox has been deleted */
break;
default:
OS_EXIT_CRITICAL();
*perr = OS_ERR_INVALID_OPT;
pevent_return = pevent;
break;
}
return (pevent_return);
}
放弃
同放弃对信号量的等待类似, 放弃等待邮箱也绝对不会是放弃本任务对邮箱的等待。放弃等待邮箱函数将放弃的是所有等待某邮箱的任务对该邮箱的等待或等待某邮箱的优先级最高的任务对邮箱的等待
INT8U OSMboxPendAbort (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
INT8U nbr_tasks;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制块指针是否有效 */
*perr = OS_ERR_PEVENT_NULL;
return (0u);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制块类型是否有效*/
*perr = OS_ERR_EVENT_TYPE;
return (0u);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any task waiting on mailbox? */
nbr_tasks = 0u;
switch (opt) {
case OS_PEND_OPT_BROADCAST: /* Do we need to abort ALL waiting tasks? */
while (pevent->OSEventGrp != 0u) { /* Yes, ready ALL tasks waiting on mailbox */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MBOX, OS_STAT_PEND_ABORT);
nbr_tasks++;
}
break;
case OS_PEND_OPT_NONE:
default: /* No, ready HPT waiting on mailbox */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_MBOX, OS_STAT_PEND_ABORT);
nbr_tasks++;
break;
}
OS_EXIT_CRITICAL();
OS_Sched(); /* Find HPT ready to run */
*perr = OS_ERR_PEND_ABORT;
return (nbr_tasks);
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (0u); /* No tasks waiting on mailbox */
}
-
检查事件控制块指针是否有效及事件控制块类型是否有效。
-
如果pevent->OSEventGrp为0说明没有任务等待消息邮箱, 取消等待的任务数是0, 返回0。
-
否则根据参数opt(选项)进行分支转移,如为OS_PEND_OPT_BROADCAST, 使用while语句循环地将等待该邮箱的每个任务用OS_EventTaskRdy来取消等待并使其就绪( 除非任务还被挂起) ;如果为其他值则只将最高优先级的任务取消等待并就绪之。
-
返回取消等待信号量的任务数。
无等待请求
在中断服务程序和有些用户任务中, 需要无等待的请求消息邮箱。 也就是说, 到邮箱中取邮件, 如果有邮件就获得邮件, 如果没有并不阻塞自己, 而是继续执行其他代码。
void *OSMboxAccept (OS_EVENT *pevent)
{
void *pmsg;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制块指针是否有效 */
return ((void *)0);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制块类型是否有效 */
return ((void *)0);
}
OS_ENTER_CRITICAL();
pmsg = pevent->OSEventPtr;
pevent->OSEventPtr = (void *)0; /* 清邮箱 */
OS_EXIT_CRITICAL();
return (pmsg); /* 返回获得的消息(或空指针) */
}
首先参数检查ECB是否有效, 如果有效, 将消息邮箱中邮件的地址OSEventPtr赋值给pmsg, 然后清邮箱内容, 返回获得的邮件的地址pmsg。这样, 如果邮箱中有邮件, 那么返回邮件的地址, 如果没有, 返回值就是空地址。
查询
INT8U OSMboxQuery (OS_EVENT *pevent,
OS_MBOX_DATA *p_mbox_data)
{
INT8U i;
OS_PRIO *psrc;
OS_PRIO *pdest;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /*事件控制块指针是否有效 */
return (OS_ERR_PEVENT_NULL);
}
if (p_mbox_data == (OS_MBOX_DATA *)0) { /* Validate 'p_mbox_data' */
return (OS_ERR_PDATA_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* 事件控制块类型是否有效*/
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
p_mbox_data->OSEventGrp = pevent->OSEventGrp; /* Copy message mailbox wait list */
psrc = &pevent->OSEventTbl[0];
pdest = &p_mbox_data->OSEventTbl[0];
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++;
}
p_mbox_data->OSMsg = pevent->OSEventPtr; /* Get message from mailbox */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
消息队列
使用消息队列管理, 就允许使用可以容纳多条信息的大邮箱, 按照先进先出( FIFO) 的原则, 发送和接收邮件。 需要注意的是, 这样的邮箱不是操作系统提供的, 而是要由用户任务来提供。 操作系统提供的是对其进行管理的程序。 另外, 邮箱中的内容仍然是邮件的地址。
数据结构
- 消息队列及其控制块
void *MessageStorage[size];
typedef struct os_q { /* 队列控制块QCB*/
struct os_q *OSQPtr; /*在空闲QCB链表中, 指示下一个QCB*/
void **OSQStart; /*队列数据的首地址*/
void **OSQEnd; /*队列数据的末地址+1*/
void **OSQIn; /*指示下次插入消息的位置 */
void **OSQOut; /* 指示下次提取消息的位置*/
INT16U OSQSize; /*队列的最大容量*/
INT16U OSQEntries; /*队列中当前的消息量*/
} OS_Q;
- 消息控制块实体
OS_Q OSQTbl[OS_MAX_QS];
- 空闲消息控制块链表
消息控制块QCB中OSQPtr用来指示空闲消息控制块QCB链表中的下一个消息控制块QCB。 OSQFreeList指向该表表头
初始化
消息队列初始化函数在操作系统初始化时被调用, 主要用于初始化消息队列使用的数据结构。 消息队列初始化函数的名称为OS_QInit
void OS_QInit (void)
{
#if OS_MAX_QS == 1u
OSQFreeList = &OSQTbl[0]; /* Only ONE queue! */
OSQFreeList->OSQPtr = (OS_Q *)0;
#endif
#if OS_MAX_QS >= 2u
INT16U ix;
INT16U ix_next;
OS_Q *pq1;
OS_Q *pq2;
OS_MemClr((INT8U *)&OSQTbl[0], sizeof(OSQTbl)); /* Clear the queue table */
for (ix = 0u; ix < (OS_MAX_QS - 1u); ix++) { /* Init. list of free QUEUE control blocks */
ix_next = ix + 1u;
pq1 = &OSQTbl[ix];
pq2 = &OSQTbl[ix_next];
pq1->OSQPtr = pq2;
}
pq1 = &OSQTbl[ix];
pq1->OSQPtr = (OS_Q *)0;
OSQFreeList = &OSQTbl[0];
#endif
}
- 将所有QCB全部清为全0。
- 使用for循环将除最后一个消息控制块
OSQTbl[OS_MAX_QS - 1]之外的所有消息控制块初始化, 构建了单向的消息队列空闲链表。 - 初始化最后一个QCB, 将消息队列空闲链表完善。
OSQFreeList指向链表的表头
建立
创建消息队列就是将从ECB空闲链表中取下一个事件控制块ECB来, 将其用于消息队列管理。 并从QCB空闲链表的表头取下一个消息控制块QCB, 将其各种属性进行设置, 用于指示消息的位置以及提取和插入消息的位置。创建消息队列的函数名称为OSQCreate。
OS_EVENT *OSQCreate (void **start,
INT16U size)
{
OS_EVENT *pevent;
OS_Q *pq;
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#ifdef OS_SAFETY_CRITICAL_IEC61508
if (OSSafetyCriticalStartFlag == OS_TRUE) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
if (OSIntNesting > 0u) { /* 查看是否在中断服务程序ISR中调用本函数 */
return ((OS_EVENT *)0); /* 不允许在中断服务程序ISR中调用本函数 */
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; /* 取得事件控制块ECB */
if (OSEventFreeList != (OS_EVENT *)0) { /*是否有有效的ECB */
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) { /* 是否有有效的ECB */
OS_ENTER_CRITICAL();
pq = OSQFreeList; /* 在空闲QCB链表中取一个QCB */
if (pq != (OS_Q *)0) { /* 得到了有效的QCB吗? */
OSQFreeList = OSQFreeList->OSQPtr; /* 取下该QCB*/
OS_EXIT_CRITICAL();
pq->OSQStart = start; /* 队列初始化 */
pq->OSQEnd = &start[size];
pq->OSQIn = start;
pq->OSQOut = start;
pq->OSQSize = size;
pq->OSQEntries = 0u;
pevent->OSEventType = OS_EVENT_TYPE_Q;
pevent->OSEventCnt = 0u;
pevent->OSEventPtr = pq;
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
OS_EventWaitListInit(pevent); /* 初始化事件等待表 */
} else {
pevent->OSEventPtr = (void *)OSEventFreeList;
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
pevent = (OS_EVENT *)0;
}
}
return (pevent);
}
- 断是否在中断服务程序中调用本函数, 如果是就返回。
- 取得消息队列的链表首地址送pevent。
- 判断pevent是否为空指针, 如果是则说明是系统已经没有空闲的ECB可供使用,填写错误信息, 返回空指针。
- 从空闲ECB链表取下表头。
- 空闲QCB链表首地址送pq。
- 如果没有有效的空闲QCB链表, 恢复空闲ECB链表, 返回空ECB指针。
- 在空闲QCB链表中取一个pq指向的QCB,对其进行初始化。 设置OSQStart为消息指针数组的首地址start。OSQEnd值为&start[size]即消息指针数组( 消息队列) 中最后一个指针后面的一个地址。 OSQIn和OSQOut也设置为start。 OSQSize的值为size。 OSQEntries为0表示该队列中还没有消息。
- 接下来对pevent指向的ECB进行初始化。 OSEventType为OS_EVENT_TYPE_Q表示用于消息队列管理。 OSEventCnt在这里没有用, 设置为0。 OSEventPtr指向QCB,即设置为pq。 调用OS_EventWaitListInit初始化ECB中的事件等待表和事件等待组。
- 返回ECB指针。
发送
发消息到消息队列的函数名称为OSQPost。 参数是事件控制块ECB的地址pevent和消息的地址pmsg。
INT8U OSQPost (OS_EVENT *pevent,
void *pmsg)
{
OS_Q *pq;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any task pending on queue */
/* Ready highest priority task waiting on event */
(void)OS_EventTaskRdy(pevent, pmsg, OS_STAT_Q, OS_STAT_PEND_OK);
OS_EXIT_CRITICAL();
OS_Sched(); /* Find highest priority task ready to run */
return (OS_ERR_NONE);
}
pq = (OS_Q *)pevent->OSEventPtr; /* Point to queue control block */
if (pq->OSQEntries >= pq->OSQSize) { /* Make sure queue is not full */
OS_EXIT_CRITICAL();
return (OS_ERR_Q_FULL);
}
*pq->OSQIn++ = pmsg; /* Insert message into queue */
pq->OSQEntries++; /* Update the nbr of entries in the queue */
if (pq->OSQIn == pq->OSQEnd) { /* Wrap IN ptr if we are at end of queue */
pq->OSQIn = pq->OSQStart;
}
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
- 首先进行参数检查, 如果参数检查失败则返回。
- 如果有任务等待消息队列中的消息, 那么消息队列现在必然是空的。 不需要将消息存入队列, 而直接将消息给在等待的优先级最高的消息, 并将其就绪。 执行一次任务调度然后返回。
- 如果没有任务等待消息队列中的消息, 那么就需要将该消息加入消息队列。 如果消息队列是满的, 不能容纳更多的消息, 返回出错信息。 否则, 在消息控制块QCB的OSQIn所指示的消息指针数组位置存入该消息, 然后将OSQIn 指向下一个单元以便下次使用。 判断OSQIn是否到超过了表尾, 如果超过了, 将其指向队首。 然后返回。
等待
等待消息队列的消息是消息队列管理中的又一核心函数。 如果消息队列中有消息,那么就取出消息, 然后返回; 如果没有消息, 只有在ECB中标记自己的等待, 然后阻塞。
等待消息队列的函数的名称为OSQPend, 参数是ECB的指针、 等待超时时间和返回函数执行信息的指针的perr。 函数的返回值是指向消息的指针。
void *OSQPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr)
{
void *pmsg;
OS_Q *pq;
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
*perr = OS_ERR_PEVENT_NULL;
return ((void *)0);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_Q) {/* Validate event block type */
*perr = OS_ERR_EVENT_TYPE;
return ((void *)0);
}
if (OSIntNesting > 0u) { /* 查看是否在中断服务程序ISR中调用本函数 */
*perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */
return ((void *)0);
}
if (OSLockNesting > 0u) { /* See if called with scheduler locked ... */
*perr = OS_ERR_PEND_LOCKED; /* ... can't PEND when locked */
return ((void *)0);
}
OS_ENTER_CRITICAL();
pq = (OS_Q *)pevent->OSEventPtr; /* Point at queue control block */
if (pq->OSQEntries > 0u) { /* See if any messages in the queue */
pmsg = *pq->OSQOut++; /* Yes, extract oldest message from the queue */
pq->OSQEntries--; /* Update the number of entries in the queue */
if (pq->OSQOut == pq->OSQEnd) { /* Wrap OUT pointer if we are at the end of the queue */
pq->OSQOut = pq->OSQStart;
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (pmsg); /* Return message received */
}
OSTCBCur->OSTCBStat |= OS_STAT_Q; /* Task will have to pend for a message to be posted */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* Load timeout into TCB */
OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs */
OS_EXIT_CRITICAL();
OS_Sched(); /* Find next highest priority task ready to run */
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */
case OS_STAT_PEND_OK: /* Extract message from TCB (Put there by QPost) */
pmsg = OSTCBCur->OSTCBMsg;
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
pmsg = (void *)0;
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
pmsg = (void *)0;
*perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get event within TO */
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */
#if (OS_EVENT_MULTI_EN > 0u)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OSTCBCur->OSTCBMsg = (void *)0; /* Clear received message */
OS_EXIT_CRITICAL();
return (pmsg); /* Return received message */
}
- 首先进行参数、 ECB类型、 中断、 调度锁检查, 失败则返回。
- 取得ECB中的QCB指针, 查询消息队列中是否有消息。 如果有消息, 那么请求直接成功, 取得消息队列中OSQOut所指的消息, 将OSQOut指向消息队列中的下一个元素以备下一次的消息提取。 如果OSQOut指向了消息队列之外, 就指向消息队列的首地址。 将OSQEntries减1表示消息数量减少了一个。 然后返回消息的指针。
- 如果没有消息, 那么任务就只有被阻塞。 首先在TCB中的OSTCBStat中添加消息队列等待标志, 任务延时时间, 初始化等待状态, 然后调用OS_EventTaskWait添加等待标志和取消就绪标志, 接着调用OS_Sched执行一次任务调度。
- 任务恢复运行后, 根据TCB中的等待状态OSTCBStatPend决定程序走向。 如果获得了消息, 进行一些处理后返回该消息。 如果是退出等待, 或是等待超时, 分别填写没有取得消息的原因, 然后返回空指针。
删除
当消息队列不再使用了, 就应该尽快归还给系统, 即将消息占用的ECB归还给ECB空闲链表以备它用, 将QCB也归还给空闲QCB链表。
OS_EVENT *OSQDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr)
{
BOOLEAN tasks_waiting;
OS_EVENT *pevent_return;
OS_Q *pq;
#ifdef OS_SAFETY_CRITICAL
if (perr == (INT8U *)0) {
OS_SAFETY_CRITICAL_EXCEPTION();
}
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
*perr = OS_ERR_PEVENT_NULL;
return (pevent);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { /* Validate event block type */
*perr = OS_ERR_EVENT_TYPE;
return (pevent);
}
if (OSIntNesting > 0u) { /* 查看是否在中断服务程序ISR中调用本函数 */
*perr = OS_ERR_DEL_ISR; /* ... can't DELETE from an ISR */
return (pevent);
}
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0u) { /* See if any tasks waiting on queue */
tasks_waiting = OS_TRUE; /* Yes */
} else {
tasks_waiting = OS_FALSE; /* No */
}
switch (opt) {
case OS_DEL_NO_PEND: /* Delete queue only if no task waiting */
if (tasks_waiting == OS_FALSE) {
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pq = (OS_Q *)pevent->OSEventPtr; /* Return OS_Q to free list */
pq->OSQPtr = OSQFreeList;
OSQFreeList = pq;
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* 取得事件控制块ECB */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Queue has been deleted */
} else {
OS_EXIT_CRITICAL();
*perr = OS_ERR_TASK_WAITING;
pevent_return = pevent;
}
break;
case OS_DEL_ALWAYS: /* Always delete the queue */
while (pevent->OSEventGrp != 0u) { /* Ready ALL tasks waiting for queue */
(void)OS_EventTaskRdy(pevent, (void *)0, OS_STAT_Q, OS_STAT_PEND_OK);
}
#if OS_EVENT_NAME_EN > 0u
pevent->OSEventName = (INT8U *)(void *)"?";
#endif
pq = (OS_Q *)pevent->OSEventPtr; /* Return OS_Q to free list */
pq->OSQPtr = OSQFreeList;
OSQFreeList = pq;
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList; /* Return Event Control Block to free list */
pevent->OSEventCnt = 0u;
OSEventFreeList = pevent; /* 取得事件控制块ECB */
OS_EXIT_CRITICAL();
if (tasks_waiting == OS_TRUE) { /* Reschedule only if task(s) were waiting */
OS_Sched(); /* Find highest priority task ready to run */
}
*perr = OS_ERR_NONE;
pevent_return = (OS_EVENT *)0; /* Queue has been deleted */
break;
default:
OS_EXIT_CRITICAL();
*perr = OS_ERR_INVALID_OPT;
pevent_return = pevent;
break;
}
return (pevent_return);
}
- 首先进行参数的检查。
- 根据选项opt决定程序的分支
- 如果opt不是这两个值当中的一个, 那就是说明是错误的选项, 也属于参数检查失败。 因此,标记错误信息perr为OS_ERR_INVALID_OPT后, 直接返回原来ECB指针pevent。
状态
消息队列数据OS_Q_DATA是为返回消息队列信息而提供的, 因此用户程序如果想了解消息队列的信息, 要先创建OS_Q_DATA的实例。 然后以消息队列所在ECB地址及该实例的地址为指针为参数调用获取消息队列的状态函数OSQQuery
INT8U OSQQuery (OS_EVENT *pevent,
OS_Q_DATA *p_q_data)
{
OS_Q *pq;
INT8U i;
OS_PRIO *psrc;
OS_PRIO *pdest;
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
if (p_q_data == (OS_Q_DATA *)0) { /* Validate 'p_q_data' */
return (OS_ERR_PDATA_NULL);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
p_q_data->OSEventGrp = pevent->OSEventGrp; /* Copy message queue wait list */
psrc = &pevent->OSEventTbl[0];
pdest = &p_q_data->OSEventTbl[0];
for (i = 0u; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++;
}
pq = (OS_Q *)pevent->OSEventPtr;
if (pq->OSQEntries > 0u) {
p_q_data->OSMsg = *pq->OSQOut; /* Get next message to return if available */
} else {
p_q_data->OSMsg = (void *)0;
}
p_q_data->OSNMsgs = pq->OSQEntries;
p_q_data->OSQSize = pq->OSQSize;
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
应用
内存管理
嵌入式系统中, 内存资源是十分宝贵的, 如果采用内存分配方式不合理, 经过一段时间的内存分配和释放、 再发配和再释放, 会产生很多零散的内存碎块。 这些零散的空间很难利用, 因此怎样解决内存分配过程中产生的碎块问题是内存管理的关键问题
μC/OS-II中, 采用分区的方式管理内存, 即将连续的大块内存按分区来管理, 每个系统中有数个这样的分区, 每个分区又包含数个相同的内存块。 这样, 在分配内存的时候,根据需要从不同的分区中得到数个内存块, 而在释放时,这些内存块重新释放回他们原来所在的分区。 这样就不会产生内存越分越凌乱, 没有整块的连续分区的问题了。
数据结构
内存控制块
typedef struct os_mem { /* MEMORY CONTROL BLOCK */
void *OSMemAddr; /* Pointer to beginning of memory partition */
void *OSMemFreeList; /* Pointer to list of free memory blocks */
INT32U OSMemBlkSize; /* Size (in bytes) of each block of memory */
INT32U OSMemNBlks; /* Total number of blocks in this partition */
INT32U OSMemNFree; /* Number of memory blocks remaining in this partition */
#if OS_MEM_NAME_EN > 0u
INT8U *OSMemName; /* Memory partition name */
#endif
} OS_MEM;
空闲内存控制块
内存控制块实体
OS_MEM OSMemTbl[OS_MAX_MEM_PART];
空闲内存控制块链表
OS_MEM *OSMemFreeList;
内存分区
内存分区与消息队列在一点上相似, 就是必须由用户任务来创建。 其实定义一个内存分区是相当简单的一件事情, 其实就是一个二维数组。
如: INT32U MemBuf [10] [20]
MemBuf就可以是一个内存分区, 该分区共有800个字节, 分为10个内存块。使用MCB可以将该分区管理起来, 实现动态分配和释放
内存控制块初始化
void OS_MemInit (void)
{
#if OS_MAX_MEM_PART == 1u
OS_MemClr((INT8U *)&OSMemTbl[0], sizeof(OSMemTbl)); /* Clear the memory partition table */
OSMemFreeList = (OS_MEM *)&OSMemTbl[0]; /* Point to beginning of free list */
#if OS_MEM_NAME_EN > 0u
OSMemFreeList->OSMemName = (INT8U *)"?"; /* Unknown name */
#endif
#endif
#if OS_MAX_MEM_PART >= 2u
OS_MEM *pmem;
INT16U i;
OS_MemClr((INT8U *)&OSMemTbl[0], sizeof(OSMemTbl)); /* Clear the memory partition table */
for (i = 0u; i < (OS_MAX_MEM_PART - 1u); i++) { /* Init. list of free memory partitions */
pmem = &OSMemTbl[i]; /* Point to memory control block (MCB) */
pmem->OSMemFreeList = (void *)&OSMemTbl[i + 1u]; /* Chain list of free partitions */
#if OS_MEM_NAME_EN > 0u
pmem->OSMemName = (INT8U *)(void *)"?";
#endif
}
pmem = &OSMemTbl[i];
pmem->OSMemFreeList = (void *)0; /* Initialize last node */
#if OS_MEM_NAME_EN > 0u
pmem->OSMemName = (INT8U *)(void *)"?";
#endif
OSMemFreeList = &OSMemTbl[0]; /* Point to beginning of free list */
#endif
}
OS_MemClr这个函数很简单, 将从pdest开始一个字节一个字节的将内存中的内容清0, 直到清0了size个字节为止
void OS_MemClr (INT8U *pdest, INT16U size)
{
while (size > 0u) {
*pdest++ = (INT8U)0;
size--;
}
}
可见OS_MemInit, 对内存控制块MCB进行了初始化, 构建了空闲MCB链表。但并未执行创建内存分区及分配内存的操作
创建内存分区
内存分区在操作系统初始化的时候并不存在。 在使用一个内存分区之前, 必须先定
义一个二维数组, 但这个二维数组仍未成为内存分区。
通过调用函数OSMemCreate, 使用一个MCB对其进行管理, 才成为一个内存分区。
OSMemCreate返回一个指向内存控制块的指针, 供内存管理的其他操作函数调用。
OSMemCreate有四个参数:
- 第一个参数是addr, 就是内存分区的首地址, 即从哪里开始创建内存分区, 应是作为分区的二维数组的首地址。
- 第二个参数是nblks,表示需要创建的内存分区中内存块的总数。
- 第三个参数是INT32U blksize, 表示每一个内存块的大小。
- 第四个参数是指向整数的指针perr, 用来返回函数运行过程中的信息。
OS_MEM *OSMemCreate (void *addr,
INT32U nblks,
INT32U blksize,
INT8U *perr)
{
OS_MEM *pmem;
INT8U *pblk;
void **plink;
INT32U loops;
INT32U i;
#if OS_ARG_CHK_EN > 0u
if (addr == (void *)0) { /* Must pass a valid address for the memory part.*/
*perr = OS_ERR_MEM_INVALID_ADDR;
return ((OS_MEM *)0);
}
if (((INT32U)addr & (sizeof(void *) - 1u)) != 0u){ /* Must be pointer size aligned */
*perr = OS_ERR_MEM_INVALID_ADDR;
return ((OS_MEM *)0);
}
if (nblks < 2u) { /* Must have at least 2 blocks per partition */
*perr = OS_ERR_MEM_INVALID_BLKS;
return ((OS_MEM *)0);
}
if (blksize < sizeof(void *)) { /* Must contain space for at least a pointer */
*perr = OS_ERR_MEM_INVALID_SIZE;
return ((OS_MEM *)0);
}
#endif
OS_ENTER_CRITICAL();
pmem = OSMemFreeList; /* Get next free memory partition */
if (OSMemFreeList != (OS_MEM *)0) { /* See if pool of free partitions was empty */
OSMemFreeList = (OS_MEM *)OSMemFreeList->OSMemFreeList;
}
OS_EXIT_CRITICAL();
if (pmem == (OS_MEM *)0) { /* See if we have a memory partition */
*perr = OS_ERR_MEM_INVALID_PART;
return ((OS_MEM *)0);
}
plink = (void **)addr; /* Create linked list of free memory blocks */
pblk = (INT8U *)addr;
loops = nblks - 1u;
for (i = 0u; i < loops; i++) {
pblk += blksize; /* Point to the FOLLOWING block */
*plink = (void *)pblk; /* Save pointer to NEXT block in CURRENT block */
plink = (void **)pblk; /* Position to NEXT block */
}
*plink = (void *)0; /* Last memory block points to NULL */
pmem->OSMemAddr = addr; /* Store start address of memory partition */
pmem->OSMemFreeList = addr; /* Initialize pointer to pool of free blocks */
pmem->OSMemNFree = nblks; /* Store number of free blocks in MCB */
pmem->OSMemNBlks = nblks;
pmem->OSMemBlkSize = blksize; /* Store block size of each memory blocks */
*perr = OS_ERR_NONE;
return (pmem);
}
首先, 当操作系统需要创建内存分区时, 调用OSMemCreate()函数, 并通过参数传递需要建立内存分区的属性, 包括: 内存分区的首地址, 内存分区中内存块的数量, 内存分区中内存块的大小, 以及返回信息代码的地址。
然后, 检查判断是否执行函数参数检查, 以保证内存分区的创建成功。 如果执行函数参数检查, 则判断: 内存分区地址是否有效, 内存分区地址大小是否一致, 内存分区是否至少含有两个内存块, 每个内存块是否起码可以容纳一个指针。
最后, 执行创建内存分区的算法, 创建一个内存分区, 并返回内存控制块的地址, 供系统调用。 内存分区创建的算法是: 将内存分区首地址加内存块大小得到下一个内存块地址, 然后再加内存块大小得到下一个内存块地址, 如此循环(内存块数量-1) 次, 直至最后一个内存块创建完成。 在其过程中, 通过指针建立内存块的链接表, 并将最后一个内存块内的指针指向空地址。
内存块获取
创建之后, 形成了一个空闲内存块链表, 而该链表由内存控制块MCB来管理。应用程序需要从已建立的内存分区中申请一个内存块时, 可以调用OSMemGet函数。 该函数有两个参数, 第一个参数为pmem, 也就是前节创建内存分区函数所返回的内存控制块的指针。 另一个参数为perr, 用于返回函数执行的信心
void *OSMemGet (OS_MEM *pmem,
INT8U *perr)
{
void *pblk;
#if OS_ARG_CHK_EN > 0u
if (pmem == (OS_MEM *)0) { /* Must point to a valid memory partition */
*perr = OS_ERR_MEM_INVALID_PMEM;
return ((void *)0);
}
#endif
OS_ENTER_CRITICAL();
if (pmem->OSMemNFree > 0u) { /* See if there are any free memory blocks */
pblk = pmem->OSMemFreeList; /* Yes, point to next free memory block */
pmem->OSMemFreeList = *(void **)pblk; /* Adjust pointer to new free list */
pmem->OSMemNFree--; /* One less memory block in this partition */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE; /* No error */
return (pblk); /* Return memory block to caller */
}
OS_EXIT_CRITICAL();
*perr = OS_ERR_MEM_NO_FREE_BLKS; /* No, Notify caller of empty memory partition */
return ((void *)0); /* Return NULL pointer to caller */
}
- 首先进行参数检查, 查看MCB指针是否有效。
- 判断是否有空闲的内存块, 如果没有就设置perr为OS_ERR_MEM_NO_FREE_BLKS, 然后返回空指针。
- 从链表中摘下表头, 返回取下的内存块的地址。 这样就申请到了内存块。
释放内存分区
内存块的释放, 就是将内存块归还给空闲内存块链表, 也可以理解为归还给内存分区。 内存分区释放函数是OSMemPut。
内存块释放函数是OSMemPut有两个参数, 一个是内存控制块MCB的地址, 一个是将释放的内存块的地址。 OSMemPut返回整数型的操作过程的信息号
INT8U OSMemPut (OS_MEM *pmem,
void *pblk)
{
if (pmem == (OS_MEM *)0) { /* Must point to a valid memory partition */
return (OS_ERR_MEM_INVALID_PMEM);
}
if (pblk == (void *)0) { /* Must release a valid block */
return (OS_ERR_MEM_INVALID_PBLK);
}
OS_ENTER_CRITICAL();
if (pmem->OSMemNFree >= pmem->OSMemNBlks) { /* Make sure all blocks not already returned */
OS_EXIT_CRITICAL();
return (OS_ERR_MEM_FULL);
}
*(void **)pblk = pmem->OSMemFreeList; /* Insert released block into free block list */
pmem->OSMemFreeList = pblk;
pmem->OSMemNFree++; /* One more memory block in this partition */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE); /* Notify caller that memory block was released */
}
- 首先进行参数检查, 查看MCB指针是否有效, 内存块指针是否有效。
- 检查内存块是不是满的, 判断条件是是否空闲的块数大于等于最大块数。 如果是满的不能释放。 这种情况一定是异常引起的。
- 将释放的块归还给空闲内存块链表, 插入到表头然后返回。
从本节的OSMemPut和上节OSMemGet,很明显, 实现了内存的动态分配, 并且一次分配和释放最小是一个内存块, 保证了内存中不会存在很多小的碎片。
查询内存分区状态
INT8U OSMemQuery (OS_MEM *pmem,
OS_MEM_DATA *p_mem_data)
{
#if OS_ARG_CHK_EN > 0u
if (pmem == (OS_MEM *)0) { /* Must point to a valid memory partition */
return (OS_ERR_MEM_INVALID_PMEM);
}
if (p_mem_data == (OS_MEM_DATA *)0) { /* Must release a valid storage area for the data */
return (OS_ERR_MEM_INVALID_PDATA);
}
#endif
OS_ENTER_CRITICAL();
p_mem_data->OSAddr = pmem->OSMemAddr;
p_mem_data->OSFreeList = pmem->OSMemFreeList;
p_mem_data->OSBlkSize = pmem->OSMemBlkSize;
p_mem_data->OSNBlks = pmem->OSMemNBlks;
p_mem_data->OSNFree = pmem->OSMemNFree;
OS_EXIT_CRITICAL();
p_mem_data->OSNUsed = p_mem_data->OSNBlks - p_mem_data->OSNFree;
return (OS_ERR_NONE);
}
内存管理实例
标签:复习,0u,void,系统,实时,CRITICAL,pevent,OS,EVENT 来源: https://www.cnblogs.com/wangx0111/p/16471848.html