中断服务子程序的入口地址叫做中断向量。
系统接收到中断请求之后,如果CPU处于中断允许状态,系统会中止当前任务,按照中断向量的指向去运行中断服务子程序;当中断服务子程序结束之后,可剥夺型内核会根据情况进行一次任务调度,去运行最高优先级别的就绪任务,不一定是接着运行被中断的任务。
变量OSIntNesting记录中断嵌套的层数;OSIntEnter()进入中断服务函数,用来记录中断嵌套的层数;OSIntExt()退出中断服务函数。
OSIntCtxSw()中断级任务切换函数与任务级切换函数OSCtxSw()一样,通常是用汇编语言编写。
OSIntCtxSw()
{
OSTCBCur=OSTCBHighRdy;
OSPrioCur=OSPrioHighRdy;
SP=OSTCBHighRdy->OSTCBStkPtr;//SP指向待运行任务的任务堆栈
用出栈指令把R1、R2弹出CPU的通用寄存器
RETI;//中断返回,PC指向待运行任务
}
CPU只有在中断开放期间才会响应中断请求。在应用程序中经常有一些代码段必须不受任何干扰地连续运行,这样的代码段叫做临界段。在临界段前使用OS_ENTER_CIRTICAL(),在临界段之后使用OS_EXIT_CRITICAL()这两个宏来实现中断的开放和关闭。在临界段不要调用系统提供的功能函数,以免系统崩溃。
有3中不同的实现方法。取决于使用的处理器和编译器。用户可通过定义移植文件OS_CPU.H中的常数OS_CRITICAL_METHOD来选择实现方法。
(1)令OS_CRITICAL_METHOD=1
#define OS_ENTER_CRITICAL() \
asm("DI") 关中断
#define OS_EXIT_CRITICAL() \
asm("EI") 开中断
(2)令OS_CRITICAL_METHOD=2
#define OS_ENTER_CRITICAL() \
asm("PUSH PSW") 通过保存程序状态字来保存中断允许标志
asm("DI") 关中断
#define OS_EXIT_CRITICAL() \
asm("POP PSW") 恢复中断允许标志
(3)令OS_CRITICAL_METHOD=3,前提条件是C编译器具有扩展功能。可获得程序状态字的值,保存在C语言函数的局部变量中,不必压入堆栈。(由于不知道C编译器所提供的函数名,函数名称只是示意而已)
#define OS_ENTER_CRITICAL() \
cpu_sr=get_processor_psw(); 获得程序状态字并保存在全局变量sr中
disable_interrupts(); 关中断
#define OS_EXIT_CRITICAL() \
set_processor_psw(cpu_sr); 用sr恢复程序状态字
最小的事中单位就是两次中断之间间隔的时间,这个最小时钟单位叫做时钟节拍。硬件定时器以时钟节拍为周期定时地产生中断,该中断的中断服务程序叫做OSTickISR()。中断服务程序通过调用OSTimeTick()来完成系统在每个时钟节拍时需要做的工作。OSTickISR()是用汇编语言来编写的。
void OSTickISR(void)
{
保存CPU寄存器
调用OSIntEnter(); //记录中断嵌套层数
if(OSIntNesting==1)
{
OSTCBCur->OSTCBStkPtr=SP;
}
调用OSTimeTick(); //节拍处理
消除中断;
开中断;
调用OSIntExit(); //中断嵌套层数-1
恢复CPU寄存器;
中断返回;
}
在时钟中断服务程序中调用的OSTimeTick()叫做时钟节拍服务函数。它的工作就是在每个时钟节拍了解每个任务的延时状态,使其中已经到了延时时限的非挂起任务进入就绪状态。
void OSTimeTick(void)
{
#if OS_CRITICAL_METHOD==3
OS_CPU_SR cpu_sr;
#endif
OS_TCB *ptcb;
OSTimeTickHook();
#if OS_TIME_GET_SET_EN>0
OS_ENTER_CRITICAL();
OSTime++;
OS_EXIT_CRITICAL();
#endif
if(OSRunning==TRUE)
{
ptcb=OSTCBList;
while(ptcb->OSTCBPrio!=OS_IDLE_PRIO)
{
OS_ENTER_CRITICAL();
if(ptcb->OSTCBDly==0)
{
if(--ptcb->OSTCBDly==0)
{
if((ptcb->OSTCBStat&OS_STAT_SUSPEND)==OS_STST_RDY)
{
OSRdyGrp|=ptcb->OSTCBBitY;
OSRdyTbl[ptcb->OSTCBY]|=ptcb->OSTCBBitX;
}
else
{
ptcb->OSTCBDly=1;
}
}
}
}
ptcb=ptcb->OSTCBNext;
OS_EXIT_CRITICAL();
}
}
OSTimeTickHook()是时钟节拍服务函数的钩子函数,除此之外,还有OSTCBInitHook()等共10个钩子函数。
96
由于嵌入式系统的任务是一个无线循环,并且μC/OS-II还是一个抢占式内核,所以为了使高优先级的任务不至于独占CPU,μC/OS-II规定:除了空闲任务之外的所有任务必须在任务中合适位置调用OSTimeDly(),使当前任务的运行延时(暂停)一段时间并进行一次任务调度,以让出CPU的使用权。
μC/OS-II还提供了一个可以使用时/分/秒/毫秒为参数的延时函数OSTimeDlyHMSM(hours,minutes,seconds,milli);它也一样要引发一次调度。当规定延时时间期满,或者取消了延时,他会立即进入就绪状态。
延时的任务可通过在其他任务中调用函数OSTimeDlyResume(INT8U prio)取消延时进入就绪状态。如果任务比正在运行的任务优先级高,则立即引发一次任务调度。
嵌入式系统中的各个任务都是以并发的方式来运行的。他们不可避免的要共同使用一些共享资源。所以系统必须具有完善的同步和通信机制。
为了实现各个人物之间的合作,必须建立一些制约关系。一种叫做直接制约关系,一种叫做间接制约关系。直接制约关系源于任务之间的合作。间接制约关系源于对资源的共享。
在多任务合作过程中,操作系统应该解决两个问题:1.各任务间应该有互斥关系,即对资源的共享;2.任务在执行上要有先后次序,即任务之间的合作。
任务之间这种制约性的合作运行机制叫做任务间的同步,依靠任务之间互相发送信息来保证同步。
信号量、邮箱、消息队列这些被称作事件。事件实现任务之间的通信。发送事件是把信息发送到事件上;读事件操作叫做请求事件。μC/OS-II把任务发送事件,请求事件以及其他对事件的操作都定义为全局函数,以供应用程序的所有任务来调用。
互斥型信号量:任务一在访问共享资源之前先进行请求信号量,如果信号量为1,它会进行资源访问,同时将1改为0;如果此时任务二也需要访问资源,它获得的信号量为0,无法访问资源。只能等待任务一结束访问,将0改为1后,任务二才能进行访问。实际上就是一个标志位。
在多任务操作系统中,需要在任务与任务之间通过传递一个数据(消息)的方式进行通信。因此,在内存中创建一个存储空间作为该数据的缓冲区。这个缓冲区叫做消息缓冲区。那么任务传递数据的最简单的一个方法就是传递消息缓冲区的指针。用来传递消息缓冲区指针的数据结构叫做消息邮箱。实际上就是一个全局的指针变量。
消息邮箱可以传递一个消息,也可以定义一个指针数组。让数组的每个元素都存放一个消息缓冲区指针。那么任务可以通过这个指针数组指针的方法来传递多个消息。这种可以传递多个消息的数据结构叫做消息队列。
在多任务系统中,当一个事件被占用时,其他请求该事件的任务在暂时得不到事件的服务时应该处于等待状态。因此,作为功能完善的事件,应该对这些等待任务具有一定的管理功能。这个管理功能应该包括两个方面:1.要对等待事件的所有任务进行记录并排序;2.允许任务有一定的等待时限。
μc/OS-II使用事件控制块ECB的数据结构将描述时间的数据结构统一起来,来描述信号量、消息邮箱、消息队列、等待任务表在内的所有有关事件的数据。
μC/OS-II有4个对事件控制块进行基本操作的函数(定义在文件OS_CORE.C),以供操作信号量、消息邮箱、消息队列等事件的函数调用。
(1)事件控制块的初始化函数:作用是将OSEventGrp及任务等待表中的每一位都清零。
参数是事件控制块的指针
void OS_EventWaitListInit(OS_EVENT * pevent);
初始化事件的函数在任务调用创建信号量、消息邮箱、消息队列的函数时,被调用。
(2)使任务进入等待状态
参数是事件控制块的指针
void OS_EventTaskWait(OS_EVENT *pevent);
OS_EventTaskWait()在任务调用OSxxxPend()请求一个事件时,被函数OSxxxPend()调用。
(3)使一个正在等待任务进入就绪状态
如果一个正在等待的任务具备了可以运行的条件,那么就让它进入就绪状态。调用OS_EventTaskRdy().作用就是将任务在任务等待表中清零,在任务就绪表对应位置置1,引发一次任务调度。
参数是事件控制块指针;未使用;清除TCB状态标志掩码
INT8U OS_EventTaskRdy(OS_EVENT *pevent,void *msg,INT8U msk);
OS_EventTaskRdy()在任务调用OSxxxPost()发送一个事件时,被函数OSxxxPost()调用。
(4)使一个等待超时的任务进入就绪状态
如果一个等待超时的任务因为没有获取事件等原因而没有具备可以运行的条件,却又要它进入就绪状态,这时调用OS_EventTO()
void OS_EventTO(OS_EVENT *pevent);
OS_EventTO()在任务调用OSxxxPend()请求一个事件时,被函数OSxxxPend()调用。
在系统初始化时,系统会在初始化函数OSInit()中按应用程序使用事件的总数OS_MAX_EVENTS(在文件OS_CFG.H中定义),创建OS_MAX_EVENTS个空事件控制块,并用成员OSEventPtr作为链接指针,把空事件控制块连接成一个单向链表。以后,每当应用程序创建一个事件,系统会取出一个空事件控制块并初始化以描述该事件。当应用程序删除一个事件时,就会将该事件的控制块归还给空事件控制块链表。
当事件控制块的成员OSEventType的值被设置为OS_EVENT_TYPE_SEM时,这个时间控制块描述的就是一个信号量。信号量由信号量计数器和任务等待表组成。
信号量使用事件控制块成员OSEventCnt作为计数器,而用OSEventTbl[ ] 数组来充当等待任务表。
(1)创建信号量
参数是信号量计数器初值
OS_EVENT *OSSemCreate(INT16U cnt);
(2)请求信号量
参数是信号量指针,等待时限,错误信息
void OSSemPend(OS_EVENT *pevent,INT16U timeout, INT8U *err);
当等待时间超过timeout时,结束等待状态进入就绪状态。如果timeout=0,表示等待时间为无限长。当任务需要访问一个共享资源时,先请求信号量,若OSEventCnt>0,说明可以访问,计数器减1.若=0,任务处于等待状态,等待时限保存在任务控制块TCB的成员OSTCBDly中。
当任务请求信号量时,如果希望在信号量无效时准许任务不进入等待状态而继续运行,则不调用OSSemPend(),而是调用函数INT16U OSSemAccept(OS_EVENT * pevent)来请求信号量。.
(3)发送信号量
任务获取信号量,并在访问结束后释放信号量。释放消耗量也叫作发送信号量。发送信号量需要调用函数INT16U OSSemPost(OS_EVENT * prevent).OSSemPost()在对信号量的计数器操作之前首先要检查是否有等待该信号量的任务,如果没有,那么OSEventCnt+1,如果有,则调用OS_Sched()去运行等待任务中优先级别最高的任务。
(4)删除信号量
参数是信号量指针,删除条件选项,错误信息;
OS_EVENT *OSSemDel(OS_EVENT *pevent,INT8U opt,INT8U *err);
opt有两个参数可以选择:1.OS_DEL_NO_PEND表示当等待任务表中没有等待任务才删除信号量;2.OS_DEL_ALLWAYS表示在等待任务表中无论是否有等待任务都立即删除信号量
只能在任务中删除信号量,不能在中断服务程序中删除。
(5)查询信号量的状态
参数是信号量指针,存储信号量状态的结构
INT8U OSSemQuery(OS_EVENT *pevent,OS_SEM_DATA *pdata);
typedef struct
{
INT16U OSCnt;
INT8U OSEventTbl[OS_EVENT_TBL_SIZE];
INT8U OSEventGrp;
}OS_SEM_DATA;
OSSemQuery()对信号进行查询之后,会将信号量中的相关信息存储到OS_SEM_DATA类型的变量中,所以,在调用此函数之前,必须先定义一个OS_SEM_DATA类型的变量。
函数调用成功后,返回OS_NO_ERR。
任务优先级的反转现象:在可剥夺型内核中,当任务以独占的方式使用共享资源时,会出现低优先级任务先于高优先级任务运行的现象。
优先级反转的原因:一个优先级较低的任务获得了信号量使用共享资源期间内,被具有较高优先级的任务打断而不能释放信号量,从而使这个优先级较高的任务因为得不到信号量而被迫进入等待状态。在等待期间,优先级低于它的但是高于占据信号量的任务的任务先运行了。显然,如果这种优先级介于使用量的两个任务级别中间的优先级任务较多,会极大的恶化任务的运行环境。
优先级反转的解决办法:使获得信号量任务的优先级别在使用共享资源期间暂时提升到所有任务最高优先级的高一个级别上,以使该任务不被其他任务所打断,从而尽快地使用完共享资源并释放信号量;然后在释放信号量之后,恢复该任务原来的优先级别。
1.互斥型信号量
为了解决独占式资源出现的优先级反转的问题,互斥型信号量OS_EVENT_TYPE_MUTEX除了具有普通信号量的机制之外,还有一些其他特性。
OSEventCnt被分成了低8位和高8位。低8位用来存放信号值,0XFF时说明信号量未被占用,信号有效,否则无效。高8位用来存放为了避免出现优先级反转现象而要提升的优先级别prio。
(1)创建互斥型信号
OS_EVENT *OSMutexCreate(INT8U prio,INT8U *err);参数为优先级别和错误信息
(2)请求互斥型信号量
void OSMutexPend(OS_EVENT * pevent,INT16U timeout,INT8U *err);
调用此函数来请求访问共享资源,如果OSEventCnt的低8位是0xff,说明信号量有效,可以进行访问,否则进入等待状态,直到信号量被其他任务释放。为了防止等待时间过长,设置了一个参数timeout,当等待时间超过这个时限,可以结束等待。
INT8U OSMutexAccept(OS_EVENT *pevent, INT8U *err);
调用此函数,可以无等待地请求一个互斥型信号量。
(3)发送互斥型信号量
INT8U OSMutexPost(OS_EVENT *pevent);
(4)获取互斥型信号量的当前状态
INT8U OSMutexQuery(OS_EVENT *pevent,OS_MUTEX_DATA *pdata);
(5)删除互斥型信号量
OS_EVENT *OSMutexDel(OS_EVENT *pevent ,INT8U opt,INT8U *err) ;
消息邮箱是在两个需要通信的任务之间通过传递数据缓冲区指针的方法来通信的。将数据缓冲区的指针赋给事件控制块成员OSEventPtr。
(1)创建消息邮箱
OS_EVENT *OSMboxCreate(void *msg)参数是消息指针,返回值是消息邮箱的指针。须先定义msg的初始值,为NULL。但也可以事先定义一个邮箱,把邮箱指针作为参数传递到此函数。
(2)请求消息邮箱
void *OSMboxPend(OS_EVENT *pevent,INT16U timeout,INT8U *err);
这个函数主要是看邮箱指针OSEventPtr是否为NULL。如果不是,则把消息指针返回给调用函数的任务。如果是NULL,则使任务进入等待状态,并引发一次任务调度。
void *OSMboxAccept(OS_EVENT *pevent);任务在请求邮箱失败时也可以不进行等待而继续运行。如果要以这种方式来请求邮箱,则任务需要调用此函数。
(3)向消息邮箱发送消息
INT8U OSMboxPost(OS_EVENT *pevent,void *msg);消息邮箱指针;消息缓冲区的指针
INT8U OSMboxPostOpt(OS_EVENT *pevent,void *msg,INT8U opt);opt是广播选项。函数中第三个参数opt用来说明是否把消息向所有等待任务广播。该值为OS_POST_OPT_NONE则把消息向优先级别最高的等待任务发送;如果该值为OS_POST_OPT_BROADCAST则意味着把消息向所有等待任务广播。
(4)查询消息邮箱的状态
INT8U OSMboxQuery(OS_EVENT *pevent,OS_MBOX_DATA *pdata);消息邮箱的指针;存放邮箱信息的结构
(5)删除消息邮箱
OS_EVENT *OSMboxDel(OS_EVENT *pevent,INT8U opt,INT8U *err);
消息队列由事件控制块、消息队列和消息三部分组成。当把事件控制块成员OSEventType的值置为OS_EVENT_TYPE_Q,为消息队列。消息队列的核心就是消息指针数组。
消息队列相当于一个共用一个任务等待列表的消息邮箱数组,事件控制块成员OS_EventPtr指向一个叫做队列控制块OS_Q的结构,该结构管理一个数组MsgTbl[ ],该数组中的元素都是一些指向消息的指针。
OSQStart和OSQEnd是一个常指针,OSQIn和OSQOut是可移动的指针。当移动到数组末尾时,也就是与OSQEnd相等时,指针会被移动到起始位置。从效果上看,OSQEnd和OSQStart是等值的。于是,这个消息指针数组是一个循环队列。
向指针数组中插入消息指针的方式有两种:先进先出(FIFO),和后进先出(LIFO)
在系统初始化时,系统将按文件OS_CFG.H中的配置常数OS_MAX_QS定义OS_MAX_QS个队列控制块,并用队列控制块中的指针OSQPtr将所有队列控制块链接为链表。
(1)创建消息队列
OS_EVENT OSQCreate(void **start,INT16U size);参数是指针数组的地址,数组长度;返回值是消息队列的指针。创建一个消息队列首先应该定义一个指针数组,然后把各个消息数据缓冲区的首地址存入这个数组中,最后再调用函数OSQCreate().
(2)请求消息队列
void *OSQPend(OS_EVENT * pevent,INT16U timeout,INT8U *err);返回值为消息指针。通过访问事件控制块成员OSEventPtr指向的OS_Q的成员OSQEntries来判断是否有消息可用。如果有消息可用,则返回OS_QOut指向的消息,同时将指针指向下一条消息,并把有效消息数的变量OSQEntries减1;如果无消息可用(OSQEntries=0),则将此任务挂起,使之处于等待状态并引发一次任务调度。
void OSQAccept(OS_EVENT *pevent)无等待地请求一个消息队列
(3)向消息队列发送消息
(4)清空消息队列
INT8U OSQFlush (OS_EVENT *pevent);
(5)删除消息队列
OS_EVENT *OSQDel (OS_EVENT *pevent);
(6)查询消息队列
INT8U OSQQuery (OS_EVENT *pevent,OS_Q_DATA *pdata);
OSQQuery ()的查询结果就存放在OS_Q_DATA为类型的变量中。