SylixOS同步互斥之二进制信号量(二)

1. 基本作用

上一章节我们介绍了互斥锁,用于“共享资源”的互斥访问,在驱动开发中我们有时候需要等待某种资源准备好之后才能继续执行代码,这就需要使用同步机制来实现这个目的。比如某个应用想要读取AD转换数据,但是这时候转换还未完成,那么就有两种基本处理方法:一是轮询某个寄存器直到AD转换完成;二是将当前线程阻塞,等待AD转换完成后在中断处理中唤醒。第一种方法由于一直在轮询寄存器,cpu的占用率比较高,其他线程得不到运行,cpu利用率较低;第二种方法由于线程阻塞住之后系统可以调度其他的线程运行,cpu利用率较高,但是需要系统提供相应的同步机制。

多个线程可以等待同一个二进制信号量,唤醒的时候根据实际需要可以唤醒优先级最高的线程或者唤醒所有阻塞的线程。二进制信号量详细使用方法在《SylixOS应用开发手册》的7.3.1章节有介绍,这里只介绍基本用法和使用过程中的一些注意事项或者比较重要的东西。

2. 二进制信号量相关接口

2.1 创建二进制信号量

类似于互斥锁,二进制信号量在使用之前也需要先进行创建,这是通过API_SemaphoreBCreate 接口实现的,如下所示。

LW_API LW_OBJECT_HANDLE API_SemaphoreBCreate(CPCHAR             pcName,
                                             BOOL               bInitValue,
                                             ULONG              ulOption,
                                             LW_OBJECT_ID      *pulId); /*  建立二进制信号量            */
  • pcName:二进制信号量也可以设置一个名字,在调试的时候很有用。
  • bInitValue:二进制信号量顾名思义它的状态只有两种,0或者1。初始化的时候可以设置初始状态值为0或者1,二进制信号量用于同步时初始值需要设置为0,设置为1时二进制信号量可以当作互斥锁来使用,但是由于二进制信号量用作互斥时不能解决优先级翻转问题,所以这种用法一般不怎么常用。
  • ulOption:和互斥锁类似,二进制信号量也有一些属性可以设置,其中也包括LW_OPTION_OBJECT_GLOBAL 这个属性,其作用和意义对于二进制信号量和互斥锁是一样的。
  • pulId:和互斥锁类似,可以通过此出参保存二进制信号量句柄。

成功返回二进制信号量句柄,失败返回错误码。

2.2 阻塞等待二进制信号量

当资源没有准备好时,一般情况下需要将当前线程阻塞住来等待别的线程或者中断来唤醒,这是通过API_SemaphoreBPend 接口来实现的,如下所示。

LW_API ULONG            API_SemaphoreBPend(LW_OBJECT_HANDLE  ulId, 
                                           ULONG             ulTimeout);/*  等待二进制信号量            */
  • ulId:二进制信号量句柄。
  • ulTimeout:等待时间。和互斥锁一样,等待时间单位是tick,如果想一直等待,也是通过LW_OPTION_WAIT_INFINITE 来设置。

成功返回0,失败返回错误码,比如等待超时会返回ERROR_THREAD_WAIT_TIMEOUT。调用此接口时会将二进制信号量的值减去1,如果减之前值已经是0的话,就会将当前线程阻塞住,这也是为啥我们在初始化的时候需要将二进制信号量初始值设置为0的原因,因为只有这样设置调用此接口的线程才能真正的阻塞住。

2.3 唤醒二进制信号量

当资源准备好之后,就可以唤醒通知阻塞在二进制信号量上的线程,表示资源已经准备好了,快来使用。根据唤醒的需求不同,可以分为唤醒优先级最高的线程和全部唤醒两种方式,下面分别作介绍。

2.3.1 唤醒单个线程

如果只需要唤醒所有阻塞线程中优先级最高的那个线程的话,则可以使用API_SemaphoreBPost 来实现,如下所示。

LW_API ULONG            API_SemaphoreBPost(LW_OBJECT_HANDLE  ulId);     /*  RT 释放信号量               */
  • ulId:二进制信号量句柄。

成功返回0,失败返回错误码。如果没有线程阻塞在此二进制信号量上时,调用此接口会将二进制信号量的值恢复为1。

2.3.2 唤醒全部线程

如果需要将阻塞在二进制信号量上的所有线程都唤醒的话,可以使用API_SemaphoreBFlush 接口来实现,如下所示。

LW_API ULONG            API_SemaphoreBFlush(LW_OBJECT_HANDLE  ulId, 
                                            ULONG            *pulThreadUnblockNum);
                                                                        /*  解锁所有等待线程            */
  • ulId:二进制信号量句柄。
  • pulThreadUnblockNum:这个参数用于保存将要被唤醒的线程的数量,如果不关心的话,可以设置为空。

成功返回0,失败返回错误码。

2.4 销毁二进制信号量

当二进制信号量使用完毕之后,需要进行销毁以回收资源,这是通过API_SemaphoreBDelete 接口实现的,如下所示。

LW_API ULONG            API_SemaphoreBDelete(LW_OBJECT_HANDLE  *pulId); /*  删除二进制信号量            */
  • pulId:和互斥锁类似,此参数为要销毁的二进制信号量句柄变量的地址。

成功返回0,失败返回错误码。

3. 接口使用示例

这里用伪代码来说明上述接口的基本使用方法,详细的使用请参考应用开发手册。

LW_OBJECT_HANDLE sync_handle;

1. 创建二进制信号量,并将初始值设置为0,表示用作同步
sync_handle= API_SemaphoreBCreate("sync",
                                  LW_FALSE,
                                  LW_OPTION_WAIT_FIFO | LW_OPTION_OBJECT_GLOBAL,
                                  LW_NULL);

2. 当资源未准备好时,阻塞等待二进制信号量
API_SemaphoreBPend(sync_handle, LW_OPTION_WAIT_INFINITE);

3. 当资源准备好后,唤醒等待二进制信号量的线程
API_SemaphoreBPost(sync_handle);
或者
API_SemaphoreBFlush(sync_handle, NULL);

4. 使用完毕后,销毁二进制信号量
API_SemaphoreBDelete(&sync_handle);

关于二进制信号量还有其他一些接口,具体可以参见内核源码SylixOS/kernel/include/k_api.h。

4. 注意事项

  • 和互斥锁不一样的是,二进制信号量的等待和唤醒这两个接口一般是在不同的线程中调用的
  • 唤醒接口可以在中断上下文环境中使用,但是等待接口不能在中断中使用
上一篇:前端js实现微信心形相册拖拽


下一篇:Zeebe API gRPC SaaS