信号量 订阅
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。 展开全文
信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。
信息
中文名称
信号量
作    用
两个或多个关键代码不被并发调用
别    名
信号灯
外文名称
Semaphore
要    求
线程必须获取一个信号量
类    型
计算机、电子
信号量描述
以一个停车场的运作为例。简单起见,假设停车场只有三个车位,一开始三个车位都是空的。这时如果同时来了五辆车,看门人允许其中三辆直接进入,然后放下车拦,剩下的车则必须在入口等待,此后来的车也都不得不在入口处等待。这时,有一辆车离开停车场,看门人得知后,打开车拦,放入外面的一辆进去,如果又离开两辆,则又可以放入两辆,如此往复。在这个停车场系统中,车位是公共资源,每辆车好比一个线程,看门人起的就是信号量的作用。
收起全文
精华内容
下载资源
问答
  • 信号量

    千次阅读 2018-12-21 10:11:26
    信号量(semaphores)是 20 世纪 60 年代中期 Edgser Dijkstra 发明的。 使用信号量的最初目的是 为了给共享资源建立一个标志,该标志表示该共享资源被占用情况。这样,当一个任务在访问共享资源之 前,就可以先对这...

    以下转载自安富莱电子: http://forum.armfly.com/forum.php

     

    信号量的概念及其作用
    信号量(semaphores)是 20 世纪 60 年代中期 Edgser Dijkstra 发明的。 使用信号量的最初目的是
    为了给共享资源建立一个标志,该标志表示该共享资源被占用情况。这样,当一个任务在访问共享资源之
    前,就可以先对这个标志进行查询,从而在了解资源被占用的情况之后,再来决定自己的行为。
    实际的应用中,信号量的作用又该如何体现呢?比如有个 30 人的电脑机房,我们就可以创建信号量
    的初始化值是 30,表示 30 个可用资源,不理解的初学者表示信号量还有初始值?是的,信号量说白了就
    是共享资源的数量。 另外我们要求一个同学使用一台电脑,这样每有一个同学使用一台电脑,那么信号量
    的数值就减一,直到 30 台电脑都被占用,此时信号量的数值就是 0。 如果此时还有几个同学没有电脑可
    以使用,那么这几个同学就得等待,直到有同学离开。 有一个同学离开,那么信号量的数值就加 1,有两
    个就加 2,依此类推。刚才没有电脑用的同学此时就有电脑可以用了,有几个同学用,信号量就减几,直
    到再次没有电脑可以用,这么一个过程就是使用信号量来管理共享资源的过程。
    平时使用信号量主要实现以下两个功能:
     两个任务之间或者中断函数跟任务之间的同步功能,这个和前面章节讲解的事件标志组是类似的。其
    实就是共享资源为 1 的时候。
     多个共享资源的管理,就像上面举的机房上机的例子。
    针对这两种功能,FreeRTOS 分别提供了二值信号量和计数信号量,其中二值信号量可以理解成计数

    信号量的一种特殊形式,即初始化为仅有一个资源可以使用,只不过 FreeRTOS 对这两种都提供了 API
    函数,而像 RTX,uCOS-II 和 III 是仅提供了一个信号量功能,设置不同的初始值就可以分别实现二值信
    号量和计数信号量。 当然,FreeRTOS 使用计数信号量也能够实现同样的效果。
    实际上信号量还有很多其它用法,而且极具挑战性,可以大大的开拓大家的视野,有兴趣的同学可以
    阅读一下《The Little Book Of Semaphores》 ,作者是 Allen B. Downy。 
    FreeRTOS 任务间计数信号量的实现
    任务间信号量的实现是指各个任务之间使用信号量实现任务的同步或者资源共享功能。 下面我们通过
    如下的框图来说明一下 FreeRTOS 计数信号量的实现,让大家有一个形象的认识。 

    运行条件:
     创建 2 个任务 Task1 和 Task2。
     创建计数信号量可用资源为 1。
    运行过程描述如下:
     任务 Task1 运行过程中调用函数 xSemaphoreTake 获取信号量资源,如果信号量没有被任务 Task2
    占用,Task1 将直接获取资源。 如果信号量被 Task2 占用,任务 Task1 将由运行态转到阻塞状态,
    等待资源可用。一旦获取了资源并使用完毕后会通过函数 xSemaphoreGive 释放掉资源。
     任务 Task2 运行过程中调用函数 xSemaphoreTake 获取信号量资源,如果信号量没有被任务 Task1
    占用,Task2 将直接获取资源。 如果信号量被 Task1 占用,任务 Task2 将由运行态转到阻塞状态,
    等待资源可用。一旦获取了资源并使用完毕后会通过函数 xSemaphoreGive 释放掉资源。 

     FreeRTOS 中断方式计数信号量的实现
    FreeRTOS 中断方式信号量的实现是指中断函数和 FreeRTOS 任务之间使用信号量。 信号量的中断方
    式主要是用于实现任务同步,与前面章节讲解事件标志组中断方式是一样的。
    下面我们通过如下的框图来说明一下 FreeRTOS 中断方式信号量的实现,让大家有一个形象的认识。

     

    运行条件:
     创建一个任务 Task1 和一个串口接收中断。
     信号量的初始值为 0,串口中断调用函数 xSemaphoreGiveFromISR 释放信号量,任务 Task1 调用
    函数 xSemaphoreTake 获取信号量资源。
    运行过程描述如下:
     任务 Task1 运行过程中调用函数 xSemaphoreTake,由于信号量的初始值是 0,没有信号量资源可
    用,任务 Task1 由运行态进入到阻塞态。
     Task1 阻塞的情况下,串口接收到数据进入到了串口中断服务程序,在串口中断服务程序中调用函数
    xSemaphoreGiveFromISR 释放信号量资源,信号量数值加 1,此时信号量计数值为 1,任务 Task1
    由阻塞态进入到就绪态,在调度器的作用下由就绪态又进入到运行态,任务 Task1 获得信号量后,信
    号量数值减 1,此时信号量计数值又变成了 0。
     再次循环执行时,任务 Task1 调用函数 xSemaphoreTake 由于没有资源可用再次进入到阻塞态,等
    待串口释放信号量资源,如此往复循环。
    上面就是一个简单的 FreeRTOS 中断方式信号量同步过程。 实际应用中,中断方式的消息机制要注意以下
    四个问题: 
     中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
     实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在
    任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优
    先级,以便退出中断函数后任务可以得到及时执行。 
     中断服务程序中一定要调用专用于中断的信号量设置函数,即以 FromISR 结尾的函数。
     在操作系统中实现中断服务程序与裸机编程的区别。
       如果 FreeRTOS 工程的中断函数中没有调用 FreeRTOS 的信号量 API 函数,与裸机编程是一样
    的。
       如果 FreeRTOS 工程的中断函数中调用了 FreeRTOS 的信号量 API 函数,退出的时候要检测是
    否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换,这点与裸机编程稍有
    区别,详见 实验例程说明(中断方式):
       另外强烈推荐用户将 Cortex-M3 内核的 STM32F103 和 Cortex-M4 内核的 STM32F407,F429
    的 NVIC 优先级分组设置为 4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中
    断优先级的管理将非常方便。
       用户要在 FreeRTOS 多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。 
    计数信号量 API 函数
    使用如下 18 个函数可以实现 FreeRTOS 的信号量(含计数信号量,二值信号量和互斥信号): 
     xSemaphoreCreateBinary()
     xSemaphoreCreateBinaryStatic()
     vSemaphoreCreateBinary()
     xSemaphoreCreateCounting()
     xSemaphoreCreateCountingStatic()
     xSemaphoreCreateMutex()
     xSemaphoreCreateMutexStatic()
     xSem'CreateRecursiveMutex()
     xSem'CreateRecursiveMutexStatic()
     vSemaphoreDelete()
     xSemaphoreGetMutexHolder()
     uxSemaphoreGetCount()
     xSemaphoreTake()
     xSemaphoreTakeFromISR()
     xSemaphoreTakeRecursive()
     xSemaphoreGive()
     xSemaphoreGiveRecursive()
     xSemaphoreGiveFromISR()
    关于这 18 个函数的讲解及其使用方法可以看 FreeRTOS 在线版手册 。

    这里我们重点的说以下 4 个函数:
     xSemaphoreCreateCounting ()
     xSemaphoreGive ()
     xSemaphoreGiveFromISR ()
     xSemaphoreTake ()
    因为本章节配套的例子使用的是这 4 个函数。

    函数 xSemaphoreCreateCounting 

    函数描述:
    函数 xSemaphoreCreateCounting 用于创建计数信号量。
     第 1 个参数是设置此计数信号量支持的最大计数值。
     第 2 个参数是设置计数信号量的初始值。 

     返回值,如果创建成功会返回消息队列的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不足,
    无法为此消息队列提供所需的空间会返回 NULL。
    使用这个函数要注意以下问题:
    1. 此函数是基函数 xQueueCreateCountingSemaphore 实现的:
    #define xSemaphoreCreateCounting( uxMaxCount, uxInitialCount ) \
    xQueueCreateCountingSemaphore( ( uxMaxCount ), ( uxInitialCount ) )
    函数 xQueueCreateCountingSemaphore 的实现是基于消息队列函数 xQueueGenericCreate 实现
    的。
    2. 使用此函数要在 FreeRTOSConfig.h 文件中使能宏定义:
    #define configUSE_COUNTING_SEMAPHORES 1

     

    函数 xSemaphoreGive 

    函数描述:
    函数 xSemaphoreGive 用于在任务代码中释放信号量。
     第 1 个参数是信号量句柄。
     返回值,如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为计数信号量的实现是基于消
    息队列,返回失败的主要原因是消息队列已经满了。
    使用这个函数要注意以下问题:
    1. 此函数是基于消息队列函数 xQueueGenericSend 实现的: 

    #define xSemaphoreGive( xSemaphore ) \
    xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, \
    queueSEND_TO_BACK )
    2. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是
    xSemaphoreGiveFromISR。
    3. 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者
    xSemaphoreCreateCounting()创建了信号量。
    4. 此函数不支持使用 xSemaphoreCreateRecursiveMutex()创建的信号量。

    函数 xSemaphoreGiveFromISR 
     

    函数描述:
    函数 xSemaphoreGiveFromISR 用于中断服务程序中释放信号量。
     第 1 个参数是信号量句柄。
     第 2 个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE,
    说明有高优先级任务要执行,否则没有。
     返回值,如果信号量释放成功返回 pdTRUE,否则返回 errQUEUE_FULL。
    使用这个函数要注意以下问题:
    1. 此函数是基于消息队列函数 xQueueGiveFromISR 实现的:
    #define xSemaphoreGiveFromISR( xSemaphore, pxHigherPriorityTaskWoken ) \
    xQueueGiveFromISR( ( QueueHandle_t ) ( xSemaphore ), ( pxHigherPriorityTaskWoken ) )
    2. 此函数是用于中断服务程序中调用的,故不可以任务代码中调用此函数,任务代码中中使用的是
    xSemaphoreGive。
    3. 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary()或者 xSemaphoreCreateCounting()
    创建了信号量。
    4. 此函数不支持使用 xSemaphoreCreateMutex ()创建的信号量

       

    函数 xSemaphoreTake 

     

    函数描述:
    函数 xSemaphoreTake 用于在任务代码中获取信号量。
     第 1 个参数是信号量句柄。
     第 2 个参数是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。
     返回值,如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。
    使用这个函数要注意以下问题:
    1. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是
    xSemaphoreTakeFromISR。
    2. 如果消息队列为空且第 2 个参数为 0,那么此函数会立即返回。
    3. 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配
    置为 portMAX_DELAY,那么此函数会永久等待直到信号量可用。

    实验例程现象(任务间通信)

     

     

    FreeRTOS 实验_计数信号量(中断方式)——及其重要

    K2 键按下,启动单次定时器中断,50ms 后在定时器中断给任务 vTaskBeep 发送同步信号。 

     这个中断程序调试了我大半下午,现在总结一下要点:

    我的定时器中断服务函数:

    复制代码

    void  BASIC_TIM_IRQHandler (void)
    {
        if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) 
        {    
            ulHighFrequencyTimerTicks++;
            cc++;
            TIM_ClearITPendingBit(BASIC_TIM , TIM_IT_Update);           
        }        
            if(cc==10000)
            {
                cc=0;
                TIM_ClearITPendingBit(BASIC_TIM , TIM_IT_Update);
                BaseType_t xHigherPriorityTaskWoken = pdFALSE;
        LED3_TOGGLE
        /* 发送同步信号 */
            xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
    
        /* 如果xHigherPriorityTaskWoken = pdTRUE,那么退出中断后切到当前最高优先级任务执行 */
            portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
            
            }
            
    }

    复制代码

    portYIELD_FROM_ISR函数的参数,如果为pdTRUE,退出中断就会切换到高优先级任务执行。那是因为这个portYIELD_FROM_ISR函数在参数为pdTRUE时,会调用portYIELD()函数强制上下文切换。

    FreeRTOS有两种方法触发任务切换:

    • 执行系统调用,比如普通任务可以使用taskYIELD()强制任务切换,中断服务程序中使用portYIELD_FROM_ISR()强制任务切换;
    • 系统节拍时钟中断

          对于Cortex-M3,M4平台,这两种方法的实质是一样的,都会使能一个PendSV中断,在PendSV中断服务程序中,找到最高优先级的就绪任务,然后让这个任务获得CPU运行权,从而完成任务切换。
          对于第一种任务切换方法,不管是使用taskYIELD()还是portYIELD_FROM_ISR(),最终都会执行宏portYIELD(),代码如下:

     从上面的代码中可以看出,PendSV中断的产生是通过代码:portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT实现的,它向中断状态寄存器bit28位写入1,将PendSV中断设置为挂起状态,等到优先级高于PendSV的中断执行完成后,PendSV中断服务程序将被执行,进行任务切换工作。

    PendSV中断服务程序源码是一段汇编,我们这里不去深究,画出这段汇编程序的流程图:

     

     (此处摘自:http://blog.csdn.net/zhzht19861011/article/details/51418383)

     

    我们只用知道这个中断函数会进入临界区和退出临界区就行了,这也是我刚开始做实验找到了影响实验的因素却没有找到源头,经过上面的流程图就豁然开朗了。

    要进入临界区,就要关闭优先级大于 等于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY这个宏配置的值的所有中断,这个值我之前配置成3,而我的定时器中断优先级配置成2,造成的实验现象就是,定时器中断进入一次之后,系统就卡死了。从逻辑上讲,这样设置是可以的才对的啊,我不想要os管理我的定时器中断,所以就不屏蔽我的定时器中断,但是FreeRTOS有自己的机制和解释,我们需要遵守别人的规定,继续看下面。卡在这里:

     一直在寻找中断优先级

    当我把定时器中断的优先级的数学字面值设置成大于等于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY这个值时,系统就正常运行了。

    这也就是说,在我们的实验中,必须保证定时器中断被freertos管理,毕竟我们的发送同步消息函数是在定时器中断函数中的,也就是我们定时中断设置的优先级,在数学字面值(后来看官方文档,这个数学字面值既然巧合的默契了)上,应该大于等于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY宏的值,这样系统才可以正常运行。

    上面这一段是我自己在没有看到官方解释时候的理解,果不其然,在卡死函数下方,官方给出了参考链接。现在截取关键部分展示:

     译:

    数字优先级值与逻辑优先级设置之间的反向关系

    Cortex-M硬件细节

    接下来要知道的是,在ARM Cortex-M内核中,数字低优先级值用于指定逻辑高的中断优先级。例如,分配数值优先级值为2的中断的逻辑优先级高于分配数字优先级为5的中断的逻辑优先级。换句话说,即使数字2较低,中断优先级2高于中断优先级5.希望能够清楚:分配数字优先级为2的中断可以中断(嵌套)一个分配数字优先级为5的中断,但是分配数字优先级为5的中断不能中断分配了数字中断优先级为2的。
    这是ARM Cortex-M中断优先级的最直观的方面,因为它与大多数非ARM Cortex-M3微控制器架构相反。

    使用RTOS时的相关性

    以“FromISR”结尾的FreeRTOS功能是中断安全的,但是即使这些函数的逻辑优先级高于configMAX_SYSCALL_INTERRUPT_PRIORITY(configMAX_SYSCALL_INTERRUPT_PRIORITY在FreeRTOSConfig.h头文件中定义)的优先级,也不能调用这些函数。因此,使用RTOS API函数的任何中断服务程序必须将其优先级手动设置为数值等于或大于configMAX_SYSCALL_INTERRUPT_PRIORITY设置的值。这确保中断的逻辑优先级等于或小于configMAX_SYSCALL_INTERRUPT_PRIORITY设置。(果然和我想的一样,这也就是既然你调用了FreeRTOS的API,你的中断优先级设置就应该是属于FreeRTOS管理的)
    Cortex-M中断优先级的默认值为零。零是可能的最高优先级值。因此,不要将使用中断安全RTOS API的中断优先级设置为其默认值。

    这也就是说,官方都已告诉我们了,我们既然使用别人的产品,就要遵守别人的规定和约束,终于,解惑了。

    展开全文
  •  2.POSIX 信号量和 System V 信号量的作用是相同的,都是用于同步进程之间及线程之间的操作,以达  到无冲突地访问共享资源的目的。  3.POSIX 信号量的作用和 System V 信号量是一样的。但是两者在接口上有很大...

    (1)概述
        1.理论与SYSTE-V一样, 但在IPC上进行了封装, 和在文件系统上进行了扩展

        2.POSIX 信号量和 System V 信号量的作用是相同的,都是用于同步进程之间及线程之间的操作,以达
          到无冲突地访问共享资源的目的。

        3.POSIX 信号量的作用和 System V 信号量是一样的。但是两者在接口上有很大的区别:
            ·POSIX 信号量将创建和初始化合二为一
            ·一次只能修改一个信号量。System V 信号量其本质是信号量集,一次可以修改多个信号量。
            ·POSIX 信号量一次只能将信号量的值加 1 或减 1 。System V 信号量能够加上或减去一个大于 1 的值。
            ·POSIX 信号量并没有提供一个等待信号量变为 0 的接口, System V 信号量中, semop 函数则提供了这样的接口
            ·POSIX 信号量并没有提供 UNDO 操作,而 System V 信号量则提供了这样的操作。

        4.POSIX 信号量真正比 System V 信号量优越的地方在于, POSIX 信号量性能更好。    
            对于 System V 信号量而言,每次操作信号量,必然会从用户态陷入内核态时间上的开销很大
             POSIX 信号量,只要不存在真正的两个线程争夺一把锁的情况,那么修改信号量就只是用户态的操作,并不会牵扯到内核。
             在竞争并不激烈的情况下, POSIX 的性能要远远高于 System V 信号量。

        5.(☆)POSIX 提供了两类信号量:有名信号量和无名信号量。
            1.无名信号量,又称为基于内存的信号量,由于其没有名字,没法通过 open 操作直接找到对应的信号量,
              所以很难直接用于没有关联的两个进程之间:无名信号量多用于线程之间的同步。
            2.有名信号量由于其有名字,多个不相干的进程可以通过名字来打开同一个信号量,从而完成同步
                操作,所以有名信号量的操作要方便一些,适用范围也比无名信号量更广。

    (2)有名信号量(用于进程间的通信)

        // 有名字,多个不相干的进程可以通过名字来打开同一个信号量,从而完成同步
        ①创建、打开、关闭、删除有名型号量
            1.sem_t *sem_open(const char *name, int oflag);

            2.sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
              第二个参数 oflag 标志位支持的标志包括 O_CREAT 和 O_EXCL 标志位。如果带了 O_CREAT 标志位,则表示要创建信号量。
                mode:  创建的新信号量的访问权限
                value: 新建信号量的初始值。创建和赋初值都是由一个接口来完成的
                不要尝试创建 sem_t 结构体的副本,切记,后面所有的调用都要用通过 sem_open 返回的 sem_t 类型的指针来进行操作,而不能使用结构体的副本。

            3.int sem_close(sem_t *sem);             // 关闭信号量
                1.当一个进程打开有名信号量时,系统会记录进程与信号的关联关系。调用 sem_close 时,会终止这
                  种关联关系,同时信号量的进程数的引用计数减 1 。
                2.进程终止时,进程打开的有名信号量会自动关闭。当进程执行 exec 系列函数时,进程打开的有名信号量会自动关闭。
                3.关闭不等同于删除.

            4.int sem_unlink(const char *name);    // 删除信号量
               将有名信号量的名字作为参数,传递给 sem_unlink ,该函数会负责将该有名信号量删除。由于系统
                为信号量维护了引用计数,所以只有当打开信号量的所有进程都关闭了之后,才会真正地删除。

            5.注意
               如果程序结尾没有删除Posix信号量, 第二次运行时就只会打开前面创建的信号量;
                而信号量的初值是在信号量创建时设置的; 
            ==> 第二次运行时,信号量的初值可能不是我们想要的

        ②信号量的使用
            /*  信号量的使用,总是和某种可用资源联系在一起的。创建信号量时的 value 值,其实指定了对应资
                源的初始个数。当申请该资源时,需要先调用 sem_wait 函数;当发布该资源或使用完毕释放该资源时,
                则调用 sem_post 函数。
            */
            1.等待信号量
              int sem_wait(sem_t *sem);
                如果调用 sem_wait 函数时,信号量的当前值大于 0 ,那么 sem_wait 函数立刻返回。否则 sem_wait 函
                数陷入阻塞,待信号量的值大于 0 之后,再执行减 1 操作,然后成功返回。

              int sem_trywait(sem_t *sem);
                sem_trywait 会尝试将信号量的值减 1 ,如果信号量的值大于 0 ,那么该函数将信号量的值减 1 之后会
                立刻返回。如果信号量的当前值为 0 ,那么 sem_trywait 也不会陷入阻塞,而是立刻返回失败,并置 errno
                为 EAGAIN 。

              int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
                有限期等待,第二个参数为一个绝对时间。可以使用 gettimeofday 函数获取到 struct timeval 类型的当前时间,然后
                将 timeval 转换成 timespec 类型的结构体,最后在该值上加上想等待的时间。
                如果超过了等待时间,信号量的值仍然为 0 ,那么返回 -1 ,并置 errno 为 ETIMEOUT 。

            2.发布信号量
              int sem_post(sem_t *sem);
                表示资源已经使用完毕,可以归还资源了。该函数会使信号量的值加 1 。
                如果发布信号量之前,信号量的值是 0 ,有多个进程正等待在信号量上,那么将无法确认哪个进程会被唤醒。

            3.获取信号量的值
              int sem_getvalue(sem_t *sem, int *sval);
                返回当前信号量的值,并将值写入 sval 指向的变量
                当 sem_getvalue 返回时,其返回的值可能已经过时了。从这个意义上讲,该接口的意义并不大。

    (3)无名信号量(用于多线程间的同步互斥)
        ①为什么要使用无名信号量
            1.无名信号量,由于其没有名字,所以适用范围要小于有名信号量。只有将无名信号量放在多个进
              程或线程都共同可见的内存区域时才有意义,否则协作的进程无法操作信号量,达不到同步或互斥的目的。
            2.所以一般而言,无名信号量多用于线程之间。因为线程会共享进程的地址空间,所以访问共同的无名信号量是很容易办到的事情。
            3. 或者将信号量创建在共享内存内,多个进程通过操作共享内存的信号量达到同步或互斥的目的。

        ②初始化.使用无名信号量
            int sem_init(sem_t *sem, int pshared, unsigned int value);
                pshared: 用于声明信号量是在线程间共享还是在进程间共享。
                      0 表示在线程间共享,非零值则表示信号量将在进程间共享。
                1.要想在进程间共享,无名信号量必须位于共享内存区域内。
                2.对于线程间共享的信号量,线程组退出了,无名信号量也就不复存在了。
                3.无名信号量初始化以后,就可以像操作有名信号量一样操作无名信号量了。
            int sem_wait(sem_t *sem);
            int sem_trywait(sem_t *sem);
            int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
            int sem_post(sem_t *sem);
            int sem_getvalue(sem_t *sem, int *sval);
                  
        ③销毁无名信号量
            int sem_destroy(sem_t *sem);
                1.销毁 sem_init 函数初始化的无名信号量。
                2.只有在所有进程都不会再等待一个信号量时,它才能被安全销毁。

     (4)示例代码

    #include <pthread.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <stdlib.h>
    #include <semaphore.h>
    
    #define CUR printf("cur %d\n", __LINE__)
    //#define CUR
    #define USED_TO_PTHREADS 0
    #define USED_TO_PROCESSS 1
    
    static sem_t sem_product;
    static sem_t sem_5;
    static sem_t sem_1;
    static int val = 0;
    
    void *productor(void *arg)
    {	
    	while(1) {
    		sem_post(&sem_product);				// 生产了一个产品
    		sem_getvalue(&sem_product, &val);	// 获取无名信号量的值
    		printf("prod: %d\n", val); 
    		if (val == 5)
    		{	// 唤醒阻塞的消费者
    			sem_post(&sem_5);
    			// 生产5个后不再生产, 直到消费到只有1个产品
    			sem_wait(&sem_1);
    		}
    		sleep(1);		// 每1s生产一个产品
    	}
    	pthread_exit(NULL);
    }
    void *consumer1(void *arg)
    {		
    	while(1) {
    		sem_wait(&sem_5);		// 未达到5个之前, 消费者阻塞
    		sem_wait(&sem_product);				// 消费了一个产品
    		sem_getvalue(&sem_product, &val);	// 获取无名信号量的值
    		printf("cons1: %d\n", val); 
    		if (val == 1)		
    		{	// 唤醒阻塞的生产者
    			sem_post(&sem_1);	// 通知生产
    			sem_wait(&sem_5);	// 阻塞到生产了5个产品
    		}
    		sem_post(&sem_5);		// 从5个消费到一个的过程中不应该阻塞消费者
    		sleep(1);								
    	}
    	pthread_exit(NULL);
    }
    void *consumer2(void *arg)
    {		
    	while(1) {
    		sem_wait(&sem_5);		// 未达到5个之前, 消费者阻塞
    		sem_wait(&sem_product);				// 消费了一个产品
    		sem_getvalue(&sem_product, &val);	// 获取无名信号量的值
    		printf("cons2: %d\n", val); 
    		if (val == 1)		
    		{	// 唤醒阻塞的生产者
    			sem_post(&sem_1);	// 通知生产
    			sem_wait(&sem_5);	// 阻塞到生产了5个产品
    		}
    		sem_post(&sem_5);		// 从5个消费到一个的过程中不应该阻塞消费者
    		sleep(1);								
    	}
    	pthread_exit(NULL);
    }
    int main(int argc, char **argv)
    {
    	pthread_t thid, thid1, thid2;
    
    	// 创建POSIX无名信号量
    	sem_init(&sem_product, USED_TO_PTHREADS, 0);		/* 最开始没有产品 */
    	sem_init(&sem_5, USED_TO_PTHREADS, 0);			/* 最开始没有5个产品 */
    	sem_init(&sem_1, USED_TO_PTHREADS, 0);			/* 最开始没有达到一个产品 */
    	
    	if(pthread_create(&thid, NULL, productor, NULL))
    		perror("pthread_create err");
    	if(pthread_create(&thid1, NULL, consumer1, NULL))
    		perror("pthread_create err");
    	if(pthread_create(&thid2, NULL, consumer2, NULL))
    		perror("pthread_create err");
    	
    	pthread_join(thid, NULL);	// 线程未退出时陷入阻塞
    	pthread_join(thid1, NULL);
    	pthread_join(thid2, NULL);
    	
    	// 销毁无名信号量
    	sem_destroy(&sem_product);
    	sem_destroy(&sem_5);
    	sem_destroy(&sem_1);
    	
    	return 0;
    }
    
    /* 要求: 1.生产5个后广播消费者使用
    		 2.消费到还剩一个后,通知生产者生产
    		 3.生产期间不准消费, 消费期间不准生产
    		 4.生产者0.5s生产一个, 每个消费者1s消费一个
    		 5.使用无名信号量实现
    */
    /* 运行结果 : 	
    	book@gui_hua_shu:$ ./a.out
    		// 生产期间, 所有消费者阻塞在sem_5
    	prod: 1
    	prod: 2
    	prod: 3
    	prod: 4
    	prod: 5		
    		
    	cons1: 4	// 生产5个后, 生产者阻塞在sem_1, cons1抢夺到sem_5, cons2阻塞
    				 用完后, cons1释放了sem_5
    	cons2: 3	// 线程cons2抢到了sem_5
    				 用完后, cons2释放了sem_5
    	cons1: 2
    	cons2: 1	// cons2释放了sem_1, cons2阻塞在sem_5
    				// 这时cons1也阻塞在了sem_5
    	// 循环
    	prod: 2
    	prod: 3
    	prod: 4
    	prod: 5
    .................................*/
    

     

    展开全文
  • 【Linux】Linux的信号量

    万次阅读 2018-08-18 22:33:08
    所谓信号量集,就是由多个信号量组成的一个数组。作为一个整体,信号量集中的所有信号量使用同一个等待队列。Linux的信号量集为进程请求多个资源创造了条件。Linux规定,当进程的一个操作需要多个共享资源时,如果只...

    所谓信号量集,就是由多个信号量组成的一个数组。作为一个整体,信号量集中的所有信号量使用同一个等待队列。Linux的信号量集为进程请求多个资源创造了条件。Linux规定,当进程的一个操作需要多个共享资源时,如果只成功获得了其中的部分资源,那么这个请求即告失败,进程必须立即释放所有已获得资源,以防止形成死锁。

     

    信号量集的结构

    信号量结构

    描述信号量的内核数据结构如下:

    struct sem {
    	int	semval;		/* 信号量的当前值 */
    	int	sempid;		/* 上一次操作本信号的进程PID */
    };

    其中,域semval为一个整型变量,表示相应共享资源的被占用情况;域sempid则记录了上一次使用这个信号量的进程的标识号。

    信号量集的结构

    如果把若干个信号量组成一个数组sem[],那么这个数组就是信号量集。使用信号量集可以同时把多个共享资源设置为互斥资源。

    Linux用一个数组头来管理这个信号量集,它包含信号量集的所有基本信息。

    数组头的结构sem_array如下:

    struct sem_array {
    	struct kern_ipc_perm	sem_perm;	/* IPC许可结构 */
    	time_t			sem_otime;	/* 上一次信号量的操作时间 */
    	time_t			sem_ctime;	/* 信号量变化时间 */
    	struct sem		*sem_base;	/* 指向信号量数组的指针 */
    	struct list_head	sem_pending;	/* 等待队列 */
    	struct list_head	list_id;	/* undo结构 */
    	unsigned long		sem_nsems;	/* 信号量集里面信号量的数目 */
    };

    结构的第一个域sem_perm为检查用户权限的许可结构。数组头结构中的指针sem_base指向信号量数组,该数组中的每一个元素都是sem结构的变量,即信号量。

    一个信号量集的结构如下图所示:

    从上图可以看出,信号量集统一有一个进程等待队列,而不是每个信号量都有一个,这正是信号量集的特点。

    进程等待队列结构sem_queue如下:

    struct sem_queue {
    	struct list_head	list;	 /* queue of pending operations */
    	struct task_struct	*sleeper; /* 指向等待进程控制块的指针 */
    	struct sem_undo		*undo;	 /* undo请求操作结构指针 */
    	int    			pid;	 /* 请求操作的进程标识 */
    	int    			status;	 /* 操作完成状态 */
    	struct sembuf		*sops;	 /* 挂起的操作集 */
    	int			nsops;	 /* 操作数目 */
    	int			alter;   /* does the operation alter the array? */
    };

    等待队列是一个由进程控制块所组成的队列,每个进程控制块代表着一个等待进程,sem_queue的域为sleeper指向了该等待队列。

    另外,为了使系统可以从等待进程控制块中得到该进程所在的等待队列,进程控制块task_struct中有一个指向等待队列的指针semsleeping。

    内核管理结构

    Linux系统所有的信号量集都注册在一个数组中,该数组是内核全局数据结构struct ipc_id_ary的一个域。结构struct ipc_id_ary的定义如下:

    struct ipc_id_ary
    {
            int size;
            struct kern_ipc_perm *p[0];            //存放段描述结构的数组
    };

    结构中的数组p[]就是信号量集的注册数组。

    数组p[]暂时只定义了0个元素,数组的长度在系统运行时会在相应的操作里动态地增加或减少。

    为了方便对上述数组进行管理,Linux又定义了一个数组头struct ipc_ids。struct ipc_ids的定义如下:

    struct ipc_ids {
    	int in_use;
    	unsigned short seq;
    	unsigned short seq_max;
    	struct rw_semaphore rw_mutex;
    	struct idr ipcs_idr;
            struct ipc_id_ary *entries;        //指向struct ipc_id_ary的指针
    };
    

    很清楚,域entries就是指向信号量集数组的指针。

    另外需要注意的是,由于为了充分利用内存空间,进程消亡时需要及时释放其所创建的信号量集,所以数组p[]的下标是动态的。这也就意味着以信号量在数组中的位置(下标)来作为标识不唯一,因此在上述结构中有一个叫做序列号的域seq,系统每增加一个信号量集,系统就会将seq加1,然后把信号量集在数组p[]中的下标与之拼接起来形成唯一的标识,以供内核来识别。

    内核对于信号量集的管理结构如下图所示:

     

    信号量集的操作

    信号量集的创建或打开

    进程可以通过调用函数semget()创建或打开一个信号量集,这个函数是通过系统调用sys_semget()来实现的。系统调用sys_semget()的原型如下:

    asmlinkage long sys_semget(key_t key, int nsems, int semflg);

    其中,参数key是用户给定的键值;参数semflg是该函数的功能标志。

    系统调用sys_semget()有两个功能:如果参数semflg的IPC_CREATE的值给定为1,则这个系统调用会为用户创建或打开一个信号量集,并返回信号量集标识符;如果为0,则会在系统已有的信号量集中寻找与键值相同的信号量集,找到后,打开该信号量集并返回信号量集的标识号。参数nsems用来指明在所创建的信号量集中信号量的个数,即定义sem_base指向的数组的大小。

    信号量集的操作

    用于信号量操作的函数是semop()。为了用户的方便,Linux提供了数据结构sembuf,用户在这个数据结构中指明对信号量的操作。sembuf结构定义如下:

    struct sembuf {
    	unsigned short  sem_num;	/* 信号量集在集中的序号 */
    	short		sem_op;		/* 信号量操作 */
    	short		sem_flg;	/* 操作标志 */
    };

    其中,域sem_num指明待操作信号量在集中的位置;域sem_op就是对信号量的增量。通常,在访问共享资源之前,域sem_op应设为-1(对信号量进行减1的P操作);访问之后,设为1(对信号量进行加1的V操作)。

    前面讲过,为了防止产生死锁,信号量集的操作必须对集中的所有信号量同时操作,所以用户还需要定义一个其长度与信号量数目相等的sembuf类型数组,以便把各个信号量的sembuf结构数据存放到对应的元素中。

    函数semop()由系统调用sys_semop()实现,其原型如下:

    asmlinkage long sys_semop(int semid, struct sembuf __user *sops,
    				unsigned nsops);

    其中,参数semid为信号量集的标识;参数sops就是指向上述sembuf数组的指针,数组每个元素都是对应信号量集的操作结构sembuf;参数nspos为这个数组的长度。

    结构undo

    介绍信号量的基本原理时曾经说过,P和V操作必须成对出现。也就是说,对于Linux信号量集,在临界段前要用semop()来请求资源,而在临界段后要用semop()来释放资源,但在具体应用中可能会因进程非正常中止而导致临界段没有机会来释放资源。

    如果有产生这种情况的可能,进程必须将释放资源的任务转交给内核来完成。即在调用semop()请求资源时,把传递给函数的sembuf结构的域sem_flg设置为SEM_UNDO。这样,函数semop()在执行时就会为信号量配置一个sem_undo的结构,并在该结构中记录释放信号量的调整值;然后把信号量集中所有sem_undo组成一个队列,并在等待进程队列中用指针undo指向该队列。

    也就是说,通过设置SEM_UNDO,当进程非正常中止时内核会产生响应操作,以保证信号量处于正常状态。

    结构sem_undo定义如下:

    struct sem_undo {
    	struct list_head	list_proc;	/* per-process list: all undos from one process. */
    						/* rcu protected */
    	struct rcu_head		rcu;		/* rcu struct for sem_undo() */
    	struct sem_undo_list	*ulp;		/* sem_undo_list for the process */
    	struct list_head	list_id;	/* per semaphore array list: all undos for one array */
    	int			semid;		/* 信号量集标识符 */
    	short *			semadj;		/* 存放信号量集调整值的数组指针 */
    };

    于是,当系统执行内核函数do_ext()结束一个进程时,如果sembuf结构中的sem_flg的值为SEM_UNDO,则该函数会扫描该进程的sem_undo队列,并根据每个sem_undo结构中的调整信息,依次调整各个信号量值,以释放各个信号量。

    信号量的控制

    为实现对信号量的初始化等控制,Linux提供了函数semctl()。其对应的内核函数原型如下:

    asmlinkage long sys_semctl(int semid, int semnum, int cmd, union semun arg);

    其中,semid为信号量集的表示;semnum为信号量的数目;cmd为操作命令;arg为信号量的初始值。

    从参数定义中可知,arg的类型为union semun。该类型定义如下:

    union semun {
    	int val;			/* 信号量初始值 */
    	struct semid_ds __user *buf;	/* buffer for IPC_STAT & IPC_SET */
    	unsigned short __user *array;	/* array for GETALL & SETALL */
    	struct seminfo __user *__buf;	/* buffer for IPC_INFO */
    	void __user *__pad;
    };

    内核函数sys_semctl()将根据其命令参数cmd(第三个参数)及参数arg来对信号量集实时控制。

     

    进程控制块中关于信号量集的域

    进程使用信号量集的相关信息也被记录在进程控制块中。进程控制块与信号量及相关的域如下:

    struct task_struct
    {
            ...
            struct sem_undo * semundo;        //指向进程使用的信号量集undo队列
            struct sem_queue * semsleeping;        //指向进程所在等待队列的指针
            ...
    };

    其实就是两个指针:一个指向进程使用的信号量undo队列;另一个指向进程所在的等待队列。

    特别地,信号量集的undo队列被组织在进程控制块和信号量集两个队列中,如下图所示:

     

    展开全文
  • 进程间通信:信号量概念及代码

    千次阅读 2020-03-09 23:12:55
    前言 接下讨论的IPC机制,它们最初由...信号量:用于管理对资源的访问。 共享内存:用于在程序之间高效地共享数据。 消息队列:在程序之间传递数据。 先从这个代码开始思考: #include <stdio.h> #in...

    前言

    接下讨论的IPC机制,它们最初由System V版本的Unix引入。由于这些机制都出现在同一个版本中并且有着相似的编程接口,所以它们被称为System V IPC机制。接下来的内容包括:

    信号量:用于管理对资源的访问。

    共享内存:用于在程序之间高效地共享数据。

    消息队列:在程序之间传递数据。

    操作系统中的同步和异步:https://blog.csdn.net/qq_38289815/article/details/81012826

    进程间通信:管道和命名管道(FIFO)  https://blog.csdn.net/qq_38289815/article/details/104742682

    进程间通信:共享内存  https://blog.csdn.net/qq_38289815/article/details/104776076

    进程间通信:消息队列  https://blog.csdn.net/qq_38289815/article/details/104786412

     

    先从这个代码开始思考:

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h> 
    
    int asd = 0;
    
    int main(int argc, char *argv[])
    {
    	char test_value = 'X';
    	int i = 0;
    	if (argc > 1)
    	{
    		test_value = argv[1][0];
    	}
    
    	for (i = 0; i < 3; ++i)
    	{
    		printf("%d:%c\n", asd,test_value);
    		fflush(stdout);
    		sleep(rand() % 3);
    		printf("%d:%c\n", asd,test_value);
    		asd++;
    		fflush(stdout);
    		sleep(rand() % 2);
    	}
    	
    	sleep(2);
    	printf("\n%d - finished\n", getpid());
    	exit(EXIT_SUCCESS);
    }

    上面的程序展示了两个进程访问同一代码段的情况。有时,不同的程序需要访问同一代码段,但是这个代码段在一个时间点只允许一个进程访问。多个进程随意访问该代码段必然会产生不同的结果。为了解决上述问题,我们需要学习关于信号量的知识。

    补充概念:临界区指的是一个访问共用资源的程序片段,而这些共用资源又无法同时被多个进程或线程访问的特性。每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用。

    进程进入临界区的调度原则是:

    1、如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。

    2、任何时候,处于临界区内的进程不可多于一个。如已有进程进入临界区,则其它所有试图进入临界区的进程必须等待。

    3、进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。

    4、如果进程不能进入临界区,则应让出CPU,避免进程出现"忙等"现象。

     

    信号量

    当我们编写的程序使用了线程时,不管它是运行在多用户系统上、多进程系统上,还是运行在多用户多进程系统上,我们通常会发现,程序中存在着一部分临界代码,需要确保只有一个进程可以进入这个临界代码并拥有对资源独占式的访问权。这里可以简单的理解临界区就是一种独占式的资源,同一时间点只能有一个进程访问它。

    信号量的一个正式的定义:它是一个特殊变量,只允许对它进行等待(wait)和发送信号(signal)这两种操作。因为在Linux编程中,"等待"和"发送信号"都已具有特殊的含义,所以我们将用原先定义的符号来表示这两种操作。

    P(信号量变量):用于等待。
    V(信号量变量):用于发送信号。

    最简单的信号量是只能取值0和1的变量,即二进制信号量。这也是信号量最常见的一种形式。可以取多个整数值的信号量被称为通用信号量。PV操作的定义非常简单。假设有一个信号量变量n。

    P(n):如果n的值大于零,就给它减去1;如果它的值为零,就挂起该进程的执行。

    V(n):如果有其他进程因为等待n而被挂起,就让它恢复运行;如果没有进程因等待n而被挂起,就给它加1。

    还可以这么理解:当临界区资源可用时,信号量变量n的值是true(即二进制信号量),然后P(n)操作将它变成false以表示临界区正在被使用;当进程离开临界区时,使用V(n)操作将它加1,使临界区再次变为可用。

     

    Linux信号量机制

    学习了信号量的含义及其工作原理,接下来看看在Linux系统中是如何实现这些功能的。Linux系统中的信号量接口经过了精心设计,它提供了被通常所需更多的机制。所有的Linux信号量函数都是针对成组的通用信号量进行操作,而不是只针对一个二进制信号量。在一个进程需要锁定多个资源的复杂情况下,这种能够对一组信号量进行操作的能力是一个巨大的优势。

     

    信号量函数的定义如下:

    #include <sys/sem.h>
    
    int semctl(int sem_id, int sem_num, int command, ...);
    int semget(key_t key, int num_sems, int sem_flags);
    int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

    参数key的作用很香一个文件名,它代表程序可能要使用的某个资源。如果多个程序使用相同的key值,它将负责协调工作。与此类似,有semget函数返回的并用在其他共享内存函数中的标识符也与fopen返回的FILE *文件流很相似,进程需要通过它来访问共享文件。此外,类似于文件的使用情况,不同的进程可以用不同的信号量标识符来指向同一个信号量。对于接下来要讨论的IPC机制来说,这种一个键加上一个标识符的用法是很常见的,尽管每个机制都使用独立的键和标识符。

     

    semget函数

    semget函数的作用是创建一个新的信号量或取得一个已有信号量的键。函数原型如下:

    int semget(key_t key, int num_sems, int sem_flags);
    semget()函数成功返回一个相应信号标识符(非零),失败返回-1.

    key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键,再由系统生成一个相应的信号标识符(semget()函数的返回值),只有semget()函数才直接使用信号量键,所有其他的信号量函数使用由semget()函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。

    num_sems指定需要的信号量数目,它的值几乎总是1。

    sem_flags是一组标志,它低端的9个比特是该信号量的权限,其作用类似于文件的访问权限。当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

     

    semop()函数

    semop用于改变信号量的值,函数原型如下:

    int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
    semop调用的一切动作都是一次性完成的,这是为了避免出现因使用多个信号量而可能发生的竞争现象。

    第一个参数sem_id是由semget()返回的信号量标识符。第二个参数sem_ops是指向一个结构数组的指针,每个数组元素至少包含以下几个成员:

    struct sembuf{
        short sem_num;
        short sem_op;
        short sem_flg;
    };

    sem_num是信号量编号,除非使用一组信号量,否则它为0

    sem_op成员的值是信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作, 一个是+1,即V(发送信号)操作。

    sem_flg通常被设置为SEM_UNDO。它将使操作系统跟踪当前进程对这个信号量的修改情况。如果这个进程没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量

     

    semctl()函数

    semctl函数用来直接控制信号量信息,函数原型如下:

    int semctl(int sem_id, int sem_num, int command, ...);
    semctl函数将根据command参数的不同而返回不同的值。对于SETVAL和IPC_RMID,成功时返回0,失败时返回-1。

    sem_id是由semget返回的信号量标识符。

    sem_num参数是信号量编号,当需要用到成组的信号量时,就要用到这个参数,它一般取值为0,表示这是唯一的一个信号量。

    command参数是将要采取的动作。

    如果有第四个参数,它通常是一个union semum结构,定义如下:

    union semun {
        int val;
        struct semid_ds *buf;
        unsigned short *arry;
    };

    这个联合体可由程序员自己定义,如果需要定义该结构体,可以查阅semctl的手册。command可以设置许多不同的值,下面两个值最常用:

    SETVAL:用来把信号量初始化为一个已知的值。这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。

    IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。

     

    使用信号量

    用两个不同字符的输出来表示进入和离开临界区。如果程序启动时带有一个参数,它将在进入和退出临界区时打印字符X;而程序的其他运行实例将在进入和退出临界区时打印字符O。因为在任一给定的时刻,只能有一个进程可以进出入临界区,所以X和O应该是成对出现的。

    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <sys/sem.h>
    
    union semun
    {
      int val;
      struct semid_ds *buf;
      unsigned short *arry;
    };
    
    static int sem_id = 0;
    static int set_semvalue();
    static void del_semvalue();
    static int semaphore_p();
    static int semaphore_v();
    
    int main(int argc, char *argv[])
    {
    	int pause_time;
    	int i;
    	char op_char = 'O';
    	
    	srand((unsigned int)getpid());
    
    	// 创建信号量
    	sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
    
    	if (argc > 1)
    	{// 程序第一次被调用,初始化信号量
    		if (!set_semvalue())
    		{
    			fprintf(stderr, "Failed to initialize semaphore\n");
    			exit(EXIT_FAILURE);
    		}
    		// 设置要输出到屏幕中的信息
    		op_char = 'X';
    		sleep(2);
    	}
    
    	for (i = 0; i < 10; ++i)
    	{
    		// 进入临界区
    		if (!semaphore_p())  exit(EXIT_FAILURE);
    
    		// 向屏幕中输出数据
    		printf("%c", op_char);
    		// 清理缓冲区,然后休眠随机时间
    		fflush(stdout);
    		sleep(pause_time);
    
    		// 离开临界区前再一次向屏幕输出数据
    		printf("%c", op_char);
    		fflush(stdout);
    
    		// 离开临界区,休眠随机时间后继续循环
    		if (!semaphore_v())
    		{
    			exit(EXIT_FAILURE);
    		}
    		pause_time = rand()%2;
    		sleep(pause_time);
    	}
    
    
    	printf("\n%d - finished\n", getpid());
    
    	if (argc > 1)
    	{
    		// 如果程序是第一次被调用,则在退出前删除信号量
    		sleep(10);
    		del_semvalue();
    	}
    	exit(EXIT_SUCCESS);
    }
    
    static int set_semvalue()
    {
    	// 用于初始化信号量,在使用信号量前必须这样做
    	union semun sem_union;
    
    	sem_union.val = 1;
    	if (semctl(sem_id, 0, SETVAL, sem_union) == -1)
    	{
    		return 0;
    	}
    	return 1;
    }
    
    static void del_semvalue()
    {
    	// 删除信号量
    	union semun sem_union;
    
    	if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
    	{
    		fprintf(stderr, "Failed to delete semaphore\n");
    	}
    }
    
    static int semaphore_p()
    {
    	// 对信号量做减1操作,即等待P(sv)
    	struct sembuf sem_b;
    	sem_b.sem_num = 0;
    	sem_b.sem_op = -1;//P()
    	sem_b.sem_flg = SEM_UNDO;
    	if (semop(sem_id, &sem_b, 1) == -1)
    	{
    		fprintf(stderr, "semaphore_p failed\n");
    		return 0;
    	}
    
    	return 1;
    }
    
    static int semaphore_v()
    {
    	// 这是一个释放操作,它使信号量变为可用,即发送信号V(sv)
    	struct sembuf sem_b;
    	sem_b.sem_num = 0;
    	sem_b.sem_op = 1; // V()
    	sem_b.sem_flg = SEM_UNDO;
    	if (semop(sem_id, &sem_b, 1) == -1)
    	{
    		fprintf(stderr, "semaphore_v failed\n");
    		return 0;
    	}
    
    	return 1;
    }

    在程序开始前,使用semget函数通过一个键来取得一个信号量标识符。IPC_CREAT标志的作用是:如果信号量不存在就创建它。如果程序带有一个参数,它就负责信号量的初始化工作,这是通过set_semvalue函数来完成的,该函数是针对更通用的semctl函数的简化接口。程序还将根据是否带有参数来决定需要打印哪个字符。sleep函数的作用是,让我们有时间在这个程序实例执行太多次循环之前调用其他的程序实例。利用srand函数和rand函数来为程序引入一些伪随机形式的时间分配。

    接下来程序循环10次,在临界区和非临界区会分别暂停一段随机的时间。临界区有semaphore_p和semaphore_v前后把守,它们是更通用的semop函数的简化接口。删除信号量之前,带有参数启动的程序会进入等待状态,以允许其他调用实例都执行完毕。如果不删除信号量,它将继续在系统中存在,即使没有程序在使用它也是如此,在实践编程中,我们需要特别小心这一点,不要在程序执行结束后还留有信号量未删除。信号量是一种有限的资源,请节约使用。

    展开全文
  • 公用信号量 私用信号量

    千次阅读 2019-10-15 22:31:23
    公用信号量 私用信号量 公用信号量 用来实现进程间的互斥,初值为1,允许它所联系的一组进程对它执行P/V操作。 私用信号量 用来实现进程间的同步,初值为0或者某个正整数,仅允许拥有它的进程对其执行P/V操作。 ...
  • 【UCOSIII】UCOSIII的信号量

    万次阅读 2018-07-01 20:42:34
    信号量 信号量像是一种上锁机制,代码必须获得对应的钥匙才能继续执行,一旦获得了钥匙,也就意味着该任务具有进入被锁部分代码的权限。一旦执行至被锁代码段,则任务一直等待,直到对应被锁部分代码的钥匙被再次...
  • RT-Thread学习笔记——信号量

    千次阅读 2019-01-25 19:18:12
    本文讲RT-Thread的线程间同步之信号量,包括为什么要进行线程间同步、信号量创建与删除、信号量获取与释放以及基于STM32的二值信号量示例和计算型信号量示例,采用RTT&正点原子联合出品潘多拉开发板进行实验。 ...
  • FreeRTOS系统-二值信号量的使用

    千次阅读 2020-12-26 16:50:17
    FreeRTOS系统-二值信号量的使用 提示:以下文章基于FreeRTOS全部移植完成,能够正常运行. 文章目录FreeRTOS系统-二值信号量的使用前言一、FreeRTOS系统的中断管理?二、使用步骤1.引入库2.读入数据总结 前言 ...
  • 辨析:自旋锁与信号量

    千次阅读 2020-02-05 15:09:51
    信号量(Semaphore): 1. 自旋锁与信号量简介 自旋是锁的一种实现方式,通过忙等待(“自旋,spinning”)来实现【例如通过while循环持续请求获取锁】。 信号量的概念比锁的范围更大, 可以说, 锁是信号量的一种特殊...
  • 操作系统:信号量机制之生产者与消费者实验 实验目的:了解和熟悉linux系统下的信号量集和共享内存。 任务:使用linux系统提供的信号量集和共享内存实现生产者和消费者问题。 实验要求: 写两个程序,一个模拟...
  • 进程间通信之Linux信号量编程

    千次阅读 2018-11-12 16:03:03
    信号量 信号量(Semaphore)是一种用于实现计算机资源共享的IPC机制之一,其本质是一个计数器。信号量是在多进程环境下实现资源互斥访问或共享资源访问的方法,可以用来保证两个或多个关键代码段不被并发调用。在...
  • FreeRTOS信号量

    万次阅读 2018-01-11 22:27:04
    ——————(正点原子FreeRTOS学习笔记) ... 二值信号量通常用于互斥访问或同步,二值信号量和互斥信号量非常类似,但是还是有一些细微的差别,互斥信号量拥有优先级继承机制,二值信号量没有优先级
  • 互斥信号量信号量的理解

    万次阅读 多人点赞 2018-09-17 10:10:24
     至于信号量,和互斥信号量是用区别的,简单来说(个人理解,欢迎纠正)就是互斥信号量再同一时刻,任务得到互斥信号量量后是独占共享资源的,在他没有释放信号量之前,任何其他任务都是不能访问共享资源的。而信号...
  • 操作系统考点之PV操作、信号量

    千次阅读 多人点赞 2020-11-11 23:41:05
    如题:2020年8月 分析: 要明白PV操作,是对信号量进行的操作。什么是信号量呢?含义就是临界资源。当Sem大于等于零时代表可供并发进程使用的资源实体数,但sem小于零时则表示正在等待使用临街区的进程数。 当前值为...
  • 文章目录1. 基本结构2. P,V操作3. 信号量的应用3.1 信号量实现进程互斥3.2 信号量实现前驱关系4....信号量有整形信号量、记录型信号量、AND型信号量等,这里主要介绍我们常见的记录型信号量。 1...
  • 信号量机制

    千次阅读 2018-09-27 11:51:40
    1) 整型信号量 *信号量定义为一个整型量; *根据初始情况赋相应的值; *仅能通过两个原子操作来访问。 P操作 wait(S): While S&lt;=0 do no-op; S:=S-1; V操作 signal(S): S:=S+1; 2)记录型信号量 *整型信号...
  • POSIX信号量进程是3种 IPC(Inter-Process Communication) 机制之一,3种 IPC 机制源于 POSIX.1 的实时扩展。Single UNIX Specification 将3种机制(消息队列,信号量和共享存储)置于可选部分中。在 SUSv4 之...
  • 实验5 用信号量实现进程互斥

    千次阅读 2020-12-18 18:29:38
    实验5 利用信号量实现进程互斥 【实验目的】 (1) 理解互斥概念、信号量机制及信号量结构; (2) 掌握信号量的使用方法; (3) 掌握PV操作的定义; (4) 掌握PV操作实现互斥的方法。 【实验原理/实验基础知识】...
  • 信号量和互斥信号量的理解

    千次阅读 2019-04-09 08:26:11
    信号量和互斥信号量的记住和理解应用是不一样的哦,面试常问。 做下本人理解和参考他人后的笔记 对于互斥锁(Mutex)来说,只要有线程占有了该资源,那么不好意思,其他线程就是优先级再高,您也得等着,等我用完...
  • 【UCOSIII】UCOSIII的任务内嵌信号量

    千次阅读 2018-07-03 18:11:24
    我们一般使用信号量时都需要先创建一个信号量,不过在UCOSIII中每个任务都有自己的内嵌的信号量,这种功能不仅能够简化代码,而且比使用独立的信号量更有效。任务信号量是直接内嵌在UCOSIII中的,任务信号量相关代码...
  • 【UCOSIII】UCOSIII的互斥信号量

    万次阅读 2018-07-02 21:18:54
    信号量用于控制对共享资源的保护,但是现在基本用来做任务同步用(不太清楚的可以参考链接:【UCOSIII】UCOSIII的信号量)。   优先级反转 优先级反转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种...
  • 进程间通信之-信号量semaphore

    万次阅读 2018-03-23 13:41:16
    转载自: https://blog.csdn.net/gatieme/article/details/50994533信号量什么是信号量信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有。信号量的值为正的时候,说明它空闲。所...
  • 记录型信号量(1)举一个生动形象的例子了解记录型信号量(2)梳理一下记录型信号量的知识点(P、V) 0.思维导图 1.为什么引入信号量机制? 为了更好的解决进程互斥与同步的问题 2.什么是信号量机制? 3.整型...
  • 以下代码中前两个是为定义信号量的,后两个是在上次共享内存那里的代码中加入了信号量的控制。 对于 定义几个信号量,每个信号量的初值是多少 ?可以这么考虑,我们需要控制几个点?比如上图中的两个进程,我们...
  • 11、UCOSIII信号量和互斥信号量

    千次阅读 2018-08-30 15:14:40
    1、 信号量 1.1 信号量简介 ①信号量像是一种上锁机制,代码必须获得对应的钥匙才能继续执行,一旦获得了钥匙,也就意味着该任务具有进入被锁部分代码的权限。一旦执行至被锁代码段,则任务一直等待,直到对应被...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 522,387
精华内容 208,954
关键字:

信号量