多任务与处理器的关系,调度。
处理器中俩指针PC和SP。PC指向任务代码,SP指向任务堆栈。将多任务进行调度。就是实时多任务操作系统。
完整代码包含:
任务的几种状态。
任务控制块(TCB)
程序在多任务中for(;;)无限循环。空闲时,有空闲任务即计数任务和统计任务。
空闲任务优先级最低,计数任务为OS_LOWEST_PRIO。OS_LOWEST_PRIO-1为统计任务。
为每一个任务分配一个任务堆栈,
//定义堆栈的长度
#define TASK_STK_SIZE 512
//定义一个数组来作为任务堆栈
OS_STK TaskStk [TASK_STK_SIZE];使用时先进行任务创建OSTaskCreate( ),再任务堆栈初始化OSTaskStkInit( )。靠任务控制块链表链接。有任务就创建,无任务就是空的任务控制块链表。
任务就绪表 二维数组OSRdyTbl[ ],变量OSRdyGrp表明就绪表中每行是否存在就绪任务。
x为Grp(二进制)转化成的八进制,y为Tbl的列(二进制)转换成八进制。二者组成任务的优先级。
可以根据就绪表,计算任务的优先级。可以将优先级的任务设置为就绪状态。
即,根据就绪表获得待运行任务的任务控制块指针。还要进行一下判断:究竟是待运行任务是否为当前任务,如果是,则不切换;如果不是才切换,而且还要保存被中止任务的运行环境。
任务切换OS_TASK_SW(),这是中断。中断服务子程序是OSCtxSw( )。
用
INT8UOSTaskCreate (
void (*task)(void *pd),//指向任务的指针
void *pdata, //传递给任务的参数
OS_STK *ptos, //指向任务堆栈栈顶的指针
INT8Uprio //任务的优先级
)
创建任务。创建完后就开始任务调度了。即OSStart。
中断和时钟
OSIntCtxSw( )中断级任务切换函数。
OS_ENTER_CRITICAL( )
OS_EXIT_CRITICAL()分别是关中断和开中断。利用此可以构成临界区域。
μC/OS-II与大多数计算机系统一样,用硬件定时器产生一个周期为ms级的周期性中断来实现系统时钟,最小的时钟单位就是两次中断之间相间隔的时间,这个最小时钟单位叫做时钟节拍(TimeTick)。
硬件定时器以时钟节拍为周期定时地产生中断,该中断的中断服务程序叫做OSTickISR( )。中断服务程序通过调用函数OSTimeTick()来完成系统在每个时钟节拍时需要做的工作。
void OSTickISR(void)
{
保存CPU寄存器;
调用OSIntEnter( ); //记录中断嵌套层数
if (OSIntNesting = =1;
{
OSTCBCur->OSTCBStkPtr = SP;//保存堆栈指针
}
调用OSTimeTick( ); //节拍处理
清除中断;
开中断;
调用OSIntExit( ); //中断嵌套层数减一
恢复CPU寄存器;
中断返回;
}
void OSTimeTick(void)时钟节拍服务函数:就是在每个时钟节拍了解每个任务的延时状态,使其中已经到了延时时限的非挂起任务进入就绪状态。
{
……
OSTimeTickHook();
……
OSTime++; //记录节拍数
........
....
}
为了使高优先级别的任务不至于独占CPU,可以给其他任务优先级别较低的任务获得CPU使用权的机会,μC/OS-II规定:除了空闲任务之外的所有任务必须在任务中合适的位置调用系统提供的函数OSTimeDly( ),使当前任务的运行延时(暂停)一段时间并进行一次任务调度,以让出CPU的使用权。
void OSTimeDly (INT16U ticks)
{....
取消当前任务的就绪状态
延时节拍数存入任务控制块
调用调度函数
....
}
INT8U OSTimeDlyResume( INT8Uprio);取消任务延时函数
INT32U OSTimeGet( void );获得系统时间函数
void OSTimeSet( INT32U ticks );设置系统时间函数
第四章 任务的同步与通信
例如,任务A和任务B共享一台打印机,如果系统已经把打印机分配给了任务A,则任务B因不能获得打印机的使用权而应该处于等待状态,只有当任务A把打印机释放后,系统才能唤醒任务B使其获得打印机的使用权。
任务间通信的东西叫做:事件。事件分为几类:信号量、消息邮箱、消息队列。
事件控制块。ECB
其中有两部分:INT8U OSEventGrp; //等待事件的任务组
INT8UOSEventTbl[OS_EVENT_TBL_SIZE];//任务等待表
1、而OS_EventTaskWait()是把一个任务设置为等待某一事件的函数。在任务调用函数OS×××Pend( )请求一个事件时,被OS×××Pend( )所调用。 /
2、如果一个正在等待的任务具备了可以运行的条件,那么就要使它进入就绪状态。这时要调用OS_EventTaskRdy()函数。该函数的作用就是把调用这个函数的任务在任务等待表中的位置清0(解除等待状态)后,再把任务在任务就绪表中对应的位置1,然后引发一次任务调度。
函数OS_EventTaskRdy( )在任务调用函数OS×××Post ( )发送一个事件时,被函数OS×××Post ( )所调用。
3、如果一个正在等待事件的任务已经超过了等待的时间,却仍因为没有获取事件等原因而未具备可以运行的条件,却又要使它进入就绪状态,这时要调用OS_EventTO( )函数。
4、 如果一个正在等待事件的任务已经超过了等待的时间,却仍因为没有获取事件等原因而未具备可以运行的条件,却又要使它进入就绪状态,这时要调用OS_EventTO( )函数。函数OS_EventTO ( )将在任务调用OS×××Pend( )请求一个事件时,被函数OS×××Pend( )所调用。配合事件的有一个空事件链表。当程序创建一个控制块时就从空事件链表中取出一个空事件控制块。
信号量及其操作
调用函数 OSSemCreate ( ) 来创建一个信号量。任务通过调用函数OSSemPend( )请求信号量:
为防止任务因得不到信号量而处于长期的等待状态,函数OSSemPend允许用参数timeout设置一个等待时间的限制,当任务等待的时间超过timeout时可以结束等待状态而进入就绪状态。如果参数timeout被设置为0,则表明任务的等待时间为无限长。
释放信号量也叫做发送信号量,发送信号量需调用函数 OSSemPost ( )。调用函数OSSemDel( )来删除该信号量。
互斥型信号量和优先级反转
描述了A、B、C三个任务的运行情况。其中,任务A的优先级别高于任务B,任务B的优先级别高于任务C。任务A和任务C都要使用同一个共享资源S,而用于保护该资源的信号量在同一时间只能允许一个任务以独占的方式对该资源进行访问,即这个信号量是一个互斥型信号量。
解决问题的办法之一,是使获得信号量任务的优先级别在使用共享资源期间暂时提升到所有任务最高优先级的高一个级别上,以使该任务不被其他的任务所打断,从而能尽快地使用完共享资源并释放信号量,然后在释放了信号量之后再恢复该任务原来的优先级别。
OSMutexCreate ()是解决办法的函数,创建互斥信号量。某任务调用函数OSMutexPend( )向互斥信号量请求使用该资源,如果互斥信号量在占用资源,则该任务等待。
消息邮箱
消息邮箱是在两个需要通信的任务之间通过传递数据缓冲区指针的方法来通信的
创建邮箱需要调用函数OSMboxCreate ( )
任务可以通过调用函数OSMboxPost ( )向消息邮箱发送消息
当一个任务请求邮箱时需要调用函数OSMboxPend( )::查看邮箱指针OSEventPtr是否为NULL,如果不是NULL就把邮箱中的消息指针返回给调用函数的任务。
如果邮箱指针OSEventPtr是NULL,则使任务进入等待状态,并引发一次任务调度。
消息队列及其操作
消息队列由三个部分组成:事件控制块、消息队列和消息。
事件控制块成员OSEventPtr指向了一个叫做队列控制块(OS_Q)的结构,该结构管理了一个数组MsgTbl[ ],该数组中的元素都是一些指向消息的指针。
消息指针数组的基本参数都记录在一个叫做队列控制块的结构中。
队列控制块中的指针OSQPtr将所有队列控制块链接为链表。由于这时还没有使用它们,故这个链表叫做空队列控制块链表 。
创建一个消息队列首先需要定义一指针数组,然后把各个消息数据缓冲区的首地址存入这个数组中,然后再调用函数OSQCreate( )来创建消息队列。
请求消息队列的目的是为了从消息队列中获取消息。任务请求消息队列需要调用函数OSQPend( )。
任务需要通过调用函数OSQPost( )或OSQPostFront()来向消息队列发送消息。函数OSQPost( )以FIFO(先进先出)的方式组织消息队列,函数OSQPostFront()以LIFO(后进先出)的方式组织消息队列。
信号量集
在实际应用中,任务常常需要与多个事件同步,即要根据多个信号量组合作用的结果来决定任务的运行方式。为了实现多个信号量组合的功能定义了一种特殊的数据结构——信号量集。所有信号量集实质上是一种可以对多个输入的逻辑信号进行基本逻辑运算的组合逻辑。
不同于信号量、消息邮箱、消息队列等事件,μC/OS-II不使用事件控制块来描述信号量集,而使用了一个叫做标志组的结构OS_FLAG_GRP。
typedefstruct{
INT8U OSFlagType; //识别是否为信号量集的标志
void *OSFlagWaitList;//指向等待任务链表的指针 成员OSFlagWaitList是一个指针,当一个信号量集被创建后,这个指针指向了这个信号量集的等待任务链表。
OS_FLAGS OSFlagFlags; //所有信号列表
}OS_FLAG_GRP;
等待任务链表
信号量集用一个双向链表来组织等待任务,每一个等待任务都是该链表中的一个节点(Node)。标志组OS_FLAG_GRP的成员OSFlagWaitList就指向了信号量集的这个等待任务链表。
typedefstruct {
void *OSFlagNodeNext; //指向下一个节点的指针
void *OSFlagNodePrev; //指向前一个节点的指针
void *OSFlagNodeTCB; //指向对应任务控制块的指针
void *OSFlagNodeFlagGrp; //反向指向信号量集的指针
OS_FLAGS OSFlagNodeFlags;//信号过滤器
INT8U OSFlagNodeWaitType;//定义逻辑运算关系的数据
} OS_FLAG_NODE;
给等待任务链表添加节点的函数为OS_FlagBlock( ),这个函数的原型为:
static void OS_FlagBlock (
OS_FLAG_GRP *pgrp, //信号量集指针
OS_FLAG_NODE *pnode, //待添加的等待任务节点指针
OS_FLAGS flags, //指定等待信号的数据
INT8U wait_type, //信号与等待任务之间的逻辑
INT16U timeout //等待时限
);
这个函数将在请求信号量集函数OSFlagPend ( )中被调用。
信号量集的操作
任务可以通过调用函数OSFlagCreate ( )来创建一个信号量集。
任务可以通过调用函数OSFlagPost ( )向信号量集发信号。
任务可以通过调用函数OSFlagPend( )请求一个信号量集。
内存的动态分配
应用程序在运行中为了某种特殊需要,经常需要临时获得一些内存空间,因此作为一个比较完善的操作系统必须具有动态分配内存的能力。
能否合理、有效地对内存储器进行分配和管理,是衡量一个操作系统品质的指标之一。特别地对于实时操作系统来说,还应该保证系统在动态分配内存时,它的执行时间必须是可确定的。 μ C /OS-II 改进了 ANSIC 用来动态分配和释放内存的 malloc ( ) 和 free( ) 函数,使它们可以对大小固定的内存块进行操作,从而使 malloc ( ) 和 free() 函数的执行时间成为可确定的,满足了实时操作系统的要求。
内存控制块
μC/OS-II对内存进行两级管理,即把一个大片连续的内存空间分成了若干个分区,每个分区又分成了若干个大小相等的内存块来进行管理。
typedefstruct {
void *OSMemAddr; //内存分区的指针
void *OSMemFreeList; //内存控制块链表的指针
INT32U OSMemBlkSize; //内存块的长度
INT32U OSMemNBlks; //分区内内存块的数目
INT32U OSMemNFree; //分区内当前可分配的内存块的数目
} OS_MEM;
多内存控制块构成链表
在应用程序需要一个内存块时,应用程序可以通过调用函数OSMemGet( )向某内存分区请求获得一个内存块
划分了欲使用的分区和内存块之后,应用程序可以通过调用函数OSMemCreate()来建立一个内存分区
当应用程序不再使用一个内存块时,必须要及时地将它释放。应用程序通过调用函数OSMemPut( )来释放一个内存块
应用程序可以通过调用函数OSMemQuery( )来查询一个分区目前的状态信息
其中参数pdata是一个OS_MEM_DATA类型的结构,该结构的定义如下:
typedefstruct {
void *OSAddr; //内存分区的指针
void *OSFreeList; //分区内内存块链表的头指针
INT32U OSBlkSize; //内存块的长度
INT32U OSNBlks; //分区内内存块的数目
INT32U OSNFree; //分区内空闲内存块的数目
INT32U OSNUsed; //已被分配的内存块数目
} OS_MEM_DATA;