1.25k likes | 1.38k Views
嵌入式系统及应用. 第六章 同步、互斥与通信. 主要内容. 概述 信号量 邮箱和消息队列 事件 异步信号* 管道*. 概述. 多任务系统中任务之间的关系 相互独立 仅竞争 CPU 资源 竞争除 CPU 外的其他资源(互斥) 同步 协调彼此运行的步调,保证协同运行的各个任务具有正确的执行次序 通信 彼此间传递数据或信息,以协同完成某项工作. 概述. 任务能以以下方式与中断处理程序或其他任务进行同步或通信: 单向同步或通信 :一个任务与另一个任务或一个 ISR 同步或通信。
E N D
主要内容 • 概述 • 信号量 • 邮箱和消息队列 • 事件 • 异步信号* • 管道*
概述 • 多任务系统中任务之间的关系 • 相互独立 仅竞争CPU资源 • 竞争除CPU外的其他资源(互斥) • 同步 协调彼此运行的步调,保证协同运行的各个任务具有正确的执行次序 • 通信 彼此间传递数据或信息,以协同完成某项工作
概述 • 任务能以以下方式与中断处理程序或其他任务进行同步或通信: • 单向同步或通信:一个任务与另一个任务或一个ISR同步或通信。 • 双向同步或通信:两个任务相互同步或通信。双向同步不能在任务与ISR之间进行,因为ISR不能等待。
POST PEND 任务与任务之间的同步(单向) Task x Task y POST PEND ISR x Task y 任务与ISR之间的同步(单向) POST PEND Task x Task y 任务与任务之间的同步(双向) PEND POST
概述 • 在嵌入式多任务系统中,任务间的耦合程度是不一样的: • 耦合程度较高:任务之间需要进行大量的通信,相应的系统开销较大; • 耦合程度较低:任务之间不存在通信需求,其间的同步关系很弱甚至不需要同步或互斥,系统开销较小。 • 研究任务间耦合程度的高低对于合理地设计应用系统、划分任务有很重要的作用。
概述 • 在单处理器平台上,嵌入式操作系统内核提供的同步、互斥与通信机制主要包括: • 信号量(semaphore),用于互斥与同步 • 事件(组)(event group),用于同步 • 异步信号(asynchronous signal),用于同步 • 邮箱(mailbox)、消息队列(message queue),用于消息通信 • 管道(pipe),提供非结构化数据交换和实现同步
概述 • 以下一些机制也可用于同步与通信(在单处理器或多处理器系统中): • 全局变量 • 共享内存 • Sockets • 远程过程调用(Remote Procedure Call)
第一节信号量 信号量的种类及用途 互斥信号量 二值信号量 计数信号量 信号量机制的主要数据结构 典型的信号量操作
信号量的种类及用途 • 信号量用于实现任务与任务之间、任务与中断处理程序之间的同步与互斥。 • 信号量一般分为三种: 用于解决互斥问题。它比较特殊,可能会引起优先级反转问题。 互斥信号量 二值信号量 用于解决同步问题 计数信号量 用于解决资源计数问题 将信号量进行种类细分,可以根据其用途,在具体 实现时做专门处理,提高执行效率和可靠性。
Task1 共享资源 Task2 互斥信号量 • 用互斥信号量保护的代码区称作“临界区”,临界区代码通常用于对共享资源的访问。 • 互斥信号量的值被初始化成1,表明目前没有任务进入“临界区”,但最多只有一个任务可以进入“临界区”。 • 第一个试图进入“临界区”的任务将成功获得互斥信号量,而随后试图进入用同一信号量保护的临界区的所有其他任务就必须等待。 • 当任务离开“临界区”时,它将释放信号量并允许正在等待该信号量的任务进入“临界区”。
互斥信号量 • 共享资源可能是一段存储器空间、一个数据结构或I/O设备,也可能是被两个或多个并发任务共享的任何内容。 • 使用互斥信号量可以实现对共享资源的串行访问,保证只有成功地获取互斥信号量的任务才能够释放它。 • 互斥信号量是一种特殊的二值信号量,一般它支持所有权、递归访问、任务删除安全和一些避免优先级反转、饥饿、死锁等互斥所固有问题的协议。
互斥信号量状态图 申请(递归)并获得 锁定数加1 申请并获得 值为0 开启 锁定 初始化 值为1 释放 值为1 释放(递归) 锁定数减1 互斥信号量状态图
互斥信号量 • 所有权:当一个任务通过获取互斥信号量而将其锁定时,得到该互斥信号量的所有权。相反,当一个任务释放信号量时,失去对其的所有权。 • 当一个任务拥有互斥信号量时,其他的任务不能再锁定或释放它,即任务要释放互斥信号量,必须事前先获取该信号量。
共享资源 互斥信号量 • 嵌套(递归)资源访问 • 如果Task1调用RoutineA,而RoutineA又调用RoutineB,并且三者访问相同的共享资源,就发生了递归共享资源的访问同步问题。 Task1 一个递归的互斥信号量允许嵌套锁定互斥信号量,而不引起死锁。 RoutineA RoutineB
互斥信号量 • 嵌套(递归)资源访问 • 每个获取信号量的调用必须与释放信号量的调用相匹配。当最外层的获取信号量的调用与释放信号量的调用匹配时,该信号量才允许被其它任务访问。 • 用于同步的信号量不支持嵌套访问,任务如果对同步信号量使用上述操作是错误的,任务会被永久阻塞,并且阻塞条件永远不会解除。
互斥信号量 • 删除安全: • 在一个受信号量保护的临界区,经常需要保护在临界区执行的任务不会被意外地删除。 • 删除一个在临界区执行的任务可能引起意想不到的后果,造成保护资源的信号量不可用,可能导致资源处于破坏状态,也就导致了其它所有要访问该资源的任务无法得到满足。
互斥信号量 • 删除安全: • 为避免任务在临界区执行时不被意外删除: • 提供“任务保护”和“解除任务保护”原语对 • 同时,为互斥信号量提供“删除安全”选项。在创建信号量的时候使用这个选项,当应用每次获取信号量时隐含地使能“任务保护”功能,当每次释放信号量时隐含地使用“解除任务保护”功能。
Task1 Task2 二值信号量 初值为0 二值信号量 • 二值信号量主要用于任务与任务之间、任务与中断服务程序之间的同步 • 用于同步的二值信号量初始值为0,表示同步事件尚未产生; • 任务申请信号量以等待该同步事件的发生; • 另一个任务或ISR到达同步点时,释放信号量(将其值设置为1)表示同步事件已发生,以唤醒等待的任务。
二值信号量 申请并获得 (值为0) 初始化 值为0 可获得 不可获得 释放 (值为1) 二值信号量状态图
Task1() { …… 执行一些操作; 将信号量sem1置1; 申请信号量sem2; …… …… } Task2申请信号量sem1失败,系统切换到Task1 Task2() { …… 申请信号量sem1; 执行一些操作; 将信号量sem2置1; …… …… } sem1被置1后,Task2得到sem1并抢占Task1 Task2运行到某处时因某种原因被阻塞,系统切换到Task1 • 用二值信号量实现两个任务之间的双向同步 • Task2优先级高于Task1 • sem1和sem2的初始值均为0
Task1 共享资源实例1 …… Task2 共享资源实例n …… Task m 计数信号量 • 计数信号量用于控制系统中共享资源的多个实例的使用,允许多个任务同时访问同一种资源的多个实例 • 计数信号量被初始化为n(非负整数),n为该种共享资源的数目。
计数信号量 申请并获得 值减1 申请并获得 值为0 可获得 不可获得 初始化 值大于0 释放 值为1 释放 值加1 计数信号量状态图
信号量控制块 SCB1 信号量名字或ID 任务等待列表 SCB2 …… …… Task1 Task2 …… count 信号量机制的主要数据结构
信号量机制的主要数据结构 • 信号量控制块:管理所有创建的信号量,内核在系统运行时动态分配和回收信号量控制块 • 互斥和二值信号量控制块结构: Binary_Semaphore_Control_Block wait_queue 任务等待队列 attributes 信号量属性 lock_nesting_behavior试图嵌套获得时的规则 wait_discipline 任务等待信号量的方式 priority_ceiling 优先级天花板值 lock 是否被占有 holder 拥有者 nest_count 嵌套层数
信号量机制的主要数据结构 • 计数信号量控制结构Counting_Semaphore_Control_Block wait_queue 任务等待队列 attributes 计数信号量属性 maximum_count 最大计数值 wait_discipline 任务等待信号量的方式 count 当前计数值
信号量内部实现机制实例说明-µC/OS-II • 事件控制块ECB-同步与通信机制的基本数据结构 typedef struct{ INT8U OSEventType;//事件类型 INT8U OSEventGrp;//等待任务所在的组 INT16U OSEventCnt;//计数器(信号量) void *OSEventPtr;//指向消息或消息队列的指针 INT8U OSEventTbl[OS_EVENT_TBL_SIZE];//等待任务列表 }OS_EVENT;
信号量内部实现机制实例说明-µC/OS-II • 当一个事件发生后,等待事件列表中优先级最高的任务(即在.OSEventTbl[]&OSEventGrp中所有被置1的位中优先级数值最小的任务)得到该事件。
信号量内部实现机制实例说明-µC/OS-II • 当.OSEventTbl[n]中的任何一位为1时,OSEventGrp中的第n位为1。 与任务就绪列表类似!
信号量内部实现机制实例说明-µC/OS-II • 将一个任务插入到等待事件的任务列表中: pevent->OSEventGrp |= OSMapTbl[prio >> 3]; pevent->OSEventTbl[prio >> 3] |= OSMapTbl[prio & 0x07]; 与将一个任务插入到就绪列表中的操作类似! Index Bit mask (Binary) 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 1 0 2 0 0 0 0 0 1 0 0 3 0 0 0 0 1 0 0 0 4 0 0 0 1 0 0 0 0 5 0 0 1 0 0 0 0 0 6 0 1 0 0 0 0 0 0 7 1 0 0 0 0 0 0 0
信号量内部实现机制实例说明-µC/OS-II • 从等待事件的任务列表中使任务脱离等待状态 if ((pevent->OSEventTbl[prio >> 3] &= ~OSMapTbl[prio & 0x07]) == 0) { pevent->OSEventGrp &= ~OSMapTbl[prio >> 3]; } 与将任务从就绪列表中清除的操作类似!
信号量内部实现机制实例说明-µC/OS-II • 在等待事件的任务列表中查找优先级最高的任务 y = OSUnMapTbl[pevent->OSEventGrp]; x = OSUnMapTbl[pevent->OSEventTbl[y]]; prio = (y << 3) + x; 与查找优先级最高的就绪任务的操作类似!
信号量内部实现机制实例说明-µC/OS-II • 空闲事件控制块链表
典型的信号量操作 • 创建信号量 • 获取(申请)信号量 • 释放信号量 • 删除信号量 • 清除信号量的任务等待列表 • 获取有关信号量的各种信息
创建信号量 • 功能:根据应用传递的参数创建一个信号量 • 参数:信号量的名字、属性和初始值等。 • 内核动作: • 从空闲信号量控制块链中分配一个信号量控制块,并初始化信号量属性。 • 创建成功时,为其分配唯一的ID号返回给应用。 • 如果已创建信号量数量已达到用户配置的最大数量,就返回错误。
创建信号量 信号量的属性包括: • 类型 • 任务等待信号量的方式(即排列的顺序) • 与任务删除安全、递归访问以及解决优先级反转的策略相关的参数(只针对互斥信号量)。
创建信号量 互斥信号量(MUTEX_SEMAPHORE) 信号量的类型 计数信号量(COUNTING_SEMAPHORE) 信号量的属性 二值信号量(BINARY_SEMAPHORE) 先进先出(FIFO)顺序 任务等待信号量的方式 优先级(PRIORITY)顺序 优先级继承算法(INHERIT_PRIORITY) 优先级反转问题的解决方法(只适用于互斥信号量) 优先级天花板算法(PRIORITY_CEILING) ,需给出所有可能获得此信号量的任务中优先级最高的任务的优先级。
创建一个信号量OSSemCreate() 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则表示建立失败 }
获取(申请)信号量 • 功能:试图获得应用指定的信号量。 if 信号量的值大于0 then 将信号量的值减1 else 根据接收信号量的选项,将任务放到等待队列中,或是直接返回
获取(申请)信号量 • 当所申请的信号量不能被立即获得时,可以有以下几种选择: • 永远等待 • 不等待,立即返回,并返回一个错误状态码 • 指定等待时限(可有效避免死锁) 注意: • 不允许在ISR中选择等待 • 当任务选择等待时,将被按FIFO或优先级顺序放置在等待队列中
获取(申请)信号量 • 如果任务等待一个使用优先级继承算法的互斥信号量,且它的优先级高于当前正占有此信号量的任务的优先级,那么占有信号量的任务将继承这个被阻塞的任务的优先级。 • 如果任务成功地获得一个采用优先级天花板算法的互斥信号量,它的优先级又低于优先级天花板,那么它的优先级将被抬升至天花板。
获取(等待)一个信号量OSSemPend() void OSSemPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) { if (pevent->OSEventCnt > 0) { //信号量值大于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); } 注意:即使不能成功获得信号量(返回值为0),调用者也不会被阻塞。此函数可以在中断处理程序中使用。
释放信号量 • 功能:释放一个应用指定的信号量。 if 没有任务等待这个信号量 then 信号量的值加1 else 将信号量分配给一个等待任务(将相应的任务移出等待队列,使其就绪) • 如果使用了优先级继承或优先级天花板算法,那么执行该功能(系统调用)的任务的优先级将恢复到原来的高度。
释放一个信号量OSSemPost() 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);//信号量溢出 }
删除信号量 • 功能:从系统中删除应用指定的一个信号量 • 内核动作:将信号量控制块返还给系统 • 删除信号量的不一定是创建信号量的任务 • 如果有任务正在等待获得该信号量,执行此功能将使所有等待这个信号量的任务回到就绪队列中,且返回一个状态码指示该信号量已被删除
删除信号量 • 企图获取已删除的信号量将返回一个错误; • 在互斥信号量正被使用时(已经被某任务获取),不能删除它。因为该信号量正在保护一个共享资源或临界代码段,该动作可能造成数据崩溃或其他严重问题。
删除一个信号量OSSemDel() 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); }
删除一个信号量OSSemDel() case OS_DEL_ALWAYS://无论有无任务等待都删除信号量 //将等待列表中的每个任务都设置成就绪 while(pevent->OSEventGrp!=0x00){ OS_EventTaskRdy(pevent,(void *)0, OS_STAT_SEM);} //释放该信号量的ECB回空闲控制块链 pevent->OSEventType=OS_EVENT_TYPE_UNUSED; pevent->OSEventFreeList; OSEventFreeList=pevent; //如果之前有任务等待信号量,内核实施任务调度 if(tasks_waiting==TRUE){OS_Sched();} *err=OS_NO_ERR; return((OS_EVENT *)0); default: *err=OS_ERR_INVALID_OPT; return(pevent); } }