• 以生活中的停车场为例来理解信号量的概念:

    1. 当停车场空的时候, 停车场的管理员发现有很多空车位, 此时会让外面的车陆续进入停车场获得停车位;
    2. 当停车场的车位满的时候, 管理员发现已经没有空车位, 将禁止外面的车进入停车场, 车辆在外排队等候;
    3. 当停车场内有车离开时, 管理员发现有空的车位让出, 允许外面的车进入停车场; 待空车位填满后, 又禁止外部车辆进入。
  • 在此例子中

    • 管理员手中的空车位就相当于信号量
    • 管理员手中空车位的个数就是信号量的值
    • 停车位相当于公共资源
    • 车辆相当于线程
    • 车辆通过获得管理员的允许取得停车位, 就类似于,线程通过获得信号量访问公共资源
  • 信号量的工作机制

    • 信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。

      image.png

    • 在 RT-Thread 中,信号量控制块是操作系统用于管理信号量的一个数据结构

      • rtos\rt-thread\include\rtdef.h

        #ifdef RT_USING_SEMAPHORE
        /**
         * Semaphore(**信号量**) structure
         *
         * **信号量控制块**
         */
        struct rt_semaphore
        {
            struct rt_ipc_object parent;         /**< inherit(**继承**) from ipc_object(**进[线]程间通信对象**) */
        
            rt_uint16_t          value;          /**< **信号量的值** , 它是uint16_t类型的,所以它的**最大值**为**65535***/
            rt_uint16_t          reserved;       /**< 保留字段 */
        };
        typedef struct rt_semaphore *rt_sem_t;
        #endif
        
        // **定义静态信号量**
        struct rt_semaphore static_sem; **// 结构体类型**
        
        // **定义动态信号量 (后续需要我们自己去给它分配内存空间)**
        rt_sem_t dynamic_sem; **// 指针类型**
        
  • 定义好信号量之后,就需要对信号量进行相应的操作,RT-Thread中提供了一整套对信号量进行操作的API

  • 信号量的操作

    • 初始化与脱离(针对于静态信号量)

      //                                                                        **决定了信号量的值为0的时候,多个线程等待信号量的排列方式
      //                                                                        |**                  
      // **静态信号量的初始化   信号量指针      信号量名称         信号量初始值      信号量标志位————————————> RT_IPC_FLAG_FIFO—————先进先出,先进入线程等待队列的线程将优先获得信号量**
      rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag); // **|———>** **RT_IPC_FLAG_PRIO—————线程将按照优先级的方式排队等候信号量,优先级高的线程将优先获得信号量**
      **// 通过这个函数,可以将静态信号量,加入,系统的,对象管理器**
      
      // **静态信号量的脱离      信号量指针**
      rt_err_t rt_sem_detach(rt_sem_t sem); // **不再使用**这个**静态信号量**的**时**候,用于**将静态信号量从系统的,对象管理器,中移除,释放系统资源**
      
    • 创建与删除(针对于动态信号量)

      // **动态信号量的创建        信号量名称        信号量初始值       信号量标志位————> 1.RT_IPC_FLAG_FIFO**    2.**RT_IPC_FLAG_PRIO**
      rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag); // 需要**动态申请**信号量所占的**内存空间**
      // **创建成功之后**,得到 rt_sem_t dynamic_sem 的**指针**,**需要进行判断**一下,如果 dynamic_sem **不为 NULL**,那么说明**创建成功**了,那么接下来就可以**对新创建的**这个**动态信号量**进行**操作**
      
      // **动态信号量的删除      信号量指针**
      rt_err_t rt_sem_delete(rt_sem_t sem); // **不再使用**这个**动态信号量**的**时**候,用于**将动态信号量从系统的,对象管理器,中移除,释放系统资源**
      
    • 获取信号量

      • 注意:当获取不到信号量的时候,这两个函数会导致线程被挂起,所以这个API只能在线程中去调用,不能在中断(ISR)中去调用

      • 但中断上下文不允许阻塞,因为它没有关联的线程上下文,阻塞会导致系统未定义行为(如死锁或崩溃)

        //               表示**要获取的信号量**
        //                         |
        // **信号量的获取        信号量指针       时间参数     RT_WAITING_FOREVER = -1**
        rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t time)    // **线程**通过**这个API**来**获得信号量**所**指示的共享资源**,当**信号量的值**(sem.value)**大于0**的时候,线程将**成功**的**获得信号量**
        																											 // 获得信号量之后,这个函数将立即返回RT_EOK,并且将**信号量的值**(sem.value)**减一**
        // 当线程调用这个函数获取信号量的时候,如果**信号量的值**(sem.value)**等于0**,说明信号量所指示的**共享资源不可用**
        // 申请该信号量的线程会根据**时间参数**(**time**)来进行相应的动作
        // 当**时间参数**(**time**)大于**0**的时候,会按照时间来进行等待,等到时间了之后会返回
        // 当**时间参数**(**time**)等于**0**的时候,会立即返回
        // **时间参数**(**time**)是以**系统**的**滴嗒时钟**为**计数单位**的
        // 如果**时间参数**(**time**)为负数,那么表示**这个线程**会**永远的**在这个函数里面**等待获取信号量** (负数一般会用 **RT_WAITING_FOREVER = -1** 来定义)
        
      • 非阻塞获取信号量(可以在中断中使用)

        // **信号量的尝试获取**
        rt_err_t rt_sem_trytake(rt_sem_t sem); // 是一个**时间参数**(**time**)为0的rt_sem_take,不做任何等待,只是看是否可以获取到,如果可以获取到会进行返回RT_EOK,如果获取不到会返回-RT_ETIMEOUT
        
    • 释放信号量

      • 既可以在线程中去释放,也可以在中断(ISR)中去释放

      • 非阻塞的原子操作**,通过关中断保护临界区**

        // **信号量的释放           信号量指针**
        rt_err_t rt_sem_release(rt_sem_t sem); // 调用这个函数,可以将信号量所指示的共享资源的数量(**sem.value**)**加一**
        
        // 如果**有线程被**这个信号量**挂起**了,那么这个时候**挂在这个信号量下面**的这个**线程**就会被**唤醒**
        
  • 应用示例

    • 代码
    • 运行结果
    • 运行结果分析