RTOS中互斥量、临界区、信号量、事件标志组和消息邮箱的详述(转)

1,300 阅读12分钟

原文地址:blog.chinaunix.net/uid-2309448…

为了好的理解互斥量、临界区、信号量、事件标志组和消息邮箱,下面一些知识对初学者来说很重要:

为了实现各任务之间的合作和无冲突的运行,在有关联的任务之间必须建立一些制约关系。这些制约关系主要有两种:直接制约关系和间接制约关系。
直接制约关系源于任务之间的合作。例如,有两个任务:任务1和任务2,它们需要通过访问同一个数据缓冲区合作完成一项工作,任务1负责向缓冲区写入数据,任务2负责从缓冲区读取数据。显然任务1还未向缓冲区写入数据时(缓冲区为空),任务2因不能从缓冲区得到有效数据而应处于等待状态;只有等任务1向缓冲区写入了数据之后,才应该通知任务2去读取数据。相反,当缓冲区的数据还未被任务2读取时(缓冲区为满时),任务1就不能向缓冲区写入新的数据而应处于等待状态;只有等到任务2自缓冲区中读取数据后,才应该通知任务1去写入数据。显然,如果这两个任务不能如此协调工作,将势必造成严重的后果。
间接制约关系源于对资源的共享。例如,任务1和任务2共享一台打印机,如果系统已经把打印机分配给了任务1,则任务2因不能获得打印机的使用权而应处于等待状态;只有当任务1把打印机释放后,系统才能唤醒任务2使其获得打印机的使用权。如果这两个任务不这样做,也会造成极大的混乱。
由上可知,在多任务合作工作的过程中,操作系统应解决两个问题:一是各任务间应具有一种互斥关系,即对于某个共享资源,如果一个任务正在使用,则其他任务只能等待,等到该任务释放该资源后,等待的任务之一才能使用它;二是相关的任务在执行上要有先后次序,一个任务要等其伙伴发来通知,或建立了某个条件后才能继续执行,否则只能等到。
任务之间的这种制约性的合作运行机制叫做任务间的同步(注意阿,是同步,不是同时啊!)。

1 互斥量(mutex)

1.1 定义

互斥量是一种公共资源,在指定时刻,它只能被一个线程占有(也就是所有权特性),而且占有它的线程可以反复申请这个互斥量。

1.2 目的

适合用来控制线程对某些特定资源(比如,临界区和共享资源)

① 保护临界区(Critical Section):临界区是一段代码,在执行时不能被其他线程打断,如果要保护它,用互斥量可以实现。举例如下:

status=tx_mutex_get(&my_mutex,TX_WAIT_FOREVER);//申请互斥量
if(status!=TX_SUCCESS)  break;//判断是否申请成功
tx_thread_slepp(5);//睡眠5个时钟节拍
status=tx_mutex_put(&my_mutex);//释放互斥量

以上代码就是临界区。

② 提供对共享资源的互斥访问:如果线程想对资源进行独占访问,就必须为每个资源提供对应的互斥量。而在访问资源之前必须为之申请到互斥量,用完之后要把互斥量释放掉,以便其他线程可以使用它来访问资源。

1.3 需要注意的问题

使用互斥量可能会带来死锁的问题和优先级倒置(反转)的问题。

  1. 死锁的问题,比如:进程A要调用M资源,而M资源此刻却被进程B占用,且进程B要调用N资源而N资源此刻被A占用,两进程均向CPU发出资源申请,而不释放自己占用的资源,所以两进程均处于暂停状态,这时死锁问题。

如何避免死锁问题呢?有其中一种方法就是:强制每个进程在同一时刻只能占有一个互斥量,如果一定要只有多个互斥量,可以要求线程必须按相同的顺序来申请互斥量,如:如果线程先得到互斥量1,然后才是互斥量2。

如果线程进入了死锁状态,怎样从中恢复呢?有下面两种方法:

  • 在调用tx_mutex_get()函数申请互斥量时,使用超时属性。

  • 使用tx_thread_wait_abort()中止陷入死锁的线程的挂起状态。

  1. 优先级倒置(反转)的问题,比如:一个低优先级的线程3某一时刻已经占有互斥量,而此时一个高优先级线程1申请此互斥量,此时级线程1就只能等待,这时如果有一个中优先级线程2已经就绪,那线程2就会中断线程3,从而就会出现线程2先于线程1而执行,这种现象就是优先级倒置问题。

    如何避免优先级倒置?可以让线程3申请的互斥量使用优先级继承的选项来解决这个问题。而优先级继承就是:当出现这种情况(一个低优先级的线程3某一时刻已经占有互斥量,而此时一个高优先级线程1申请此互斥量,此时级线程1就只能等待)时,低优先级线程3就继承高优先级线程的优先级,直到线程3释放互斥量后才恢复其优先级,这样就可以避免优先级倒置的问题。

1.4 互斥量的一大特性:所有权特性。

2 临界区(Critical Section)

2.1定义

通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。

2.2目的

同步同一个进程中的线程,保证在某一时刻只有一个线程能访问数据的简便方法,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个进程进入后期他所有试图访问此临界区的线程将被挂起,并一直等到已经进入此临界区的线程离开。

2.3互斥量和临界区同步线程的区别

虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。而互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。

3 信号量(semaphore)

3.1 定义

每个信号量都是公共资源,其值是一个32位计数。信号量的数据结构为一个值和一个指针,指针指向等待该信号量的下一个进程。

信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量;当它的值小于0时,其绝对值表示等待使用该资源的进程个数。注意,信号量的值仅能由PV操作来改变。

//实现的P,V操作算法描述:
P操作:while s>0:
        s=s-1
V操作:s=s+1

P表示申请一个资源,如果条件满足(即右可以分配的资源),则把资源分配给提出申请的进程,并且时资源数目s减1。V表示资源使用哪完毕之后,要把占有的资源释放,并且资源数目s加1 。

3.2 使用信号量的场合

  1. 互斥访问,和互斥量类似,两者的区要区别是:信号量不支持所有权,而所有权是互斥量的核心。当信号量提供互斥访问时,信号量的值代表它允许多少个线程同时访问与之相联系的资源。大多数情况下,提供互斥量的信号量的初值为1,这意味着在任何时刻只有一个线程可以访问相关资源,其值只可能为1或0的信号量一般称为二进制信号量。

使用二进制信号量时,使用者必须避免已经控制了信号量的线程再次调用tx_semaphore_get()来向信号量申请一个实例,如果调用了此项服务,这此调用失败,并将永久性挂起该线程,同时令资源永远不可用。

  1. 事件通知:比如可以在生产者—消费者模式的程序中提供事件通知。在这种程序中,消费者在消费资源(例如队列中的数据)前试图获取信号量,生产者一旦生产了资源就增加信号量的值,也就可以理解为生产者把实例交给了信号量,而消费者把实例从信号量中拿走了。这类信号量通常初值为0,其值直到生产者生产了资源才会增加。

上面这个例子说明了:生产者与消费者通过信号量通信,生产者通过向信号量放入实例产生事件通知(通过信号量的值变化来产生事件通知的作用),而消费者通过申请信号量实例来等待事件通知。

  1. 线程同步:除了使用临界区与互斥量可以完成线程间的同步外,还可以使用信号量,其信号允许多个线程同时使用共享资源,它指出了同时访问共享资源的线程最大数目。在信号量内部有一个计数器,当有线程访问共享资源时,计数器将自动递减,当它为0时,不再允许其他线程对共享资的访问,直到有一个线程释放共享资源,从而完成对共享资源的保护。

多个线程竞争n个资源(n>0),比如有4个资源,那么就初始化量为4,每当线程各自请求信号量的时候,如果信号量为0,则表示没有可用的资源,请求的线程要等它其他占用者释放信号量,如果信号量大于0,则线程的请求被允许,信号量被递减,然后线程可以按约定访问资源,当线程使用资源完毕,应该释放信号量,以便其他线程需要时使用。

3.3 互斥量和信号量的区别

如果互斥对程序很关键,那么就该使用互斥量;如果互斥不是程序中的主要因素,那么就使用信号量,因为它比互斥量稍快,使用系统资源比较少。

3.4 信号量的事件通知和线程同步的区别

信号量的事件通知是用于两个线程之间(比如生产者—消费者问题),而信号量的线程同步时用于多线程之间(比如一个可以多人打电话的电话亭问题)。

4 事件标志组

4.1 概述

事件标志可以被任何线程置位和复位,也可以被任何程序查看,线程可以因为等待一组事件标志被置位而挂起。每个事件标志由一个位代表,每32个事件标志被安排在一组。

将事件标志置位或复位是通过把当前事件标志组和新的标志组进行逻辑“与”或逻辑“或”;为了得到事件标志,也要做类似的逻辑操作。一旦事件标志组中有标志被置位,系统就会检查相应组的挂起队列,如果能满足挂起线程,则该线程就被恢复。

4.2 使用场合

应为一个事件标志组中包括32个1位的标志,可以有非常多的组合方式,所有它比较适合于同步很多线程的场合。

5 消息队列

5.1 概述

首先它是一种队列,所以它是一种数据结构,但它又是“消息队列”,所以它是一种可以传递多个消息(消息也就是数据,实际上传递得是消息的指针)的数据结构。

5.2 使用

如果线程与线程之间要传递很多消息(数据),那么为了适应不同数据的需要,最好是在存储器中建立多个数据缓冲区,把要传递的数据放在缓冲区中,其中在消息队列中分别存放消息的缓冲区的地址,这样就可实现线程间的数据通信。适合于线程间传递很多数据的场合

5.3 消息队列的线程间通信和信号量的线程同步的区别

消息队列的线程间通信是用于传递数据,而信号量的线程同步侧重的是信号量的指示作用。

6 应用

6.1 互斥量和二进制信号量的一个应用:

一个一次只允许一个用户使用的电话亭,为了防止用户发生冲突,电话亭的门上就应该有这样一个标志,并用它来表示电话亭的被使用情况。例如,用一个可以变换两种颜色的牌子,用红色表示“有人”,用绿色表示“没人”。这样,人们见到牌子上的颜色是绿色时(线程通过查询知道信号量的值为1),就可以进去打电话(表示一个进程可以占用此资源(电话亭));如果是红色,那么就只好等待;如果又陆续来了很多人,那么就排队等待。其中,电话亭上的这个牌子就是信号量或互斥量。

6.2 信号量同步线程的一个例子:

一个电话亭可以允许多人(线程)打电话,电话亭门上的计数器在每进入一个人时自动减1,而每出去一个人时会自动加1。计数器上的初值就是电话亭的最多能容纳打电话的人数,那么来人只要见到计数器的值大于0,就可以进去打电话;否则只能等待。其中这个计数器就是用于同步线程的信号量。