在实际的应用之中,一个任务经常需要等待多个信号量的同时生效,或者说任务需要根据多个信号量的组合作用的结果来决定任务的运行方式,为了实现这种多信号量组合的功能,ucos实现了信号量集的特殊结构.
信号量集的基础仍然是信号量,它如同一个多个信号量组成的与非门来构成逻辑结果控制任务的执行.
信号量在ucos的实现分为两个部分,第一部分叫做标志组,其中存放了信号量集中的所有信号,第二个叫做等待任务链表,链表中的每个节点对应一个正在等待信号量集的等待任务,信号量集根据这个链表来管理等待任务
不同于消息队列消息邮箱等事件,ucos定义信号量集的时候使用了一种新的结构叫做标志组,其结构如下
typedef struct os_flag_grp { /* 事件标志组*/
INT8U OSFlagType; /* 信号量集标志,必须为OS_EVENT_TYPE_FLAG */
void *OSFlagWaitList; /* 指向任务等待组第一个节点的指针 */
OS_FLAGS OSFlagFlags; /*信号量集的长度*/
#if OS_FLAG_NAME_EN > 0u
INT8U *OSFlagName;
#endif
} OS_FLAG_GRP;
前几个都有注释,注意OSFlagFlags,这是一个OS_FLAGS类型的变量,在OSFlagFlags中,一个位代表一个信号量,一个标志组能容纳多少信号量取决于OS_FLAGS的长度,而OS_FLAGS的长度定义可以使8位 16位 32位的,如下
#if OS_FLAGS_NBITS == 8u
typedef INT8U OS_FLAGS;
#endif
#if OS_FLAGS_NBITS == 16u
typedef INT16U OS_FLAGS;
#endif
#if OS_FLAGS_NBITS == 32u
typedef INT32U OS_FLAGS;
#endif
决定其长度的宏在os_cfg.h文件中指定,一般指定为16位,根据需要决定
OSFlagWaitList指向的是等待这个信号量集的任务链表的指针,一个信号量集创建之后并没有直接和任务连接上,这点之后再说,我们先看看系统如何管理信号量集
其实信号量集的管理和之前tcb ecb以及qcb的管理类似,都是空闲链表的形式,在系统中有着一个标志组数组,如下
OS_EXT OS_FLAG_GRP OSFlagTbl[OS_MAX_FLAGS];
该变量指明系统最多包含的标志组的大小, OS_MAX_FLAGS是在os_cfg.h文件中定义的宏,用户根据需要进行配置
在标志组初始化的时候可以看到如下代码
for (ix = 0u; ix < (OS_MAX_FLAGS - 1u); ix++) { /* Init. list of free EVENT FLAGS */
ix_next = ix + 1u;
pgrp1 = &OSFlagTbl[ix];
pgrp2 = &OSFlagTbl[ix_next];
pgrp1->OSFlagType = OS_EVENT_TYPE_UNUSED;
pgrp1->OSFlagWaitList = (void *)pgrp2;
#if OS_FLAG_NAME_EN > 0u
pgrp1->OSFlagName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
}
pgrp1 = &OSFlagTbl[ix];
pgrp1->OSFlagType = OS_EVENT_TYPE_UNUSED;
pgrp1->OSFlagWaitList = (void *)0;
#if OS_FLAG_NAME_EN > 0u
pgrp1->OSFlagName = (INT8U *)(void *)"?"; /* Unknown name */
#endif
OSFlagFreeList = &OSFlagTbl[0];
可以很明显的看出来,信号量集的管理类似于tcbqcb等,都是使用链表的方式管理的,在初始化的时候用一个OSFlagFreeList指向一个标志组作为空闲标志组的表头, OSFlagWaitList在没有创建标志组的时候作为链表连接各个标志组变量
OSFlagWaitList的定义如下
OS_EXT OS_FLAG_GRP *OSFlagFreeList;
这是标志组的组织,那么如何让标志组与信号量集产生关联,依靠的函数为OS_FlagBlock,原型如下
static void OS_FlagBlock (OS_FLAG_GRP *pgrp,
OS_FLAG_NODE *pnode,
OS_FLAGS flags,
INT8U wait_type,
INT32U timeout)
第一个参数是我们生成的标志组,第二个为一个pnode变量, flags为指定等待的信号量集数据,第四个为等待类型,最后为等待超时事件
挑简单的先说,等待时间不用赘述,前面已经仔细分析过,说说等待类型,标志组的等待类型分为八种,分别如下
#define OS_FLAG_WAIT_CLR_ALL 0u //信号全部有效才能继续执行任务
#define OS_FLAG_WAIT_CLR_AND 0u //但是信号有效的标识是0有效
#define OS_FLAG_WAIT_CLR_ANY 1u //信号有一个或者一个以上有效
#define OS_FLAG_WAIT_CLR_OR 1u //但是信号有效的标识是0有效
#define OS_FLAG_WAIT_SET_ALL 2u //信号全部有效才能继续执行任务
#define OS_FLAG_WAIT_SET_AND 2u //信号有效的标识是1有效
#define OS_FLAG_WAIT_SET_ANY 3u //信号有一个或者一个以上有效
#define OS_FLAG_WAIT_SET_OR 3u //信号有效的标识是1有效
举个例子,如果此时OSFlagFlags为0b00000000,此时设置为OS_FLAG_WAIT_CLR_ALL的任务就有效,而设置为OS_FLAG_WAIT_SET_ALL的信号是无效的,因为信号位为0的时候有效,全部为0,所以有效, 信号位为1的时候有效,没有一个位是1,所以无效.
还有一个OS_FLAG_NODE元素,该元素的定义如下
typedef struct os_flag_node {
void *OSFlagNodeNext; /*指向下一个node节点 */
void *OSFlagNodePrev; /* 指向上一个node节点*/
void *OSFlagNodeTCB; /*指向当前任务节点的tcb*/
void *OSFlagNodeFlagGrp;
OS_FLAGS OSFlagNodeFlags;
INT8U OSFlagNodeWaitType; /* 当前等待类型 */
} OS_FLAG_NODE;
成员OSFlagNodeFlagGrp是一个反向指向信号量集标志组的指针,在等待任务链表中删除一个成员的时候或者添加成员的时候需要用到
OSFlagNodeFlags相当于一个过滤器,能够从标志组中将请求任务所需的信号量筛选出来,其余的无关信号屏蔽掉,
建立节点与标志组连接关系的接口为OS_FlagBlock,核心代码如下
OSTCBCur->OSTCBStat |= OS_STAT_FLAG;
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* Store timeout in task's TCB */
#if OS_TASK_DEL_EN > 0u
OSTCBCur->OSTCBFlagNode = pnode; /* TCB to link to node */
#endif
pnode->OSFlagNodeFlags = flags; /* Save the flags that we need to wait for */
pnode->OSFlagNodeWaitType = wait_type; /* Save the type of wait we are doing */
pnode->OSFlagNodeTCB = (void *)OSTCBCur; /* Link to task's TCB */
pnode->OSFlagNodeNext = pgrp->OSFlagWaitList; /* Add node at beginning of event flag wait list */
pnode->OSFlagNodePrev = (void *)0;
pnode->OSFlagNodeFlagGrp = (void *)pgrp; /* Link to Event Flag Group */
pnode_next = (OS_FLAG_NODE *)pgrp->OSFlagWaitList;
if (pnode_next != (void *)0) { /* Is this the first NODE to insert? */
pnode_next->OSFlagNodePrev = pnode; /* No, link in doubly linked list */
}
pgrp->OSFlagWaitList = (void *)pnode;
y = OSTCBCur->OSTCBY; /* Suspend current task until flag(s) received */
OSRdyTbl[y] &= (OS_PRIO)~OSTCBCur->OSTCBBitX;
if (OSRdyTbl[y] == 0x00u) {
OSRdyGrp &= (OS_PRIO)~OSTCBCur->OSTCBBitY;
可以看到,他会先将任务挂起,然后
pnode->OSFlagNodeNext = pgrp->OSFlagWaitList;
将任务放入等待任务列表中,最后将等待任务列表的头切换成我们刚才插入的节点,如下
pgrp->OSFlagWaitList = (void *)pnode;
这样,任务就和信号量集联系在了一起,在调度的时候我们可以说应该是在postxxx里面调度的,查看之后发现如下代码
sched = OS_FALSE; /* Indicate that we don't need rescheduling */
pnode = (OS_FLAG_NODE *)pgrp->OSFlagWaitList;
while (pnode != (OS_FLAG_NODE *)0) {
.........
pnode = (OS_FLAG_NODE *)pnode->OSFlagNodeNext;
}
if (sched == OS_TRUE) {
OS_Sched();
}
也就是说,在别的任务发送信号量集的时候,该信号量集内部会发生一次等待列表的遍历操作,选择出合适的可以调度的任务,然后调用OS_Sched进行调度,从而实现信号量集
说到这里,基本上信号量集的原理上的东西就差不多了,现在说说使用
创建信号量集
OSFlagCreate,返回创建的信号量集的指针
请求信号量集
OSFlagPend
向信号量集发送信号
OSFlagPost
查询信号量集的状态
OSFlagQuery
删除信号量集
OSFlagDel
向信号量集中添加任务(这个很重要)
OS_FlagBlock,这个函数有一个OS_FLAG_NODE的参数,在生成这个变量的时候,只需要赋值waittype和flags两个,其余的接口会自动赋值,不需要用户也不能去手动赋值,切记