1 事件与事件控制块
事件:μC/OS-II使用信号量,消息邮箱,消息队列这些中间环节来实现任务之间的通信,它们统称“事件”
事件控制块:μC/OS-II使用数据结构OS_EVENT来描述信号量,消息邮箱,消息队列这些事件。
事件控制块的数据结构如下图所示。
typedef struct os_event
{
INT8U OSEventType; //事件类型
void *OSEventPtr; //消息邮箱或消息队列的指针
INT16U OSEventCnt; //信号量计数器
OS_PRIO OSEventGrp; //等待事件的任务组
OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; //等待任务表
} OS_EVENT;
2 信号量
2.1 工作原理
当有任务申请信号量时:
- 如果OSEventCnt > 0,则OSEventCnt - 1 ,继续运行程序
- 如果OSEventCnt = 0,则将任务列入等待任务表OSEventTbl [ ] ,使任务处于等待状态(具有一定的等待时间)
- 如果其他任务发送信号量,则系统引发一次任务调度,在等待任务表中找到优先级最高的等待任务,执行它
- 如果等待时间结束,则结束任务等待状态,进入就绪状态
初值为10的信号量的数据结构如下图所示。
2.2 相关操作
- 创建信号量
- 请求信号量(信号量无效时等待,信号量无效时继续运行)
- 发送信号量
- 删除信号量
- 查询信号量
- 设置信号量
μC/OS-II中与信号量相关的函数定义在os_sem.c文件中,函数声明在ucos_ii.h文件中,以下列出函数声明以供查阅。
OS_EVENT *OSSemCreate (INT16U cnt);
void OSSemPend (OS_EVENT *pevent,
INT32U timeout, //赋值0则时间无限长
INT8U *perr);
INT16U OSSemAccept (OS_EVENT *pevent);
INT8U OSSemPost (OS_EVENT *pevent);
OS_EVENT *OSSemDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr);
INT8U OSSemQuery (OS_EVENT *pevent,
OS_SEM_DATA *p_sem_data);
void OSSemSet (OS_EVENT *pevent,
INT16U cnt,
INT8U *perr);
3 互斥信号量
3.1 相关概念
互斥信号量:互斥信号量是一个二值信号量,它除了具有普通信号量的机制外,主要用来解决优先级反转问题。
优先级反转:当任务以独占方式使用共享资源时,会出现低优先级任务先于高优先级任务的现象。例如:任务A,B,C的优先级为 A > B > C ,任务A,C使用同一个共享资源S。假设CPU在执行任务C在使用共享资源S,此时任务A剥夺CPU使用权,但是,由于在任务C中没有发送信号量,所以在任务A中无法访问共享资源S而进入等待状态,那么CPU继续执行任务C。这时候,如果任务B剥夺了CPU使用权,就造成了一个现象:任务A的优先级 > 任务B的优先级,但是任务A却需要等待任务B执行结束,任务C再执行过程且发送信号量后,任务A才能执行,似乎任务B的优先级 > 任务A的优先级。
μC/OS-II中解决优先级反转的方法:使获得信号量的任务(任务C)的优先级别在使用共享资源S期间暂时提升到一个更高的优先级,使该任务不被比任务A优先级低且比任务C优先级高的任务B(B1,B2,B3多个任务)打断。而这个“更高的优先级”不能是其他已有任务的优先级(因为μC/OS-II不允许多个任务具有有相同的优先级),并且优先级要比任务B(B1,B2,B3多个任务)的优先级高,比任务A的优先级低。
3.2 工作原理
互斥信号量的成员OSEventCnt被分为高8位和低8位,高8位用来存放解决优先级反转问题而要提升至的优先级,低8位用来存放信号值(0xFF时信号有效,否则信号无效)。至于μC/OS-II具体是如何解决优先级反转问题的,可以查看OSMutexPend()和OSMutexPost()函数的源码。
互斥信号量的数据结构如下图所示。
3.3 相关操作
- 创建互斥信号量
- 请求互斥信号量(信号量无效时等待,信号量无效时继续运行)
- 发送互斥信号量
- 删除互斥信号量
- 查询互斥信号量
μC/OS-II中与互斥信号量相关的函数定义在os_mutex.c文件中,函数声明在ucos_ii.h文件中,以下列出函数声明以供查阅。
OS_EVENT *OSMutexCreate (INT8U prio,
INT8U *perr);
void OSMutexPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr);
BOOLEAN OSMutexAccept (OS_EVENT *pevent,
INT8U *perr);
INT8U OSMutexPost (OS_EVENT *pevent);
OS_EVENT *OSMutexDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr);
INT8U OSMutexQuery (OS_EVENT *pevent,
OS_MUTEX_DATA *p_mutex_data);
4 消息邮箱
4.1 工作原理
任务与任务之间常常需要通过传递数据的方式进行通信,为了达到这个目的,可以在内存中创建一个存储空间作为数据缓冲区,然后在任务与任务之间传递数据缓冲区的指针。事件控制块的成员OSEventPtr指向这个缓冲区,这种用来传递数据缓冲区指针的数据结构就是消息邮箱。
消息邮箱的数据结构如下图所示。
4.2 相关操作
- 创建消息邮箱
- 请求消息邮箱(消息邮箱无效时等待,消息邮箱无效时继续运行)
- 发送消息邮箱
- 删除消息邮箱
- 查询消息邮箱
μC/OS-II中与消息邮箱相关的函数定义在os_mbox.c文件中,函数声明在ucos_ii.h文件中,以下列出函数声明以供查阅。
OS_EVENT *OSMboxCreate (void *pmsg);
void *OSMboxPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr);
void *OSMboxAccept (OS_EVENT *pevent);
INT8U OSMboxPost (OS_EVENT *pevent,
void *pmsg);
OS_EVENT *OSMboxDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr);
INT8U OSMboxQuery (OS_EVENT *pevent,
OS_MBOX_DATA *p_mbox_data);
5 消息队列
5.1 工作原理
消息邮箱只能用来传递一个消息,而可以用来传递一组消息的数据结构叫做消息队列。消息队列由三部分组成:事件控制块,消息队列控制块,消息。事件控制块的成员OSEventPtr指向消息队列控制块(OS_Q),消息队列控制块管理着一个数组( MsgTbl [ ] ),该数组中的元素都是指向消息的指针。
消息队列的数据结构如下图所示。
5.2 相关操作
- 创建消息队列
- 请求消息队列(消息队列无效时等待,消息队列无效时继续运行)
- 发送消息队列(FIFO,LIFO,广播发送)
- 删除消息队列
- 查询消息队列
- 清空消息队列
μC/OS-II中与消息队列相关的函数定义在os_q.c文件中,函数声明在ucos_ii.h文件中,以下列出函数声明以供查阅。
OS_EVENT *OSQCreate (void **start,
INT16U size);
void *OSQPend (OS_EVENT *pevent,
INT32U timeout,
INT8U *perr);
void *OSQAccept (OS_EVENT *pevent,
INT8U *perr);
INT8U OSQPost (OS_EVENT *pevent,
void *pmsg);
INT8U OSQPostFront (OS_EVENT *pevent,
void *pmsg);
INT8U OSQPostOpt (OS_EVENT *pevent,
void *pmsg,
INT8U opt);
OS_EVENT *OSQDel (OS_EVENT *pevent,
INT8U opt,
INT8U *perr);
INT8U OSQQuery (OS_EVENT *pevent,
OS_Q_DATA *p_q_data);
INT8U OSQFlush (OS_EVENT *pevent);
6 信号量集
6.1 工作原理
在实际应用中,任务常常需要根据多个信号量组成作用的结果来决定任务的运行方式,为此,μC/OS-II提供了信号量集。信号量集可以分为两个部分:标志组和等待任务链表。
信号量集的数据结构如下图所示。
//信号量集的标志组
typedef struct os_flag_grp
{
INT8U OSFlagType; //识别信号量集的标志
void *OSFlagWaitList; //指向等待任务链表的指针
OS_FLAGS OSFlagFlags; //输入信号量值列表
} OS_FLAG_GRP;
//信号量集的等待任务节点,组成等待任务链表
typedef struct os_flag_node
{
void *OSFlagNodeNext; //指向下一个节点的指针
void *OSFlagNodePrev; //指向上一个节点的指针
void *OSFlagNodeTCB; //指向对应任务控制块的指针
void *OSFlagNodeFlagGrp; //反向指向信号量集的指针
OS_FLAGS OSFlagNodeFlags; //等待的信号量
INT8U OSFlagNodeWaitType; //逻辑关系
//OS_FLAG_WAIT_AND
//OS_FLAG_WAIT_ALL
//OS_FLAG_WAIT_OR
//OS_FLAG_WAIT_ANY
} OS_FLAG_NODE;
6.2 相关操作
- 创建信号量集
- 请求信号量集(信号量集无效时等待,信号量集无效时继续运行)
- 发送信号量集
- 查询信号量集
- 删除信号量集
μC/OS-II中与消息队列相关的函数定义在os_flag.c文件中,函数声明在ucos_ii.h文件中,以下列出函数声明以供查阅。
OS_FLAG_GRP *OSFlagCreate (OS_FLAGS flags,
INT8U *perr);
OS_FLAGS OSFlagPend (OS_FLAG_GRP *pgrp,
OS_FLAGS flags,
INT8U wait_type,
INT32U timeout,
INT8U *perr);
OS_FLAGS OSFlagAccept (OS_FLAG_GRP *pgrp,
OS_FLAGS flags,
INT8U wait_type,
INT8U *perr);
OS_FLAGS OSFlagPost (OS_FLAG_GRP *pgrp,
OS_FLAGS flags,
INT8U opt,
INT8U *perr);
//该函数可以根据信号量集的不同状态实现不同的功能
OS_FLAGS OSFlagQuery (OS_FLAG_GRP *pgrp,
INT8U *perr);
OS_FLAG_GRP *OSFlagDel (OS_FLAG_GRP *pgrp,
INT8U opt,
INT8U *perr);
7 总结
本文主要介绍了μC/OS-II中任务的同步与通信,根据形式的不同,可以分为信号量,互斥信号量,消息邮箱,消息队列,信号量集。在任务之间需要通信的时候,根据它们的特点来选择。
- 信号量实现了任务之间对共享资源的访问
- 互斥信号量是二值的信号量,解决优先级反转问题
- 消息邮箱在任务之间传递一个消息指针,实现一条数据传递的功能
- 消息队列在任务之间传递一组消息指针,实现多条数据传递的功能
- 信号量集是信号量的集合,对多个信号量进行逻辑组合,实现了一个任务与多个任务的同步的功能