同步互斥与通信
概述
多任务系统中任务之间/任务与ISR(中断服务例程)的关系
- 相互独立:仅竞争CPU资源
- 竞争除CPU外的其他资源(互斥)
- 同步:协调彼此运行的步调,保证协同运行的各个任务具有正确的执行次序
- 通信:彼此间传递数据或信息,以协同完成某项工作
单向同步/通信:一个任务与另一个任务或一个ISR同步或通信。
双向同步或通信:两个任务相互同步或通信。双向同步不能在任务与ISR之间进行,因为ISR不能等待。
操作系统内核提供的同步互斥通信机制如下:
- 信号量(semaphore),用于互斥与同步
- 事件(组)(event group),用于同步
- 异步信号(asynchronous signal),用于同步
- 邮箱(mailbox)、消息队列(message queue),用于消息通信管道(pipe),提供非结构化数据交换和实现同步
其他同步与通信的机制
- 全局变量
- 共享内存
- Socket
- RPC
信号量
信号量用于实现任务与任务之间、任务与中断处理程序之间的同步与互斥。
分类:
- 互斥信号量:用于解决互斥问题。它比较特殊,可能会引起优先级反转问题。
- 二值信号量:用于解决同步问题
- 计数信号量:用于解决资源计数问题
互斥信号量
临界区: 用互斥信号量保护的代码区,临界区代码通常用于对共享资源(一段存储器空间、一个数据结构、IO设备等)的访问
- 互斥信号量的初始值为1,表示没有任务进入且最多有一个任务进入
- 第一个进入临界区的任务获得互斥信号,之后试图进入的其他任务必须等待
- 离开临界区后将释放信号量
互斥信号量是一种特殊的二值信号量,一般它支持所有权、递归访问、任务删除安全和一些避免优先级反转、饥饿、死锁等互斥所固有问题的协议。
所有权
当一个任务通过获取互斥信号量而将其锁定时,得到该互斥信号量的所有权。相反,当一个任务释放信号量时,失去对其的所有权。
递归资源访问
如果Task1调用RoutineA,而RoutineA又调用RoutineB,并且三者访问相同的共享资源,就发生了递归共享资源的访问同步问题。一个递归的互斥信号量允许嵌套锁定互斥信号量,而不引起死锁。
每个获取信号量的调用必须与释放信号量的调用相匹配。当最外层的获取信号量的调用与释放信号量的调用匹配时,该信号量才允许被其它任务访问。
用于同步的信号量不支持嵌套访问,任务如果对同步信号量使用上述操作是错误的,任务会被永久阻塞,并且阻塞条件永远不会解除。
删除安全
在一个受信号量保护的临界区,经常需要保护在临界区执行的任务不会被意外地删除。
删除一个在临界区执行的任务可能引起意想不到的后果,造成保护资源的信号量不可用,可能导致资源处于破坏状态,也就导致了其它所有要访问该资源的任务无法得到满足。
为了避免任务在临界区执行时不被意外删除提供了“任务保护”和“解除任务保护”原语对。同时互斥信号量可以设置为“删除安全”,任务获取信号量时将隐含的采用任务保护,释放信号量时将隐含地使用解除任务保护功能。
二值信号量
二值信号量主要用于任务与任务之间、任务与中断服务程序之间的同步
- 用于同步的二值信号量初始为0,表示同步时间尚未产生
- 任务申请信号量来等待该同步事件发生,申请不到则等待
- 另一个任务到达同步点时,释放信号量(将值置为1)表示同步事件已经发生来唤醒等待的任务。
比如,Task2优先级高于Task1,Task申请信号量sem1失败,切换到task1,task1执行一些操作后将信号量设置为1,task2得到sem1并抢占task1。
计数信号量
计数信号量用于控制系统中共享资源的多个实例的使用,允许多个任务同时访问同一种资源的多个实例。
计数信号量被初始化为n(非负整数),n为该种共享资源的数目。
实例:有界缓冲问题
两个进程共享一个公共的固定大小的缓冲区。其中的一个,生产者,将信息放入缓冲区;另一个,消费者,从缓冲区中取出信息。
- 计数信号量full:已被填充的数据项数目,取值范围0-n,初始值为0
- 计数信号量empty:空闲数据项数目,取值范围为0-n,初始值为n
- 互斥信号量mutex:控制生产者任务和消费者任务对有界缓冲的访问,初始值为1。
信号量机制的主要数据结构
- 信号量控制块(SCB):管理所有创建的信号量,内核在系统运行时动态分配和回收信号量控制块
- 信号量名称/ID
- 任务等待列表
互斥和二值信号量控制块结构:
Binary_Semaphore_Control_Block: |
计数信号量控制结构:
Counting_Semaphore_Control_Block: |
信号量操作
- 创建信号量
- 获取(申请)信号量
- 释放信号量
- 删除信号量
- 清除信号量的任务等待列表
- 获取有关信号量的各种信息
创建信号量
功能:根据应用传递的参数创建一个信号量
参数:信号量的名字、属性和初始值等。
内核动作:
从空闲信号量控制块链中分配一个信号量控制块,并初始化信号量属性。
创建成功时,为其分配唯一的ID号返回给应用。
如果已创建信号量数量已达到用户配置的最大数量,就返回错误。
属性:
类型(互斥,二值,计数)
任务等待信号量的方式(即排列的顺序)(FIFO,PRIORITY)
与任务删除安全、递归访问以及解决优先级反转的策略相关的参数(只针对互斥信号量)。 (优先级继承、优先级天花板)
获取(申请)信号量
if 信号量的值大于0 |
如申请的信号量不能立即获得可有几种选择:
- 永远等待
- 不等待,返回错误状态码
- 制定等待时限(可有效避免死锁)
注意:ISR中不允许选择等待
如果任务等待一个使用优先级继承算法的互斥信号量,且它的优先级高于当前正占有此信号量的任务的优先级,那么占有信号量的任务将继承这个被阻塞的任务的优先级。
如果任务成功地获得一个采用优先级天花板算法的互斥信号量,它的优先级又低于优先级天花板,那么它的优先级将被抬升至天花板。
释放信号量
if 没有任务等待这个信号量 |
如果使用了优先级继承或优先级天花板算法,那么执行该功能(系统调用)的任务的优先级将恢复到原来的高度。
删除信号量
- 功能:从系统中删除应用指定的一个信号量
- 内核动作:将信号量控制块返还给系统
- 删除信号量的不一定是创建信号量的任务。如果有任务正在等待获得该信号量,执行此功能将使所有等待这个信号量的任务回到就绪队列中,且返回一个状态码指示该信号量已被删除
- 企图获取已删除的信号量将返回一个错误。
- 在互斥信号量正被使用时(已经被某任务获取),不能删除它。因为该信号量正在保护一个共享资源或临界代码段,该动作可能造成数据崩溃或其他严重问题。
清除信号量的任务等待列表
为了清除等待一个信号量的所有任务,某些内核支持Flush操作,以便释放信号量等待任务列表中的所有任务。当多个任务的执行必须在某些点相遇时,需要这样的机制。
获取有关信号量的各种信息
系统可以通过两种方式获得信号量ID:
- 在创建信号量的系统调用时,id可以被返回
- 可以执行获取信号量ID的系统调用
可以使用ID来访问信号量
用用可以获取活动信号量的列表、每个信号量的细节信息
通信
任务间通信的方式
- 直接通信:在通信过程中双方必须明确地知道(命名)彼此
- 间接通信:通信双方不需要指出消息的来源或去向,而通过中间机制来通信。(如消息队列)
消息:内存空间中一段长度可变的缓冲区,其长度和内容均可以由用户定义,其内容可以是实际的数据、数据块的指针或空。
- 从操作系统观点看,消息没有定义的格式,所有的消息都是字节流,没有特定的含义。
- 从应用观点看,根据应用定义的消息格式,消息被解释成特定的含义。
- 应用可以只把消息当成一个标志,这时消息机制用于实现同步
一些操作系统内核把消息进一步分为:
- 邮箱(仅能存放单条消息,它提供了一种低开销的机制来传送信息。每个邮箱可以保存一条大小为若干个字节的消息。)
- 消息队列(消息队列可存放若干消息,提供了一种任务间缓冲通信的方法。)
消息机制可支持定长与可变长度两种模式的消息,可变长度的消息队列需要对队列中的每一条消息增加额外的存储开销。
消息队列机制
消息队列机制的主要数据结构:
- 消息队列控制块:管理所有创建的消息队列,系统运行时动态分配和回收消息队列控制块
- 消息队列缓冲区:存放者发送到该队列的消息,接收者从缓冲区中取出消息。
消息的发送或接收有两种方法(影响消息缓冲区结构):
- 将数据从发送任务的空间完全拷贝到接收任务的空间中(效率较低,执行时间与消息大小有关)
- 只传递指向数据存储空间的指针(提高系统性能)
逻辑上消息队列需要被组织为环形缓冲区被操作系统维护
消息队列操作
- 创建消息队列(FIFO或PRIORITY)
- 发送普通消息(满了的话操作:1. 挂起,2. 丢弃)
- 发送紧急消息(发送紧急消息将消息放在队列头。)
- 发送广播消息 :在此之前所有试图从队列中接收消息的任务此时都将获得相同的消息。该功能拷贝消息到各任务的消息缓冲中(或者让所有的等待任务得到指向消息的指针),并唤醒所有的等待任务。
- 接收消息:如果指定的消息队列中有消息,则将其中的第一条消息拷贝到调用者的缓冲区(或者将第一条消息指针传递给调用者),并从消息队列中删除它。若没有消息可能采取措施:
- 永远等待消息到达
- 等待消息并设置时限,可有效预防死锁
- 不等待强制返回,比如中断服务
- 删除消息队列:从系统中删除指定的消息队列,释放消息队列控制块及消息队列缓冲区。任何知道此消息队列ID号的代码都可以删除它。所有等待从这个消息队列接收消息的任务都回到就绪态,并得到一个错误信息表明消息队列已被删除。
- 获取有关消息队列的各种信息
事件
概述
在嵌入式实时内核中,事件是指一种表明预先定义的系统事件已经发生的机制。事件机制用于任务与任务之间、任务与ISR之间的同步。其主要的特点是可实现一对多的同步。
一个事件就是一个标志,不具备其它信息。
一个或多个事件构成一个事件集。事件集可以用一个指定长度的变量(比如一个8bit, 16bit或32bit的无符号整型变量,不同的操作系统其具体实现不一样)来表示,而每个事件由在事件集变量中的某一位来代表。
事件及事件集有以下特点:
- 事件间相互独立
- 事件仅用于同步,不提供数据传输功能
- 事件无队列,即多次发送同一事件,在未经过任何处理的情况下,其效果等同于只发送一次。
- 独立型同步:任务需要与一组事件中的任意一个发生同步(或)
- 关联型同步:任务等待若干事件都发生时才同步(与)
- 发送事件集 :“指在一次发送过程中发往接收者(比如任务)的一个或多个事件的组合。
- 待处理事件集:指已被发送到一个接收者但还没有被接收(即正在等待处理)的所有事件的集合。
- 事件条件:指事件接收者在一次接收过程中期待接收的一个或多个事件的集合。
- “或”同步:待处理事件集只要包括事件条件中的任一事件即可满足要求;
- “与”同步:待处理事件集必须包括事件条件中的全部事件方可满足要求。
数据结构
某些系统中事件集附属于任务,不需创建,其相关参数成为任务控制块的一部分
事件集控制块:管理所有创建的事件集
事件操作
- 创建事件集:申请空闲事件集控制块,设置事件集属性,初始化控制块中的域,分配ID号
- 删除事件集
- 发送事件(集)
- 接收事件(集)
- 获取有关事件集的各种信息
异步信号
概述
异步信号机制用于任务与任务之间、任务与ISR之间的异步操作,它被任务(或ISR)用来通知其它任务某个事件的出现。
异步信号标志可以依附于任务。需要处理异步信号的任务由两部分组成,一个是与异步信号无关的任务主体,另一个是ASR(异步信号服务例程)。
一个ASR对应于一个任务。当向任务发送一个异步信号,如果该任务正在运行则中止其自身代码的运行,转而运行与该异步信号相关的服务例程;或者当该任务被激活时,在投入运行前执行ASR。
异步信号机制也可以称作软中断机制,异步信号又被称为软中断信号。
异步信号与中断机制的对比:
- 相同点:
- 具有中断性:对中断的处理和对异步信号的处理都要先暂时地中断当前任务的运行。
- 有相应的服务程序: ISR 和 ASR
- 可以屏蔽响应
- 不同点
- 性质不同:中断由硬件或者特定的指令产生,不受任务调度的控制
- 处理时机(或响应时间)不同
- 执行的环境不同
- 一般地,ISR在独立的上下文中运行,操作系统为之提供专门的堆栈空间。
- ASR在相关任务的上下文中运行,所以ASR也是任务的一个组成部分。
异步信号与事件机制的对比:
同样是标志着某个事件的发生,事件机制的使用是同步的,而异步信号机制是异步的。
管道
概述
管道(pipe)是提供非结构化数据交换和实现任务间同步的内核对象。在传统的实现中,管道是单向数据交换设施。
数据在管道内像一个非结构字节流,按FIFO的次序从管道中读出。当管道空时,阻塞读者,当管道满时,阻塞写者。
管道允许有多个读者和写者。
管道与消息队列的区别
- 管道不存储多个消息,它存储的数据是非结构化的字节流;
- 管道中的数据严格地遵循先进先出的顺序;
- 管道支持选择(select)操作,而消息队列不支持。
命名管道和无名管道:
- 一个命名管道具有一个类似于文件名的名字,并像一个文件或设备出现在文件中。任务或ISR可以用名字对其进行引用。
- 无名管道没有名字且不在文件系统中出现,它必须使用创建管道时系统返回调用者的描述符才能引用。
操作
- 创建和删除一个管道;
- 读或写管道;
- 管道上的选择操作;
- Flush操作。