前段时间老师上课讲到了uC/OS中的信号量机制,所以我想要结合《μC/OS-III源码分析笔记》和中国大学MOOC-电子科技大学《嵌入式系统及应用》PPT写一篇笔记对这部分的内容进行总结。
Figure 1. 模型 \text{Figure 1. 模型} Figure 1. 模型
Figure 2. 同步或通信的基本方式 \text{Figure 2. 同步或通信的基本方式} Figure 2. 同步或通信的基本方式
信号量用于实现任务与任务之间、任务与中断处理程序之间的同步与 互斥。信号量一般分为三种:
临界区
,临界区代码通常用 于对共享资源的访问。互斥信号量可以实现对共享资源的串行访问
,保证只有成功 地获取互斥信号量的任务才能够释放它。临界区
。Figure 3. 互斥信号量状态图 \text{Figure 3. 互斥信号量状态图} Figure 3. 互斥信号量状态图
支持所有权、递归访 问、任务删除安全
和一些避免优先级反转、饥饿、死锁等
互斥所固 有问题的协议。同步的信号量不支持嵌套访问
,否则任务会被永久阻塞。任务保护
和解除任务保护
原语对。Figure 4. 各种互斥机制的比较 \text{Figure 4. 各种互斥机制的比较} Figure 4. 各种互斥机制的比较
二值信号量主要用于任务与任务之间、任务与中断服务程序之间的 同步。二值信号量的初始值为0,表示同步事件尚未产生。
Figure 5. 二值信号量状态图 \text{Figure 5. 二值信号量状态图} Figure 5. 二值信号量状态图
用二值信号量实现任务间双向同步时,申请者和释放者不是同一个任务,与互斥信号量不同。互斥信号量必须谁申请,谁释放。
Figure 6. 用二值信号量实现任务间双向同步 \text{Figure 6. 用二值信号量实现任务间双向同步} Figure 6. 用二值信号量实现任务间双向同步
Figure 7. 计数信号量状态图 \text{Figure 7. 计数信号量状态图} Figure 7. 计数信号量状态图
Figure 8. 有界缓冲问题1 \text{Figure 8. 有界缓冲问题1} Figure 8. 有界缓冲问题1
Figure 9. 有界缓冲问题 \text{Figure 9. 有界缓冲问题} Figure 9. 有界缓冲问题
OS_EVENT* OSSemCreate(INT16U cnt)
{
OS_EVENT* pevent;
pevent = OSEventFreeList; //从空闲事件控制块链中取得一个ECB
if (OSEventFreeList != (OS_EVENT*)0) {
OSEventFreeList = (OS_EVENT*) { OSEventFreeList->OSEventPtr; }
if (pevent != (OS_EVENT*)0) { //初始化ECB的各个域
pevent->OSEventType = OS_EVENT_TYPE_SEM; //事件类型为信号量
//信号量的初始计数值
pevent->OSEventCnt = cnt;
pevent->OSEventPtr = (void*)0;
OS_EventWaitListInit(pevent); //初始化等待任务列表
}
}
return (pevent); //调用者需检查返回值,如果为NULL则表示建立失败
}
说明:这是针对计数信号量和二值信号量的实现。 μC/OS-II中互斥 信号量的实现参见源码os_mutex.c文件。
Figure 10. 基本流程 \text{Figure 10. 基本流程} Figure 10. 基本流程
void OSSemPend(OS_EVENT* pevent, INT16U timeout, INT8U* err)
{
//信号量值大于0,成功获得信号量并返回
if (pevent->OSEventCnt > 0) {
pevent->OSEventCnt--;
*err = OS_NO_ERR;
return;
}
//设置任务状态为等待信号量
OSTCBCur->OSTCBStat |= OS_STAT_SEM;
//设置等待时限
OSTCBCur->OSTCBDly = timeout;
//将任务放置到信号量的等待列表中
OS_EventTaskWait(pevent);
//内核实施任务调度,系统切换到另一就绪任务执行
OS_Sched();
//判断任务恢复执行的原因,如果等待时限超时但仍然未获得信号量,则返回超时信息
if (OSTCBCur->OSTCBStat & OS_STAT_SEM) {
OSEventTO(pevent);
*err = OS_TIMEOUT;
return;
}
OSTCBCur->OSTCBEventPtr = (OS_EVENT*)0;
*err = OS_NO_ERR; //任务由于获得信号量而恢复执行,本调用成功返回
}
说明:这里同样是针对计数信号量和二值信号量的实现。
OSSemAccept
也可以实现获取(申请)信号量的功能。
INT16U OSSemAccept(OS_EVENT* pevent)
{
INT16U cnt;
cnt = pevent->OSEventCnt;
if (cnt > 0) {
pevent->OSEventCnt--;
}
return (cnt);
}
Figure 12. 释放信号量 \text{Figure 12. 释放信号量} Figure 12. 释放信号量
INT8U OSSemPost(OS_EVENT* pevent)
{
if (pevent->OSEventGrp != 0x00) { //如果有任务在等待该信号量
OS_EventTaskRdy(pevent, (void*)0, OS_STAT_SEM); //使等待列表中优先级最高的任务就绪
OS_Sched(); //内核实施任务调度
return (OS_NO_ERR); //成功返回
}
if (pevent->OSEventCnt < 65535) { //如果没有任务等待该信号量,并且信号量的值未溢出
pevent->OSEventCnt++; //信号量的值加1
return (OS_NO_ERR); //成功返回
}
return (OS_SEM_OVF); //信号量溢出
}
Figure 13. 删除信号量 \text{Figure 13. 删除信号量} Figure 13. 删除信号量
OS_EVENT* OSSemDel(OS_EVENT* pevent, INT8U opt, INT8U* err)
{
BOOLEAN tasks_waiting;
if(pevent->OSEventGrp!=0x00{//根据是否有任务在等待信号量设置等待标志
tasks_waiting = TRUE;
}else{
tasks_waiting = FALSE;
}
switch(opt){
case OS_DEL_NO_PEND: //如果有任务等待信号量则不删除信号量
if(task_waiting==FALSE{ //没有任务等待,释放ECB回空闲链
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventPtr = OSEventFreeList;
OSEventFreeList = pevent; //调整空闲ECB链头指针
*err = OS_NO_ERR;
return ((OS_EVENT)0);
}else{
*err = OS_ERR_TASK_WAITING; //有任务等待,删除信号量失败
return (pevent);
}
case OS_DEL_ALWAYS: //无论有无任务等待都删除信号量
//将等待列表中的每个任务都设置成就绪
while (pevent->OSEventGrp != 0x00) {
OS_EventTaskRdy(pevent, (void*)0, OS_STAT_SEM);
}
pevent->OSEventType = OS_EVENT_TYPE_UNUSED;
pevent->OSEventFreeList;
OSEventFreeList = pevent; //释放该信号量的ECB回空闲控制块链
if (tasks_waiting == TRUE) {
OS_Sched();
} //如果之前有任务等待信号量,内核实施任务调度
*err = OS_NO_ERR;
return ((OS_EVENT*)0);
default:
*err = OS_ERR_INVALID_OPT;
return (pevent);
}
}
联系邮箱:[email protected]
CSDN:https://me.csdn.net/qq_41729780
知乎:https://zhuanlan.zhihu.com/c_1225417532351741952
公众号:复杂网络与机器学习
欢迎关注/转载,有问题欢迎通过邮箱交流。