uCOSII源代码剖析—终极篇

启动工作原理

刚接触操作系统的时候觉得这个最神秘,到底里面做了什么,怎么就成了个操作系统,它到底有什么用,为什么要引进来着个东东。学了之后才知道,原来最根本的思想还是源于汇编里面的跳转和压栈,以调用一个函数为例,编译后的汇编肯定是先通过SP压入当前代码段地址然后就是保存一些寄存器的值放栈里面(51单片机好像不是这样),然后执行程序,完了之后,出栈把寄存器恢复,最后把原来存的代码段地址付给PC然后回到原来的程序,这是汇编执行函数的做法,而操作系统人为强行的模拟这样操作,把代码写成不同代码块假定为AB,要相互之间执行似乎不受影响,是这么实现的,假设首先代码在A中执行,代码段一直往下指,如果我要执行B,首先把A的代码段压入栈,所有的寄存器值存入A的栈,然后让SP强行指到B的栈执行出栈操作,把寄存器恢复成B的,B的代码段地址放入PC,这样就B在执行,如果又要A接着上次执行,又强行把B的寄存器、当前代码段地址压入B的栈,然后SP强行指到A的栈恢复的时候恢复成A最近一次保存的东西(和函数调用不同每次切换栈里面的东西是随机的),这样,可以很自由的在AB中切换,如果切换足够快,AB看以来好像同时在执行,这就是并行,AB就是任务。如果这个切操作放到定时器函数中来做,就可以严格按照时间来切换,这就是操作系统雏形。另外,各个任务之间有存在一定的关系,有逻辑上的先后等,必须引进全局的结构体、变量来标记一些信息,全局的这些数据是不会被释放的,所以所有的任务可以去通过读、写这些数据来实现各个程序块交流信息,实现所谓的同步、互斥。这就是操作系统的原理,而这些不同的通信方式按功能细分就成事件管理、内存管理啥玩意。有这些基本的管理就是一个只有内核操作系统了。配上文件系统、图形界面这些个模块功能就能做出想Window这样的东东。只有引入操作系统才能更好的写程序,才能让性能发挥到极致。具体到uCOSII也是这样:首先是主函数,然后是OSInit(),这个函数就是对那些全局的数据结构初始化,建立希望的链表等数据结构,为后面全局变量通信做好准备,并且创建了1-2个系统任务(空闲任务必须,统计任务可选),而所谓的创建任务OSTaskCreate(另外在这个系统里还有个OSTaskCreateExt也是一种创建任务函数,只不过多了些检测栈、清楚栈的功能而已)就是把一个函数,的函数地址、自己的栈建立联系、优先级啥弄好为任务切换做好准备。设置好定时切换的相关信息类似定时器,按照节拍在中断中进行任务切换判断、发生切换,这个时候还没有开启开关,所以的任务创建完成后,启动多任务函数OSStart(),这个函数是让SP指到其中的一个站然后出栈就跳到一个任务函数里去了,接下来就是正常的任务运行了。

内存管理部分

函数名

功能简介

OS_MemInit();

初始化分区控制块OS_MEM构建空闲内存控制块MCB空闲链表,OSInit()内部调用。

OSMemCreate();

实际上是定义一个二维数组,一维也是可以的只是没那么直观方便而已,通过一个从空闲内存控制块链表上取一个OS_MEM来管理这块内存,做法是按用户参数分成小块内存(一般大小为第二维大小,直观),填写控制块的相关信息,用户调用

OSMemGet()

从指定的分区上取一个内存块

OSMemPut();

释放一个内存块到指定的分区

OSMemQuery();

查询某个指定的分区信息

OSMemNameSet();

设置某个指定分区的名

OSMemNameGet();

得到某个指定分区的名

事件管理部分

公用函数:

函数名

功能简介

OS_InitEvenList()

初始化事件控制块OS_EVENT链表,OSInit()内部调用

OS_EventWaitListInit()

仅仅清零指定事件控制块的等待任务表,等待任务组,实际上我觉得这个函数没有必要,因为创建链表的时候所有ECB全部清0,被系统用过后返回回去时都是调用OS_EventTaskRdy或删除事件函数,也都进行过清0,所以从空闲ECB链表取下来的ECB肯定等待表、组都是0

OS_EventTaskWait()

阻塞当前任务,登记就绪表、组,等待任务表、组,TCB指向ECB

OS_EventTaskRemove()

仅仅清指定ECB的等待任务表、组

OS_EventTaskRdy()

选出指定事件最高等待任务,让其就绪,TCB域设置,如果没有挂起的标志还要清就绪组、表,ECB清相应的等待任务表、组

普通信号量:

函数名

功能简介

OSSemCreate()

ECB链表上取一个ECB,设置事件类型、信号量数,清ECB指针域为0OS_EventWaitListInit()清掉等待任务表、组。返回ECB*

OSSemSet()

一般不用,特殊情况非得用,必须要求没有任务被阻塞该事件上。

OSSemDel()

有个op参数为OS_DEL_NO+PEND只有在没有任务等待是才允许

OS_DEL_ALWAYS即使有任务在等待都要清掉

首先标记是否有任务被阻塞,再判断是那种操作类型

第一种:没有任务等待,返回ECB到空闲链表,否则,错误返回

第二种:如果有任务等待时,OS_EventTaskRdy()让所有任务以等到该事件方式全部就绪,然后返回ECB到空闲链表,调OS_Sched()。如果没有任务等待,直接返回ECB到空闲链表。

OSSemPend()

请求信号量,如果该ECB有信号量,直接减1,返回;

如果没有,设置TCB3个域,超时(如果是0,就不存在时间滴答1-0的过程所以死等到有事件发生为止),OSTCBCur->Stat标志标记上信号量,OSTCBCur->StatPend默认设置为OS_STAT_PEND_OK,调用OS_EventTaskWait()

然后掉OS_Sched()任务切换

切换回来的时候:做个判断是那种原因切换回来的

如果是超时到,一定是OSTimeDlyResume或中断中的

OSTimeTick 2个函数做过处理,设置OSTCBCur->Stat(被清掉)和OSTCBCur->StatPend=OS_STAT_PEND_TO,但是ECB中的任务等待表、组还没请所以调用OS_EventTaskRemove()

如果是放弃等待或正常等到了事件,那么只需要标记返回信息,在切换回来的时候都已经被OS_EventTaskRdy()恢复过了。

最后统一把当前TCB3个相关域设置为OS_STAT_RDYOS_STAT_PEND_OK、指向ECB的指针变量清0,退出该函数

OSSemAccept()

无等待的请求信号量,如果有信号量减1,没有的话就直接退出,返回值是做减之前的信号量值,很明显如果不是0说明有信号量。

OSSemPendAbort()

放弃信号量等待,参数是某个信号量类型ECB指针,如果有效的ECB没有任务被阻塞,那么直接退出,如果有任务被阻塞,参数opt 2中取值决定操作:

OS_PEND_OPT_BROADCASE 广播方式,所有被阻塞的任务都被以放弃等待的方式就绪(实现方式如果就绪组不为0就一直循环执行OS_EventTaskRdy函数,里面有选最高优先级的功能)。

OS_PEND_OPT_NONE非广播方式,OS_EventTaskRdy函数只被执行一次,选当前被该事件阻塞的最高优先级任务就绪。

因为可能有比当前任务优先级更高的任务就绪所以调OSSched()

OSSemPost()

提交信号量,如果就绪组不为0(有任务被阻塞)以等到事件的方式执行OS_EventTaskRdy,就绪被阻塞最高优先级的任务;如果为0(没有任务被阻塞),单纯的让信号量数加1就行了。

OSSemQuery()

和内存管理查询类似,有个OS_SEM_DATA类型结构体,主要是存一下指定的某个信号量类型的ECB的就绪组、表和信号量数3个信息。

互斥信号量:

函数名

功能简介

OSMutexCreate()

创建带升级优先级的互斥类型信号量,如果指定的优先级未被占用那么把OSTCBPrioTbl[Pric]=1,占着防止别人抢,为了后面升级用。另外ECB的域OSEventCnt意义不同了,16位高8位为待升级到的优先级,低8为默认0Xff,表示还没有任务占用该互斥信号量,不是0xFF表示占用该ECB的任务的优先级。另外ECB的域(void *OSEventPtr可以指向占用他的任务的TCB指针了,源代码中请求事件成功后只有ECB指向TCBTCB没有指向ECB,TCBECB指针要指向ECB必须该任务被ECB事件阻塞起来,在该域初始化后默认为0.

OSMutexDel

删除指定互斥类型的ECB,首先判断并标记是否有任务被阻塞因该事件,参数opt决定2种操作方式:

OS_DEL_NO_PEND(没有被阻塞时才进行删除操作)如果有事件被阻塞错误退出;如果没有,首先找到升级优先级的TCB指针OSTCBPrioTbl[],内容从1清为0(是1的原因:创建时如果成功就被1占着,即使该ECB被某任务占着,如果升级了一定会有阻塞任务,当然阻塞了如果比占着的优先级低就只阻塞不升级,因为没有阻塞的任务所以一定不会升级使用,所以还是1),然后,初始化ECB的域后放到空闲的ECB链表中。

OS_DEL_ALWAYS任何情况都删除该ECB

首先取得升级优先级和实际优先级(可能为0xFF未用),如果确实发生了升级通过OSMutex_RdyAtPro(ptcb,prio)恢复为prio优先级,然后如果有被阻塞的任务让所有因该事件被阻塞的任务全部就绪,

让升级优先级的OSTCBPrioTbl[]TCB指针清0,然后设置ECB的域,返回给ECB空闲链表,调用OSSched()

OSMutex_RdyAtPro()

让升级了的任务优先级恢复原优先级并使之就绪,首先清0这个任务在就绪表、组,然后从新设置TCB中的相关域,设置全局变量

就绪表、组标记为原优先级的就绪状态,优先级指针表存TCB *

OSMutexPend()

 

为什么要优先级升级?

假如较低优先级的任务50请求互斥信号量A,请求到了,较高优先级3去请求发现被占,只能挂起,必须等待优先级为50的任务释放,但是如果这个过程中像2030优先级的任务发生就绪,50的优先级必须让出CPU,如果2030执行还未完,78优先级又就绪,导致50号任务迟迟得不到CPU,高优先级3相对重要的任务一直得不到执行,这是不允许的,所以采用优先级升级。优先级升级思想就是,创建互斥信号量时指定一个优先级2(假如),如果被低优先级50占,有高优先级3请求,50的优先级会升到2,待释放互斥信号量、或要删除该信号量时才恢复成50,这样可以避免2030这样的任务抢CPU

请求互斥信号量,如果域OSEventCnt8位位0xFF表示未被任务占用,设置ECB的相关域,返回。如果被其他任务占用,从ECB中取得占用的TCB指针和优先级,判断只有当占用ECB的优先级比升级优先级和当前请求优先级都低的时候才进行升级,然后就是升级操作(不需要升级就不操作这块),然后操作和普通信号量类似都是设置TCB3个域,超时(如果是0,就不存在时间滴答1-0的过程所以死等到有事件发生为止),OSTCBCur->Stat标志标记上互斥信号量,OSTCBCur->StatPend默认设置为OS_STAT_PEND_OK,

调用OS_EventTaskWait();然后掉OS_Sched()任务切换

切换回来的时候:做个判断是那种原因切换回来的,

如果是超时到,一定是OSTimeDlyResume或中断中的

OSTimeTick 2个函数做过处理,设置OSTCBCur->Stat(被清掉)和OSTCBCur->StatPend=OS_STAT_PEND_TO,但是ECB中的任务等待表、组还没请所以调用OS_EventTaskRemove()

如果是放弃等待或正常等到了事件,那么只需要标记返回信息,在切换回来的时候都已经被OS_EventTaskRdy()恢复过了。

最后统一把当前TCB3个相关域设置为OS_STAT_RDYOS_STAT_PEND_OK、指向ECB的指针变量清0,退出该函数

升级操作具体:

首先判断升级后的任务是否就绪并标记,如果就绪直接清就绪表、组,如果被挂起的,判断TCBECB指针是否为0推断是否因事件而挂起,是0表示不是等待事件而挂起(有可能是被挂起函数直接挂起),非0表示因事件而挂起(如果是因事件而挂起,要清该事件的等待任务表、组),然后设置其TCB中相关域,按照原来的任务状态设置就绪表、组,ECB任务等待表、组的值。恢复后的优先级的TCB指针表存该TCB指针。

OSMutexPost()

判断是否进行了优先级升级如果有恢复,判断是否有任务在等待该互斥信号量:

没有的话就设置该ECB 2个参数OSEventCnt8位为0xFFOSEventPtr=0,然后退出;如果有任务在等待,那么就执行一次事件恢复函数OS_EventTaskRdy(),设置ECB 2 2个参数OSEventCnt8位为任务优先级,OSEventPtr指向其TCB,然后发生任务切换,切换回来退出。

OSMutexAccept()

无等待请求,判断是否互斥信号量被占,如果没被占就使用,如果被占就标记错误返回信息,直接退出,不需要升级操作。

OSMutexQuery()

查询互斥信号,OS_MUTEX_DATA结构体中存5个域,等待任务表、组,原来优先级、升级优先级、逻辑变量是否进行过升级。

邮箱

函数名

功能简介

OSMboxCreate()

创建邮箱,很简单,取一个空闲的ECB,然后,标记事件类型,OSEvenPtr域为存消息的地方,创建时参数pmsg作为参数传进,所以创建时可以有消息也可没有,其余域清0

OSMboxPend()

请求指定消息类型ECB消息,如果消息域OSEventPtr不为空,取出消息,清零,消息作为返回值返回;如果为0,处理和普通信号量类似,设置当前TCB 3个域,等待时间域(如果是0,就不存在时间滴答1-0的过程所以死等到有事件发生为止),OSTCBCur->Stat标志标记上邮箱,OSTCBCur->StatPend默认设置为OS_STAT_PEND_OK,调用OS_EventTaskWait()

然后掉OS_Sched()任务切换

切换回来的时候:做个判断是那种原因切换回来的

如果是超时到,一定是OSTimeDlyResume或中断中的

OSTimeTick 2个函数做过处理,设置OSTCBCur->Stat(被清掉)和OSTCBCur->StatPend=OS_STAT_PEND_TO,但是ECB中的任务等待表、组还没请所以调用OS_EventTaskRemove(),注意设置返回存消息的变量pmsg0

如果是放弃等待,设置返回存消息的变量pmsg0

如果是正常等到了事件,从当前TCBOSTCBMsg域取消息存入返回值,和普通信号量不同吧,因为提交信号量的函数把消息已经存入了当时处于等待任务表中最高优先级的该任务的TCB消息域中。

最后统一把当前TCB4个相关域设置为OS_STAT_RDYOS_STAT_PEND_OK、指向ECB的指针变量清0TCB存消息的域清0(这是比普通信号量多出的操作),然后返回存消息的变量pmsg(有消息返回的就是消息,没有返回的就是0),然后退出函数,到底是3种原因的那种原因通过传入的参数变量来存储信息,外部知道。

信号量和邮箱请求的区别:主要是返回处理,邮箱要多出对TCB的消息域清空处理,填写存返回消息的变量。

OSMboxPost()

往指定邮箱类型ECB存消息,返回结果信息,分3种情况

情况1:等待任务表不为0,表示有任务被事件挂起,要调用事件恢复函数OS_EventTaskRdy(),传入的参数包括消息pmsg,以等到了消息的方式就绪。然后掉OS_Sched()任务切换,回来时退出

情况2:等待任务表为0,但ECB存消息的域OSEventPtr不为0,表示有消息在ECB,只能标记邮箱已满,退出

情况3:没有消息、也没有任务在等,直接投到ECB中,情况13都是正常的提交,而情况2为邮箱已满错误退出

OSMboxDel()

通过参数opt判断是无任务等待才删除OS_DEL_NO_PEND

还是OS_DEL_ALWAYS强行删除。首先判断ECB的等待任务组是否为0来知道是否有任务被挂起并通过变量标记,然后

第一种情况:判断标记变量为没有任务等待就初始化ECB然后返还给ECB空闲链表,如果有就错误退出。

第二种情况:只要等待任务组不为0就循环执行事件恢复函数OS_EventTaskRdy(),传入的参数包括消息pmsg=0,以等到了消息的方式就绪。之后也是初始化ECB返还给空闲的链表,做个判断如果标记变量表明确实之前有任务时被挂起的,那么刚才肯定有新任务被就绪了,所以执行OSSched()调度下。

OSMboxPendAbort()

讲指定邮箱类型的处于等待被挂起的任务就绪,和普通信号量操作非常类似,首先通过该ECB的等待任务组判断有没有任务在等待,如果没有就不需要回复直接退出。

如果有任务被挂起那么通过操作变量opt(连操作码都一样)判断:

OS_PEND_OPT_BROADCASE 广播方式,所有被挂起的任务都被以放弃等待的方式就绪(实现方式如果就绪组不为0就一直循环执行OS_EventTaskRdy函数,里面有选最高优先级的功能),注意消息参数传0

OS_PEND_OPT_NONE非广播方式,OS_EventTaskRdy函数只被执行一次,选当前被该事件阻塞的最高优先级任务就绪,注意消息参数传0

因为可能有比当前任务优先级更高的任务就绪所以调OSSched()

OSMboxAccept()

无等待请求邮箱消息,很简单如果没有就错误退出,如果有就取ECB消息,并将ECB存消息的域清0,正常退出

OSMboxQuery()

OS_MBOX_DATA的填写信息域3个存消息void* OSMsg,实际上是个地址,真正的消息在这个地址所指向的地方,任务等待表、组。

注意:消息这里实际上只是void*指针,真正的消息是这个指针所指向的内容,具体存消息地方自己去定义啦,另外选void*好可以存任意类型的消息地址,如果信息量比较大可消息可能是结构体,使用消息的时候强制转换为该类型结构体地址。

 

消息队列:

函数名

功能简介

OS_QInit()

初始化消息队列,和前面3种事件管理不同,消息队列引入了新的数据结构队列控制块QCB,这些结构体也要建立空闲连,每个QCB可以管理一个消息队列,OS_Q OSQTbl[OS_MAX_OS]ucos_ii。中定义,这个函数主要做的事是:把定义的这个结构体数组内容全部清0,然后构建链表,OSQFreeList指向空闲队列链表表头。

OSQCreate()

传入的参数是用户定义的存消息指针的地址的一块内存区地址(可见是指针的指针类型,如果做加法操作实际地址浮动4个自己(32CPU,32位地址线))和存消息地址的尺寸。

创建消息队列函数,首先要从ECB空闲链表中取一个ECB,从空闲的队列链表中取一个QCB,ECB中类型设置为消息队列OS_EVENT_TYPE_Q,ECB的指针域指向QCB,QCB中设置相关域有:

OSQStart域赋值用户定义的存消息指针的地址,第一个参数

OSQEnd域存通过第一、二参数算出来的存消息地址的末地址

OSQIn下次插入消息地址的地址

OSQOut下次取消息地址的地址

OSQSize队列的尺寸,就是第二个参数

OSQEnteries以及当前存了多小个消息地址

当然是用ECB还是调用了OS_EventWaitListInit()ECB等待任务组、表

OSQPost()

提交消息到队列,首先通过ECB判断等待任务组是否为0来得知是否有任务被该事件挂起:

若不为0不需要把消息投到队列而是直接投给当前等待任务表中最高优先级的任务,调用事件就绪函数

OS_EventTaskRdy(),传入的参数包括消息pmsg,以等到了消息的方式就绪,然后就是调用OSSched()任务切换,返回来时正常退出。

若为0,通过ECB的指针域获得QCB指针来操作,如果当前的队列有的消息数大于等于尺寸数(实际上最多就是等于)就错误返回,返回错误为,队列已经满了,如果消息队列未满,就如下代码:

非常经典的代码:

*pq->OSQIn++=pmsg 消息存入插入消息的位置

*pq->OSQEntries++ 队列消息数目加1

然后是if(pq->OSQIn==pq->OSQEnd)

{

pq->OSQIn=pq->OSQSart

}这段代码很关键,说明是先进先出的循环队列,只有插入点到了队列地址的末尾就插到最前面去。最后退出

OSQPend()

请求指定消息队列的消息,首先通过参数ECB指针获得QCB指针,

判断队列中是否有消息,如果有取一个消息地址,存入返回变量中,然后对队列调整,QCB相关参数设置,正常退出。

如果发现消息队列没有消息,处理和邮箱、普通信号量很类似了,

设置当前TCB 3个域,等待时间域(如果是0,就不存在时间滴答1-0的过程所以死等到有事件发生为止),OSTCBCur->Stat标志标记上队列,OSTCBCur->StatPend默认设置为OS_STAT_PEND_OK,调用OS_EventTaskWait()

然后掉OS_Sched()任务切换

切换回来的时候:和邮箱处理一模一样,下面为粘贴过来的

如果是超时到,一定是OSTimeDlyResume或中断中的

OSTimeTick 2个函数做过处理,设置OSTCBCur->Stat(被清掉)和OSTCBCur->StatPend=OS_STAT_PEND_TO,但是ECB中的任务等待表、组还没请所以调用OS_EventTaskRemove(),注意设置返回存消息的变量pmsg0

如果是放弃等待,设置返回存消息的变量pmsg0

如果是正常等到了事件,从当前TCBOSTCBMsg域取消息存入返回值,和普通信号量不同吧,因为提交信号量的函数把消息已经存入了当时处于等待任务表中最高优先级的该任务的TCB消息域中。

最后统一把当前TCB4个相关域设置为OS_STAT_RDYOS_STAT_PEND_OK、指向ECB的指针变量清0TCB存消息的域清0(这是比普通信号量多出的操作),然后返回存消息的变量pmsg(有消息返回的就是消息,没有返回的就是0),然后退出函数,到底是3种原因的那种原因通过传入的参数变量来存储信息,外部知道。

信号量和邮箱请求的区别:主要是返回处理,邮箱要多出对TCB的消息域清空处理,填写存返回消息的变量。

OSQDel()

删除消息队列中当前被挂起的任务,也是首先通过ECB的任务等待组来判断并标记是否有任务在等待,

通过参数opt(和邮箱、互斥信号量掩码都一样)判断是否无任务等待才删除OS_DEL_NO_PEND

还是OS_DEL_ALWAYS强行删除。

对于操作码OS_DEL_NO_PEND

如果标记变量表面有任务在等待错误退出,如果没有,初始化该QCB,,ECB分别放入各种的空闲链表中。

对于操作码OS_DEL_ALWAYS

只要等待任务组不为0就循环执行事件恢复函数OS_EventTaskRdy(),传入的参数包括消息pmsg=0,以等到了消息的方式就绪,时间类型为消息队列这和邮箱几乎一模一样。之后就初始化该QCB,,ECB分别放入各种的空闲链表中。

做个判断如果标记变量表明确实之前有任务时被挂起的,那么刚才肯定有新任务被就绪了,所以执行OSSched()调度下(如果没有就不需要了)。一模一样和邮箱操作。

 

比较邮箱和消息队列删除的不同:

唯一操作上的不同就是释放是邮箱仅仅是讲ECB放回ECB空闲链表,而队列多了一个将QCB放回QCB空闲链表,其余一模一样。

OSQQuery()

查询指定消息队列信息,OS_Q_DATA的信息域5

即将被取到的消息void *OSMsg

消息队列中消息数目

消息队列尺寸

事件的任务等待表、组

事件标志组:

数据结构介绍:事件标志组这个东东比较怪,虽然挂着事件2个事,执行的是事件管理,但压根就没有用事件控制块ECB来管理,完全是自己的一套数据结构,数据结构不同了,自然那些公用函数用不到了,自己有自己的挂起函数OS_FlagBlock()

和就绪函数OS_FlagTaskRdy()

关键数据结构式事件标志组OS_FLAG_GRP,包含4个域,

INT8U OSFlagType事件标志类型用的是填宏 OS_EVEN_TYPE_FLAG

Void *OSFlgWaitList 这个参数当在事件标志组空闲链表时指向下一事件标志组,

当在作为真正管理事件时指向事件标志节点链表的表头

OS_FLAGS OSFlagFlags 事件标志实际上,OS_FLAGS就是8位、16位或32位类型重定义

具体是那种取决你想怎么管理,事件多小。

关键数据结构式事件标志节点OS_FLAG_NODE包含6个域

节点的前后驱链指针,void *OSFlagNodeNext void *OSFlagNodePrev

指向等待任务的TCB void *OSFlagNodeTCB;

指向被管理的时间标志组Void *OSlagNodeFlagGrp

OS_FLAGS OSFlagNodeFlags;该任务需要等待的哪些事件标志组合

INT8U OSFlagNodeWaitType 取值是4种宏

OS_FLAG_WAIT_SET_ALL 表示请求的事件标志位全部置1才能算等到事件

OS_FLAG_WAIT_ SET_ANY 表示请求的事件标志位只要有任意一个1就算等到事件

OS_FLAG_WAIT_CLR_ALL 表示请求的事件标志位全部为0才能算等到事件

OS_FLAG_WAIT_CLR_ANY 表示请求的事件标志位只要有任意一个0就算等到事件

函数名

功能简介

OSFlagInit()

OSInit中被调用属于内部函数,主要做的事就是清0所有实际标志组数组内存,构建空闲事件标志组链表,然后让OSFlagFreeList指向链首。

OSFlagCreate()

从时间标志组空闲链表表头去一个GRP然后设置类型和初始事件

OS_FlagBlock()

内部函数,功能是设置TCB中相关域

3+1OSTCBCur->OSTCBStat|=OS_STAT_FLAG

OSTCBCur->OSTCBStatPend=OS_STAT_PEND_OK

OSTCBCur->OSTCBDly=timeout

这三个域设置和以前事件请求函数中一模一样

OSTCBCur->OSTCBFlagNode=pnode指向标志节点

这个功能类似于以前的TCB指向ECBOSEventPtr,以前是划分到OS_EventTaskWait()事件挂起函数中的。另外就是清就绪组、表中的标志。

可见这部分功能划分不一样了把原来的事件请求函数中的部分划到了标志组事件挂起函数中来了。

还有部分操作就是事件标志节点的域设置,是节点指向TCB,实现真正的互指。

 

OS_EventTaskWait()比较:OS_EventTaskWait清就绪表、组,设置ECB的等待任务表,任务组,完成TCBECB的指向;

OS_FlagBlock进行TCB的本该在请求事件函数中设置的三项,然清就绪表、组,完成TCB中到事件标志节点的指向,

并没有清等待任务表任务组操作(因为压根就没有用ECB),但是它还有其他操作,就是对事件标志节点设置,使标志节点插入事件标志节点链表,并且同时指向TCB和事件标志组GRP

 

问题来了既然没有像ECB一样的等待任务表、组,他是怎么实现选择、查找人任务并让其就绪的,原来它是通过循环遍历时间标志节点,符合条件的任务都就绪,还有问题如果是消费类型的事件标志在后面查找到的任务岂非很不公平?

OSFlagPend()

事件标志组请求函数,首先通过判断等待类型参数是否有清除标志并记录在另外定义的局部变量中,如果有消费类型的标志在标记变量后要清掉,swich分支判断:

A如果匹配OS_FLAG_WAIT_SET_ALL:

用请求的flags事件组合同GPR事件标记做与运算,如果结果和flags相同(即GPR包含flags的全部事件),等到了事件组,如果有清除标志就清了GPR中的相应事件组,并把flags存入当前TCB的域OSTCBFlagsRdy中,然后正常退出。但与结果同flags不相等,那么调用OS_FlagBlock(pgrp,&node,flags,wait_type,timeout),该任务就被挂起来了。

B如果匹配OS_FLAG_WAIT_SET_ANY

作与运算只要不为0,就算成功,如果有消费标志,把GPR中共同部分标志事件记录到当前TCB的域OSTCBFlagsRdy中,同时清掉它在GPR中的共同位清掉,退出。如果没有匹配成功也该任务就被挂起来了OS_FlagBlock(pgrp,&node,flags,wait_type,timeout),

C如果匹配OS_FLAG_WAIT_CLR_ALL

GPR的事件标志位取反同flags作与运算,如果和flags相同,表面匹配成功,如果又有消费标志就把GPRflags的项全部置1,也记录到TCB中,正常退出。如果匹配失败也是任务就被挂起来了OS_FlagBlock(pgrp,&node,flags,wait_type,timeout),

D如果匹配OS_FLAG_WAIT_CLR_ANY

GPR的事件标志位取反同flags作与运算,如果结果不为0,说明原来GPR中对于flags至少有1位为0,表示匹配成功,如果有消费标志把共同的部分位置1,退出。如果匹配失败,也是任务就被挂起来了OS_FlagBlock(pgrp,&node,flags,wait_type,timeout),

这样switch4种情况就匹配完了,出来的时候必定是没有匹配成功,将当前任务挂起来了,所以调用OSSched()

切换回来的时候:判断是什么原因切换回来,情况一:如果是超时到或放弃等待那么,对OSTCBCur->OSTCBStat=OS_STAT_RDY

OSTCBCur->OSTCBStatPend=OS_STAT_PEND_OK

调用OS_FlagUnlink()使得OSTCBCurOSTCBFlagNode如果做了记录就会清0,最后退出,退出返回的错误码表示到底是超时到还是放弃等待。情况二:正常等待了满足条件的标志,如果不是消费类型就,返回正常退出信息码退出,如果有消费类型标志,还得从TCB中域OSTCBFlagsRdy存的那个请求组合,该清00该置11

OS_FlagUnlink()

参数是处于链表中的事件标志节点地址,功能是把该节点移除链表,如果使能了删除事件标志组的宏OS_TASK_DEL_EN,那么还要把TCB指向该节点的域OSTCBFlagNode0

OSFlagDel()

首先就通过GPR的域OSFlagWaitList是否为0来判断有没有任务处于等待状态并通过标志变量标记下。

通过参数opt(和邮箱、互斥信号量掩码都一样)判断是否无任务等待才删除OS_DEL_NO_PEND

还是OS_DEL_ALWAYS强行删除。

对于操作码OS_DEL_NO_PEND

如果标志变量表明有任务在等待就退出,返回错误码信息;如果没有任务等待就把该GPR初始化然后放回到事件标志组空闲链表中。

对于操作码OS_DEL_ALWAYS

首先不管三七二十一,有没有任务等待都取GPROSFlagWaitList

存入临时变量,只要不为0就循环遍历下去,遍历到节点就调用

OS_FlagTaskRdy(pnode,(OS_FLAGS)0);就绪完所有的任务之后初始化GPR,返回给空闲GPR链表如果有就绪的任务就调用函数

OS_SChed();

OS_FlagTaskRdy()

一般用在提交事件标志组合删除事件标志组中常用,清楚该节点对于的任务等待事件标志组这样操作,并不表示他就没有等待其他的操作,首先设置TCB4个相关域

OSTCBCur->OSTCBStat&=~OS_STAT_FLAG

OSTCBCur->OSTCBStatPend=OS_STAT_PEND_OK

OSTCBCur->OSTCBDly=0

赋值给域OSTCBFlagsRdy,显然删除的时候直接赋0的。

实际上有5个域还有TCB对节点指针在OS_FlagUnlink()0

如果发现清掉等待事件标志组的实际状态就变成OS_STAT_RDY那么真的要设置就绪表、组,让其调用一次OS_SChed();和事件就绪函数完全不相同

OSFlagAccept()

无等待请求,这个函数看起来复杂实际很简单,首先标记是否是消费类型,然后按照4中匹配方式去请求事件,如果成功就按照OSFlagPend()ABCD4种方法去消费处理,如果没有当然就直接返回错误码退出撒。

OSFlagPost()

参数4个,指定事件标志组GPR指针,OS_FLAG_GPR *pgrp

提交的位组合OS_FLAGS flags

提交方式INT8U opt是清除OS_FLAG_CLR

还是置位OS_FLAG_SET

如果是前者,那么就把GPROSFlagFlags域同~flags与清除

后者,就同flags或运算置位

定义标记变量致使是否切换任务,默认不切换,通过GPR遍历事件标志节点,如果满足条件就就绪,就绪函数只要调用过就把标志变量标记为要切换,出来的时候就通过标志判断需不需要发生切换,如果要OS_Sched(),之后退出。

你可能感兴趣的:(UCOS)