uCOS事件相关函数代码理解

事件控制块/句柄/结构体

struct  os_flag_grp {                                       

    OS_OBJ_TYPE  Type;    /* Should be set to OS_OBJ_TYPE_FLAG */

    CPU_CHAR    *NamePtr;   //指向事件的名字,字符串类型

    OS_PEND_LIST    PendList;   //事件的等待列表

    OS_FLAG_GRP    *DbgPrevPtr;  //指向事件调试列表中的前一个事件

    OS_FLAG_GRP    *DbgNextPtr;  //指向事件调试列表中的后一个事件

    CPU_CHAR       *DbgNamePtr;  //该事件在调试列表中的名字

    OS_FLAGS    Flags;  //事件标志位的状态

    CPU_TS      TS;    //保存该事件最后一次被释放的时间戳

    CPU_INT32U    FlagID;  //第三方调试器和跟踪器的唯一ID

扫描二维码关注公众号,回复: 16630975 查看本文章

};

事件函数

{

OSFlagCreate():事件创建函数。和其他创建内核对象的函数一样,首先进行各种检查,然后都是初始化结构体元素的值。这里独特的是有一个32位变量,代表32个不同事件,这里一般初始化为0,表示所有事件类型都未发生。不要忘了在创建成功后,给事件全局变量OSFlagQty加1,这是uCOS的传统了,每创建好一个内核对象,都需要让相应的全局内核对象个数加1。

OSFlagDel():事件删除函数

{

删除内核对象的函数都是清空里面的内容。所以事件的删除函数也不例外。删除时总会有两个选项:在等待列表中没有任务时删除和不管有没有任务都删除。所以第一步肯定是检查等待列表中有无任务,并记录个数。然后,在OS_OPT_DEL_NO_PEND选项下,只有当等待任务数为0时,执行把事件移出调试列表;事件数减1。并调用OS_FlagClr()清空结构体内容,为了表明该事件是被创建后删除的,清空结构体函数中会把类型元素赋值为无类型;名字位置为未知名字;事件标志Flags清0;然后调用OS_PendListInit()将等待列表进行创建时的初始化操作,即把等待列表中的元素清0。如果在OS_OPT_DEL_ALWAYS选项下,那么就while循环,先将任务全部移出等待列表,然后再进行上面的操作。值得注意的是,因为这里可能有任务被移出等待列表,为了提高系统实时性,调用OSSched()进行任务切换。

返回:等待列表中的任务数。

}

OSFlagPost():事件设置函数  

{

函数思想:OSFlagPost()用于设置事件组中指定的位,(通过参数指定的事件标志来设置事件的标志位),当位被置位(置1)之后,表明事件发生或者待处理。然后遍历等待在事件对象上的事件等待列表,判断是否有任务的事件激活要求与当前事件对象标志值匹配,如果有,则唤醒该任务。

4个入口参数:指向事件的指针;事件标志位;选项;返回的错误类型。这里的选项有3个:OS_OPT_POST_FLAG_SET,OS_OPT_POST_FLAG_CLR,OS_OPT_POST_NO_SCHED,但是必须包含置1和清0标志位选项的一个。可以是置1触发,也可以为清0触发

返回:事件当前标记值。

注意:因为事件同二值信号量一样具有同步作用,既可以作用任务与任务之间,也可以实现任务与中断之间的同步。所以该函数可以在中断中被调用。

函数过程:① 调用OS_TS_GET()获取时间戳,作为发布时的时间戳。② 如果使能了中断延迟发布,并且事件设置函数OSFlagPost()在中断中被调用,应该调用OS_IntQPost()将该事件发布给中断消息队列。中断延迟发布将事件(消息等)的设置(发送)放在指定发布任务中,此时系统就将事件(消息等)发布到租单消息队列中,等待到中断发布任务唤醒再设置事件(发送消息)。③ 如果没有使能中断延迟发布,调用OS_FlagPost()直接将事件对应位置位/清0。

-调用OS_FlagPost()

5个入口参数:这里除了OSFlagPost()四个参数以外,增加了一个时间戳的参数,用于将时间戳赋值给事件结构体变量,所以事件结构体中的时间戳变量CPU_TS  TS用于保存该事件最后一次被释放的时间戳。

返回:事件当前标记值。

函数过程:① 关中断,根据选项参数和事件标志位参数flags对事件结构体中的事件标志位元素进行修改。置位用或运算,清零用与运算,因为入口参数flags的每一位只代表事件标志,根据选项是set或者clr进行事件结构体元素Flags,清0是把flags先按位取反再与事件结构体的元素p_grp->Flags相与,这样才能清0。然后将时间戳存入事件。② 获取等待列表中的任务数,如果没有任务等待该事件(任务数为0),那么就开中断,直接返回事件的当前标记值。(注意:事件同信号量一样,只是一个标志,只需要标记哪些事件发生与否就好。不像消息队列,消息队列在没有任务等待消息时,需要把消息送入消息列表,避免数据丢失)。③ 如果有任务等待该事件,那么就要遍历等待列表中的任务,看任务等待的事件是否发生,并且查看是所有事件发生触发还是只需其一。在while循环中通过将任务TCB中事件选项的元素FlagsOpt与宏OS_OPT_PEND_FLAG_MASK相与得到任务事件模式,然后用switch--case语句对模式进行判断。模式OS_OPT_PEND_FLAG_SET_ALL要求任务的所有事件都发生,那么让事件标志与任务等待事件标志相与p_grp->Flags & p_tcb->FlagsPend,只有相与的结果等于p_tcb->FlagsPend(在任务调用事件等待函数时,在那个函数中,会把任务感兴趣的事件赋值给p_tcb->FlagsPend元素)才表明所有事件发生,那么任务不再阻塞等待,调用函数OS_FlagTaskRdy()将任务等待事件状态的元素FlagsRdy赋值为flags_rdy;然后根据任务状态把任务移出等待列表。注意如果是超时等待,需要将任务移出时基列表,然后加入就绪列表中。将任务移出等待列表的函数OS_PendListRemove()只是将任务从等待链表中移除,并没有关联移出或者插入就绪列表和时基列表的操作。模式OS_OPT_PEND_FLAG_SET_ANY表明任务等待事件中任一事件发生都能唤醒任务。那么只需要事件标志与任务等待事件标志相与p_grp->Flags & p_tcb->FlagsPend后的值不为0就表明有事件发生,就可以调用函数OS_FlagTaskRdy()让任务就绪。对待清0事件标志的两个标志OS_OPT_PEND_FLAG_CLR_ALL和OS_OPT_PEND_FLAG_CLR_ANY也是如此,只不过~p_grp->Flags & p_tcb->FlagsPend,是让事件标志先按位取反后再相与。因为无论是这里的入口参数flags还是TCB中事件等待标志位元素FlagsPend都是没有clr和set属性的,比如置位或者清0第0,4,5位,这两个参数都等于0x31,而不会说根据属性来,set等于0x31,而clr等于0xFFFFFFCE。当然,参数flags的值不用计算,可以通过移位的方式使相应位置位,比如设置事件掩码的位0,那就#define  EVENT_0  (0x01 << 0),设置事件掩码的位1,那就#define  EVENT_1  (0x01 <<1),设置第n位就向左移n位。如果是多事件组合,那么就把它们相或即可。④ 如果没有选择OS_OPT_POST_NO_SCHED,那就调用OSSched()进行任务切换。发现没有?内核对象的post函数都是可选任务调度。因为任务调用post函数时,该任务的状态不会改变,如果没有什么需要,可以不进行切换;但是由于存在等待列表中有任务获得内核对象移除等待列表,插入就绪列表的情况,如果新移出的任务优先级比较高,应切换调度,使更紧急的任务快速响应。⑤ 标记无错误,获取当前事件标记flags_cur = p_grp->Flags;并返回flags_cur。

接下来我们来看一看uCOS模式选择的程序:通过将任务TCB中事件选项的元素FlagsOpt与宏OS_OPT_PEND_FLAG_MASK相与得到任务事件模式。模式类型如下:

#define  OS_OPT_PEND_FLAG_MASK       (OS_OPT)(0x000Fu)  1111

#define  OS_OPT_PEND_FLAG_CLR_ALL     (OS_OPT)(0x0001u)   0001

#define  OS_OPT_PEND_FLAG_CLR_ANY     (OS_OPT)(0x0002u)   0010

#define  OS_OPT_PEND_FLAG_SET_ALL     (OS_OPT)(0x0004u)    0100

#define  OS_OPT_PEND_FLAG_SET_ANY     (OS_OPT)(0x0008u)   1000

由此可见,每个模式类型都是16位(16进制的一个0就代表4位二进制,即4个二进制0)。其实这里的每个模式类型都通过一个独特的位置1来表示。uCOS真的很爱用这种方式。由于mode  = p_tcb->FlagsOpt & OS_OPT_PEND_FLAG_MASK; 所以TCB中事件选项的元素FlagsOpt也应该是在事件等待函数中,进入等待列表被赋予以上类型的值的

}

OSFlagPend():事件等待函数

{

函数思想:任务调用事件等待函数时,可以指定一个超时时间(也可以不指定,为0就是一直等待)。如果事件已经发生,则根据等待选项来决定是否清除事件的相应标志位,并且返回事件标志位的值。如果事件还未发生,那就进入等待列表等待事件的发生;若等待超时,自动移出等待列表,恢复就绪态。有效的体现了操作系统的实时性。事件标志分为不同模式:包括所有事件置1/清0,任意事件置1/清0。所以要根据模式判断任务是否该进入等待列表。

注意信号量和事件的等待函数只限于任务,是不允许在中断中调用的,中断遵循快进快出,之所以中断可以调用他们的释放函数,相当于是设立一个标志,表示信号量可用了或者事件发生了等等,把中断服务函数送进等待列表是个愚蠢的行为。

6个入口参数:事件指针;事件标志位;超时时间;选项;返回的时间戳(一般内核对象等待函数的时间戳在任务获得该事件时被赋予事件最后一次被创建的时间戳);返回的错误类型。

选项包括:OS_OPT_PEND_FLAG_CLR_ALL;OS_OPT_PEND_FLAG_CLR_ANY;OS_OPT_PEND_FLAG_SET_ALL;OS_OPT_PEND_FLAG_SET_ANY。这里面必选一个,除此以外,还可以或上OS_OPT_PEND_FLAG_CONSUME(标志位匹配后自动取反);还可以或上事件没有发生要不到进入等待阻塞列表的选项OS_OPT_PEND_NON_BLOCKING和OS_OPT_PEND_BLOCKING。

返回:flags_rdy,等待标志。值不为0说明等待事件成功(一般都是pend函数的入口参数flags的值,即任务所需要的事件标记值,用户根据此值与任务所需事件标记值比较,看是否相等,进而判断是否等待事件都发生);值为0说明等待事件不成功(如进入等待列表中,表示还未等到;或者出现无阻塞,调度器被锁的情况)。

函数过程:① 首先进行安全检查,中断中非法调用检查,参数选项检查,对象类型检查。选项检查中没有或上OS_OPT_PEND_BLOCKING的情况,因为该宏值为0x0000u,或与不或的结果都一样,在这里相当于如果没有事件发生默认进入等待列表。② 通过选项和opt & OS_OPT_PEND_FLAG_CONSUME的结果不为0,说明选择了OS_OPT_PEND_FLAG_CONSUME,即选择了标志位匹配后自动取反。③ 因为事件不像其他内核对象,其他内核对象没有模式类型比如信号量,有可用信号量就获取,没有可用信号量就送进等待列表等待。事件的不同模式操作不同(比如要求全部事件都发生和只需要任一事件发生,全部事件发生的判断条件和任一发生的判断条件不同;这样不同类型就需要不同代码实现,没办法合并在一起),所以要获得模式类型,在不同模式下都要判断是否事件发生,是否进入等待列表。这里通过opt & OS_OPT_PEND_FLAG_MASK得到模式mode类型,即对标志位的要求。根据模式switch--case分类判断是否任务所有要求的事件都已经发生。④ 如果模式为OS_OPT_PEND_FLAG_SET_ALL,那就让flags_rdy = p_grp->Flags & flags,如果事件都发生了(即得到的结果flags_rdy等于flags),如果设置了FLAG_CONSUME,就让p_grp->Flags &= ~flags_rdy,即让事件标志取反(这里相当于清0,表示事件已经被处理了);给任务TCB元素FlagsRdy赋值为flags_rdy,并return返回flags_rdy。如果事件不全发生(即得到的结果flags_rdy不等于 flags),那就应该进入等待列表。所以判断是否有选择OS_OPT_PEND_NON_BLOCKING,如果是,就返回错误“需要等待阻塞”,并return返回0。如果没有选择不进入等待列表,就送入等待列表。因为要改变任务状态,所以要判断调度器是否被锁,并返回错误“调度器被锁”,并return返回0;否则锁调度器,并调用函数OS_FlagBlock()将任务送进等待列表。⑤ 如果模式为OS_OPT_PEND_FLAG_SET_ANY,与set_all只有判断不一样,只需要p_grp->Flags & flags结果不为0即可,其他操作一样。⑥ 如果是CLR模式,就让 flags_rdy = ~p_grp->Flags & flags,在创建事件函数时已经分析过,因为标志没有清0和置1的性质,所以想要哪位清0或置1,值都一样;所以如果事件已经发生p_grp->Flags对应位就已经清0,所以要先取反。如果flags是0x03,性质是clr;如果事件发生后,应该就是0xx0,第0和第1位已经被清0,所以要先取反再与才好做下一步判断。其实这里可以直接相与,无非就是判断相与的结果是不是等于0,但是这里取反主要是统一判断后操作和返回值。这样做后,与set的操作就一样,而且如果事件发生返回值都与函数的入口参数flags值一样;其他情况都是返回0。CLR模式与SET模式另一个不同点在于,如果选择了OS_OPT_PEND_FLAG_CONSUME,事件发生后,p_grp->Flags |= flags_rdy,是进行或运算来进行标志位匹配后取反操作(SET是清0,CLR是置1)。⑦ 调用OSSched()进行任务切换。因为能够执行到这里,说明任务都已经进入了等待列表,因为如果是等到了事件或者是有无效的操作时,都已经return返回了。⑧ 函数运行到这里,说明任务已经从等待列表中移出,要么事件已经发生,要么是超时或者被中止、删除;所以根据任务状态分类处理。与其他内核对象不同的是,因为有选项OS_OPT_PEND_FLAG_CONSUME,所以这里事件发生后,应该根据事件模式用与运算或者或运算修改事件结构体的变量p_grp->Flag。最后返回flags_rdy和无错误。

-调用OS_FlagBlock()

① 首先将任务的TCB元素FlagsPend、FlagsOpt和FlagsRdy赋值。因为在创建事件函数中,创建了事件,会遍历等待列表中任务的情况,就要根据任务的事件选项类型FlagsOpt(事件全发生还是任一)和事件等待标志FlagsPend(哪些事件),判断任务等待的结果,是否等来了想要的事件。而对于FlagsRdy赋值为0,表示等待事件还未发生,或者说为0就是事件等待不成功。② 调用OS_Pend(),该函数先调用OS_TaskBlock()将任务移出就绪列表,如果有超时,还会插入时基列表。然后调用OS_PendListInsertPrio()将任务按照优先级插入等待列表。

}

}

猜你喜欢

转载自blog.csdn.net/m0_43443861/article/details/126250049