当一个任务等待信号量、互斥型信号量、时间标志组或消息队列时,该任务就被放入任务挂起表或等待表中,带有任务挂起表的内核对象如下:
相关内容 | 内核对象 |
---|---|
信号量、互斥信号量 | OS_SEM、OS_MUTEX |
信号量、事件标志组 | OS_SEM、OS_FLG_GRP |
消息队列 | OS_Q |
1 内核对象数据结构
每种内核对象的头部都包含三个相同的数据域,第一个数据域为内核对象的类型,系统通过该数据域判断内核对象的类型
内核对象 | Type数据域的值(4个ASCII值) |
---|---|
信号量 | SEMA |
互斥信号量 | MUTX |
事件标志组 | FLAG |
消息队列 | QUEU |
2 任务挂起表结构
struct os_pend_list {
OS_PEND_DATA *HeadPtr; /*等待表中的第一个任务(优先级最高)*/
OS_PEND_DATA *TailPtr; /*等待表中的最后一个任务(优先级最低)*/
OS_OBJ_QTY NbrEntries; /*等待表中的表项数目,每一个表象指向一个正在等待内核对象的任务*/
};
任务挂起表实际上不直接指向任务的控制块,而是指向一个OS_PEND_DATA类型的数据结构,该类型的数据结构会在任务被放入任务挂起表时动态地分配到该任务的堆栈空间中。
struct os_pend_data {
OS_PEND_DATA *PrevPtr; /*等待该内核对象的更高或相同优先级的任务*/
OS_PEND_DATA *NextPtr; /*等待该内核对象的更低或相同优先级的任务*/
OS_TCB *TCBPtr; /*该内核对象的任务的控制块OS_TCB*/
OS_PEND_OBJ *PendObjPtr; /*指向任务正在等待的内核对象*/
OS_PEND_OBJ *RdyObjPtr; /*指向已经准备就绪的内核对象*/
void *RdyMsgPtr; /*指向通过OSQPost()函数发布的信息*/
OS_MSG_SIZE RdyMsgSize; /*通过OSQPost()函数发布信息的大小*/
CPU_TS RdyTS; /*时间戳,记录内核对象被释放(Post)的时刻*/
};
μC/OS-III仅仅允许任务同时等待多个信号量和(或)多个消息队列,不支持同时等待多个事件标志组或互斥信号量。
3 两个任务等待同一个信号量
struct os_sem {
OS_OBJ_TYPE Type; /*被定义为SEMA*/
CPU_CHAR *NamePtr; /*信号的名字(ASCII字符串)*/
OS_PEND_LIST PendList; /*该信号量的任务挂起表*/
OS_SEM *DbgPrevPtr;
OS_SEM *DbgNextPtr;
CPU_CHAR *DbgNamePtr;
OS_SEM_CTR Ctr; /*信号量的当前值*/
CPU_TS TS; /*时间戳*/
CPU_INT08U SemID; /*信号量唯一ID号*/
};
- OS_PEND_LIST结构体中pendlist->NbrEntries表明有多少个任务正在等待该信号量,pendlist->HeadPtr指向等待该信号量的最高优先级任务(即OS_PEND_DATA结构体),pendlist->TailPtr指向等待该信号量的最低优先级任务(即OS_PEND_DATA结构体)
- 每个OS_PEND_DATA结构体都指向其对应的任务控制块TCB,因此可以通过信号量的任务挂起表知道哪个任务在等待该信号量
- 每个任务控制块中的OS_TCB.PendDataTblPtr又指回到OS_PEND_DATA结构体(此处指向的可能是一个结构体数组,表征该任务正在同时等待多个内核对象,每一个表项表明一个等待的内核对象)
- OS_PEND_DATA结构体构成一个双向链表,系统可以很容易的添加或删除挂起表中的任意一个任务
4 任务同时等待多个内核对象
μC/OS-III仅仅允许任务同时等待多个信号量和(或)多个消息队列,不支持同时等待多个事件标志组或互斥信号量。
一个任务调用OSPendMulti()来等待多个内核对象,同时指定一个超时时间,该时间对任务等待的内核对象都有效,如果在指定的超时时限内,没有对象被发布,该任务将返回一个错误代码,表示等待超时。
OS_OBJ_QTY OSPendMulti (OS_PEND_DATA *p_pend_data_tbl, /*等待多个内核对象的OS_PEND_DATA结构体数组,结构体中.PendObjPtr表明任务等待的内核对象*/
OS_OBJ_QTY tbl_size, /*等待内核对象的数目,即OS_PEND_DATA结构体数组的大小*/
OS_TICK timeout, /*等待超时时间*/
OS_OPT opt, /*等待对象选项*/
OS_ERR *p_err) /*返回错误码*/
//例如:一个任务等待3个信号量,2个消息队列
OS_SEM MySem1;
OS_SEM MySem2;
OS_SEM MySem3;
OS_Q MyQ1;
OS_Q MyQ2;
void MyTask(void)
{
OS_ERR err;
OS_PEND_DATA my_pend_multi_tbl[5];
...
while(DEF_ON)
{
my_pend_multi_tbl[0].PendObjPtr = (OS_PEND_OBJ)&MySem1;
my_pend_multi_tbl[1].PendObjPtr = (OS_PEND_OBJ)&MySem2;
my_pend_multi_tbl[2].PendObjPtr = (OS_PEND_OBJ)&MySem3;
my_pend_multi_tbl[3].PendObjPtr = (OS_PEND_OBJ)&MyQ1;
my_pend_multi_tbl[4].PendObjPtr = (OS_PEND_OBJ)&MyQ2;
OSPendMulti((OS_PEND_DATA*) &my_pend_multi_tbl[0],
(OS_OBJ_QTY) 5,
(OS_TICK) 0,
(OS_OPT) OS_OPT_PEND_BLOCKING, /*阻塞等待*/
(OS_ERR*) &err);
/*Check err*/
...
}
}
当被调用时,OSPendMulti()函数首先验证OS_PEND_DATA表中指定的所有对象是否为OS_SEM或OS_Q,如果不是则返回错误码。之后函数遍历OS_PEND_DATA表中的所有对象以确定是否有任何一个对象已经被发布,如果有,OSPendMulti()就会根据情况填充表中的.RdyObjPtr、.RdyMsgPtr、RdyMsySize和.RdyTS成员。
4.1 一个任务同时等待多个信号量
- 一个指向OS_PEND_DATA表基地址的指针被放到同时等待两个信号量的任务的控制块中即OS_TCB.PendDataTblPtr
- OS_PEND_DATA表的表项数也被放在该任务的控制块中(即OS_TCB.PendDataTblEntries),表明该任务同时等待几个内核对象
- 第一个信号量指向OS_PEND_DATA表的第一个表项
- OS_PEND_DATA表的第一个表项数据OS_PEND_DATA[0].PendObjPtr指向等待的第一个信号量
- 由于第一个信号量只有一个任务在等待,所以OS_PEND_DATA[0]中的PrevPtr和NextPtr都指向NULL
- 第二个信号量的具体指向同第一个信号量
4.2 多个任务同时等待多个信号量
多个任务同时等待多个信号量时,OS_PEND_DATA表被串联起来,位于双向链表前面的OS_PEND_DATA结构体属于高优先级,后面的属于低优先级。当一个任务等待多个内核对象时,OS_PEND_DATA表中只有一个表项的RdyObjPtr为非空值,表明这个任务等待的哪个内核对象被发布。
例如,上面N哪个等待3个信号量,2个消息队列的任务,如果第一个消息队列被发布,则OS_PEND_DATA表中的内容如下图: