• 信号量使用小结


    信号量

    1、信号量简介

        信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:
       (1) 测试控制该资源的信号量。
       (2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。
       (3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。
       (4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。
        维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include/linux/sem.h 中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget,用以获得一个信号量ID。Linux2.6.26下定义的信号量结构体:

    struct semaphore {

            spinlock_t                lock;

            unsigned int             count;

            struct list_head        wait_list;

    };

    从以上信号量的定义中,可以看到信号量底层使用到了spin lock的锁定机制,这个spinlock主要用来确保对count成员的原子性的操作(count--)和测试(count > 0)。

        信号量(semaphore )实际是一个整数,它的值由多个进程进行测试(test)和设置(set)。就每个进程所关心的测试和设置操作而言,这两个操作是不可中断的,或称“原子”操作,即一旦开始直到两个操作全部完成。测试和设置操作的结果是:信号量的当前值和设置值相加,其和或者是正或者为负。根据测试和设置操作的结果,一个进程可能必须睡眠,直到有另一个进程改变信号量的值。

        信号量可用来实现所谓的“临界区”的互斥使用,临界区指同一时刻只能有一个进程执行其中代码的代码段。为了进一步理解信号量的使用,下面我们举例说明。

         假设你有很多相互协作的进程,它们正在读或写一个数据文件中的记录。你可能希望严格协调对这个文件的存取,于是你使用初始值为1的信号量,在这个信号量上实施两个操作,首先测试并且给信号量的值减1,然后测试并给信号量的值加1。当第一个进程存取文件时,它把信号量的值减1,并获得成功,信号量的值现在变为0,这个进程可以继续执行并存取数据文件。但是,如果另外一个进程也希望存取这个文件,那么它也把信号量的值减1,结果是不能存取这个文件,因为信号量的值变为-1。这个进程将被挂起,直到第一个进程完成对数据文件的存取。当第一个进程完成对数据文件的存取,它将增加信号量的值,使它重新变为1,现在,等待的进程被唤醒,它对信号量的减1操作将获得成功。

    上述的进程互斥问题,是针对进程之间要共享一个临界资源而言的,信号量的初值为1。实际上,信号量作为资源计数器,它的初值可以是任何正整数,其初值不一定为0或1。另外,如果一个进程要先获得两个或多个的共享资源后才能执行的话,那么,相应地也需要多个信号量,而多个进程要分别获得多个临界资源后方能运行,这就是信号量集合机制,Linux 讨论的就是信号量集合问题。

        进程间对共享资源的互斥访问是通过“信号量”机制来实现的。信号量机制是操作系统教材中比较重要的内容之一。Linux内核中提供了两个函数down()和up(),分别对应于操作系统教材中的P、V操作。

    信号量在内核中定义为semaphore数据结构,其位于include/i386/semaphore.h:

    c代码

    struct semaphore {
             atomic_t count;
                 int sleepers;
             wait_queue_head_t wait;
     #if WAITQUEUE_DEBUG
             long __magic;
     #endif
     };

        其中的count域就是“信号量”中的那个“量”,它代表着可用资源的数量。如果该值大于0,那么资源就是空闲的,也就是说,该资源可以使用。相反,如果count小于0,那么这个信号量就是繁忙的,也就是说,这个受保护的资源现在不能使用。在后一种情况下,count的绝对值表示了正在等待这个资源的进程数。该值为0表示有一个进程正在使用这个资源,但没有其他进程在等待这个资源。

       Wait域存放等待链表的地址,该链表中包含正在等待这个资源的所有睡眠的进程。当然,如果count大于或等于0,则等待队列为空。为了明确表示等待队列中正在等待的进程数,引入了计数器sleepers。

    down()和up()函数主要应用在文件系统和驱动程序中,把要保护的临界区放在这两个函数中间,用法如下:

    down();

    临界区

    up();

    2、信号量的使用及多任务的互斥

    2.1信号量使用

    (1)二进制信号量

        二进制信号量只能有一个值,0或1。当一个二进制信号量的值为0时,认为信号量是不可使用的或空的(empty);当值为1时,认为信号量时可用的或满的(full),使用二进制信号量能够满足两种任务的协调需要:互斥和同步。二进制信号量需要的系统开销最小,因而特别是用于高性能的需求。在“互斥信号量”中讨论的互斥信号量也是一种二进制信号量,但是它用于解决内在互斥的问题。在不需要使用互斥信号量的高级特性时。二进制信号量仍可用于互斥。

        二进制信号量的特点如下:

    1、  不支持嵌套;

    2、  不支持优先级继承协议;

    3  没有任务删除保护。

    因此,在只用到互斥的场合,使用互斥信号量更可取。

    (2)互斥信号量

    互斥二进制信号量是一种用于解决内在互斥问题的特殊二进制信号量,包括优先级倒置、删除安全以及资源的递归访问。

    互斥信号量的基本行为与二进制信号量一致,不同之处如下:

    1、  他仅用于互斥;

    2、  它仅能由提取它(即调用semTake())的任务释放;

    3、不能在中断服务程序中释放;

    4、semFlush()函数操作非法。

    (3)计数器信号量

    计数器信号量使用一个计数器,允许多次获取和释放,用于控制系统中共享资源的多个任务的使用,是实现任务同步和互斥的另一个手段。

        每释放一个信号量,计数器加一。每提取一个信号量,计计数器减一。计数器为0时,试图提取信号量的任务将被阻塞。与二进制信号量不同的是,如果信号量被释放时不存在阻塞任务,那么计数器加一。这意味着一个被释放两次的信号量,可以无阻赛的被提取两次。

    2.2多任务互斥

    (1)任务调度

    基于优先级的调度算法:WIND内核分为256个优先级,编号为0~255,优先级0最高,255最低;我们系统的优先级与之相反,数值越大,优先级越高。

    时间片轮调法:所有ready态的任务平均占用系统时间,防止某一高优先级的任务长期占用资源而导致其他任务无法运行。

    我们使用时通常将两种调度算法结合起来。

    (2)锁任务切换

    关闭任务切换功能,即在此任务运行过程中,即使有更高优先级的任务也不会发生任务切换。使用函数为taskLock和taskUnlock。

    在锁任务切换过程中,如果此任务挂起或阻塞,则系统从已就绪的任务中选择优先级最高的任务执行,一旦远任务恢复,则锁任务切换继续有效。

    (3)中断上锁

    这种方法涉及中断级互斥,也就是说,在互斥期间,即使外部事件的产生而引发相应的中断,系统也不会切换到相应的中断服务程序(ISR)。从而在上锁期间,他可能会造成系统对外部事件反应迟钝。这对于大多数实时系统而言,系统的实时性也得不到保证,因而不适合作为一种通用的互斥方法。

    然而,当涉及ISR需要互斥时,中断上锁又是必要的。但是任何情形下,应该使中断上锁时间尽量短,这也是所有操作系统的基本要求。

    (4)删除任务

    删除任务有两种方式:exit()删除任务本身,taskDelete()删除指定的任务。这两种删除任务的只释放所分配给任务的内存,如栈空间,而不会释放任务执行过程中所申请的资源,如malloc所分配的内存,申请的信号量等,因此在删除任务前必须确保此任务运行过程中所分配的资源都已释放,否则会造成资源泄漏。

    2.3使用信号量应该注意的问题

    (1)信号量也是一种资源,当某些任务占用信号量没有释放,可能会导致别的任务因为没有信号量可用而得不到执行,所以任务运行结束后必须释放信号量。

    (2)申请信号量VOS_SmP( VOS_UINT32 Sm_ID, VOS_UINT32 ulTimeOutInMillSec )第二个参数ulTimeOutInMillSec为等待时间(以毫秒为单位),即没有可用信号量时此任务等待的时间,如果在此时间内没有获得可用的信号量,则任务继续运行,并且不改变计数器的值,等待时间为0时表示永久等待。

    (3)任务因为没有信号量而被阻塞,恢复运行的方式有两种:其他任务释放信号量,此任务获得信号量;或者超时后任务继续运行。两者没有本质区别,前者任务获得信号量后并没有立即运行,而是任务进入ready态,放在同优先级队列的尾部等待执行,而后者在超时后任务也进入ready态,放在同优先级队列的尾部等待执行。

                            任务执行流图

    任务进入阻塞队列前由任务自己控制,一旦进入阻塞对后的操作由系统负责

    (4)等待时间一般不要设为0(永久等待),因为一旦出现异常,此任务就会被挂死,导致系统运行出错。对于需要事件触发的任务而言,等待时间可以设置为0,此时即使出现异常,也只是一次事件未被处理,而不会影响到整个系统的运行。

    2.4多任务编程注意事项

    (1)首先明确各个任务之间的通信过程,确定整个系统的通信流程;

    (2)确定需要保护的资源;所有公共的,且每次只能由一个任务访问的资源都需要进行保护;

    (3)一个完整的通信过程不能被打断,不但要对此通信所需的资源进行保护,整个通信过程也必须进行保护,以保证通信过程的完整性。对通信过程的保护可以使用信号量,也可以使用状态机;

    (4)需要注意:不同的任务拥有各自的栈空间,必须明确每一时刻只能有一个任务在运行,这个任务的所有局部变量使用自己的栈空间,和别的任务互不冲突;

    (5)必须注意:当一个任务因没有信号量而阻塞,另一个任务释放信号量时,前一个任务获得信号量后并不是立即就可以执行,而是放入就绪队列中等待执行,因此多任务的执行顺序是很复杂的过程,而不是简单的按照程序设计的顺序执行;

    (6)信号量使用后必须释放;

    (7)合理使用状态机控制整个通信过程,可以减少信号量的使用。

  • 相关阅读:
    将抓包工具证书从用户目录移动至系统目录,解决反爬对于本地证书认证(安卓7)
    《C++ concurrency in action》 读书笔记 -- Part 2 第三章 线程间的数据共享
    《C++ concurrency in action》 读书笔记 -- Part 3 第四章 线程的同步
    C++14 也快要来了
    《C++ concurrency in action》 读书笔记 -- Part 4 第五章 C++的多线程内存模型 (1)
    利用表达式树构建委托改善反射性能
    使用Task简化Silverlight调用Wcf(再续)
    逆变与协变详解
    Beginning Silverlight 4 in C#数据访问和网络
    使用Task简化Silverlight调用Wcf(续)
  • 原文地址:https://www.cnblogs.com/liyang3226/p/11171299.html
Copyright © 2020-2023  润新知