内核信号量

更新时间:
2024-03-14
下载文档

内核信号量

信号量是实现共享资源的互斥访问或者同步访问的一种最常见的方法。

实际上,信号量是一种约定机制:在共享资源的互斥访问中,它约定当一个线程获得信号量(Wait)后,其他的线程不可以再次获得信号量直到信号量被释放(Give);在同步机制中,它约定等待信号量(Take)的线程在收到信号量之前应该处于阻塞状态,直到其他线程发送该信号量(Post)。

通常情况,对信号量只能实施三种操作:创建一个信号量(Create)、等待信号量(Wait)或者挂起(Pend)、释放信号量(Give)或者发送(Post)。

SylixOS 内核信号量包括四种类型:二进制信号量、计数型信号量、互斥型信号量(简称互斥量)和读写信号量。

二进制信号量的取值限定于 FALSE 和 TRUE;而计数型信号量的最小取值为 0,最大取值在创建计数型信号量时决定。

二进制信号量主要应用在以下场合:

  • 有允许线程访问的一个资源,使用二进制信号量作为互斥手段,初始值为 TRUE。
  • 线程或中断通知另一个线程某件事情发生,初始值为 FALSE。

计数型信号量主要应用在以下场合:

  • 有允许线程访问的 n 个资源,使用计数型信号量作为资源剩余计数,初始值为 n。
  • 线程或中断通知另一个线程某种事件发生时,使用计数型信号量作为事件计数,初始值为 0。

二进制信号量

在信号量使用之前必须要创建。SylixOS 提供下面函数来创建一个二进制信号量。

#include <SylixOS.h> 
LW_OBJECT_HANDLE  API_SemaphoreBCreate (CPCHAR               pcName,
                                        BOOL                 bInitValue,
                                        ULONG                ulOption, 
                                        LW_OBJECT_ID        *pulId);             

函数 API_SemaphoreBCreate 原型分析:

  • 此函数返回二进制信号量的句柄,失败时为 NULL 并设置错误号。
  • 参数 pcName 是二进制信号量的名字。
  • 参数 bInitValue 是二进制信号量的初始值( FALSETRUE )。
  • 参数 ulOption 是二进制信号量的创建选项如下表所示。
  • 输出参数 pulId 用于返回二进制信号量的句柄(同返回值),可以为 NULL
宏名含义
LW_OPTION_WAIT_PRIORITY按优先级顺序等待
LW_OPTION_WAIT_FIFO按先入先出顺序等待
LW_OPTION_OBJECT_GLOBAL全局对象
LW_OPTION_OBJECT_LOCAL本地对象

SylixOS 提供了两种信号量等待队列:优先级(LW_OPTION_WAIT_PRIORITY)和 FIFO(LW_OPTION_WAIT_FIFO)。优先级方式则根据线程的优先级从队列中取出符合条件的线程运行;FIFO 方式则根据先入先出的原则从队列中取出符合条件的线程运行。

需要注意的是 LW_OPTION_WAIT_PRIORITY 和 LW_OPTION_WAIT_FIFO 只能二选一,同样 LW_OPTION_OBJECT_GLOBAL 和 LW_OPTION_OBJECT_LOCAL 也只能二选一。

参数 bInitValue 的不同决定了二进制信号量的用途不同,当 bInitValue 的值为 TRUE 时可以用于共享资源的互斥访问。当 bInitValue 的值为 FALSE 时可以用于多个线程之间的同步。

不再需要的二进制信号量可以调用以下函数将其删除,SylixOS 将回收其占用的内核资源(试图使用被删除的二进制信号量将出现未知的错误)。

#include <SylixOS.h> 
ULONG  API_SemaphoreBDelete (LW_OBJECT_HANDLE  *pulId); 

函数 API_SemaphoreBDelete 原型分析:

  1. 此函数成功返回 ERROR_NONE ,失败返回错误号;
  2. 参数 pulId 是二进制信号量的句柄。

一个线程如果需要等待一个二进制信号量,可以调用 API_SemaphoreBPend 函数。需要注意的是,中断服务程序不能调用 API_SemaphoreBPend 函数来等待一个二进制信号量,因为该函数在二进制信号量值为 FALSE 时会阻塞当前执行的任务,而中断服务程序用来处理最紧急的事情,因此是不允许被阻塞的。

#include <SylixOS.h> 
ULONG  API_SemaphoreBPend (LW_OBJECT_HANDLE      ulId, 
                           ULONG                 ulTimeout); 
ULONG  API_SemaphoreBTryPend (LW_OBJECT_HANDLE   ulId);

以上两个函数原型分析:

  • 函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是二进制信号量的句柄。
  • 参数 ulTimeout 是等待的超时时间,单位为时钟节拍 Tick

参数 ulTimeout 除了可以使用数字外还可以使用如下表所示的宏。

宏名含义
LW_OPTION_NOT_WAIT不等待立即退出
LW_OPTION_WAIT_INFINITE永远等待
LW_OPTION_WAIT_A_TICK等待一个时钟嘀嗒
LW_OPTION_WAIT_A_SECOND等待一秒

SylixOS 为二进制信号量等待提供了一种超时机制,当等待的时间超时时立即返回并设置 errno 为 ERROR_THREAD_WAIT_TIMEOUT。

API_SemaphoreBTryPend 是一种无阻塞的信号量等待函数,该函数与 API_SemaphoreBPend 的区别在于,如果二进制信号量创建的初始值为 FALSE,API_SemaphoreBTryPend 会立即退出并返回,而 API_SemaphoreBPend 则会阻塞直至被唤醒。

中断服务程序可以使用 API_SemaphoreBTryPend 函数尝试等待二进制信号量,因为 API_SemaphoreBTryPend 函数在二进制信号量的值为 FALSE 时会立即返回,不会阻塞等待。

释放一个二进制信号量可以使用 API_SemaphoreBPost 或者 API_SemaphoreBRelease 函数。

#include <SylixOS.h> 
ULONG  API_SemaphoreBPost (LW_OBJECT_HANDLE  ulId); 

函数 API_SemaphoreBPost 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是二进制信号量的句柄。
#include <SylixOS.h> 
ULONG  API_SemaphoreBRelease (LW_OBJECT_HANDLE    ulId, 
                              ULONG               ulReleaseCounter, 
                              BOOL               *pbPreviousValue);

函数 API_SemaphoreBRelease 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是二进制信号量的句柄。
  • 参数 ulReleaseCounter 是释放二进制信号量的次数。
  • 输出参数 pbPreviousValue 用于接收原来的二进制信号量状态,可以为 NULL

API_SemaphoreBRelease 函数是一个高级API,当有多个线程等待同一个信号量时,调用该函数可以一次性将它们释放。

调用 API_SemaphoreBClear 函数将清除二进制信号量,这将使二进制信号量的初始值置为 FALSE。

#include <SylixOS.h> 
ULONG  API_SemaphoreBClear (LW_OBJECT_HANDLE  ulId); 

函数 API_SemaphoreBClear 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是二进制信号量的句柄。

调用 API_SemaphoreBFlush 函数将释放等待在指定信号量上的所有线程。

#include <SylixOS.h> 
ULONG  API_SemaphoreBFlush (LW_OBJECT_HANDLE       ulId, 
                            ULONG                 *pulThreadUnblockNum);

函数 API_SemaphoreBFlush 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是二进制信号量的句柄。
  • 输出参数 pulThreadUnblockNum 用于接收被解除阻塞的线程数,可以为 NULL

API_SemaphoreBStatus 函数返回一个有效信号量的状态信息。

#include <SylixOS.h> 
ULONG  API_SemaphoreBStatus (LW_OBJECT_HANDLE        ulId,
                             BOOL                   *pbValue,
                             ULONG                  *pulOption,
                             ULONG                  *pulThreadBlockNum);

函数 API_SemaphoreBStatus 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是二进制信号量的句柄。
  • 输出参数 pbValue 用于接收二进制信号量当前的值( FALSETRUE )。
  • 输出参数 pulOption 用于接收二进制信号量的创建选项。
  • 输出参数 pulThreadBlockNum 用于接收当前阻塞在该二进制信号量的线程数。

调用 API_SemaphoreGetName 函数可以获得指定信号量的名字。

#include <SylixOS.h> 
ULONG  API_SemaphoreGetName (LW_OBJECT_HANDLE  ulId, PCHAR  pcName);

函数 API_SemaphoreGetName 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是二进制信号量的句柄。
  • 输出参数 pcName 是二进制信号量的名字, pcName 应该指向一个大小为 LW_CFG_OBJECT_NAME_SIZE 的字符数组。
#include <SylixOS.h> 
ULONG  API_SemaphoreBPendEx (LW_OBJECT_HANDLE        ulId, 
                             ULONG                   ulTimeout,
                             PVOID                  *ppvMsgPtr);

函数 API_SemaphoreBPendEx 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是二进制信号量的句柄。
  • 参数 ulTimeout 是等待的超时时间,单位为时钟嘀嗒 Tick。
  • 输出参数 ppvMsgPtr (一个空类型指针的指针)用于接收 API_SemaphoreBPostEx 函数传递的消息。
#include <SylixOS.h> 
ULONG  API_SemaphoreBPostEx (LW_OBJECT_HANDLE      ulId, 
                             PVOID                 pvMsgPtr);

函数 API_SemaphoreBPostEx 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是二进制信号量的句柄。
  • 参数 pvMsgPtr 是消息指针(一个空类型的指针,可以指向任意类型的数据),该消息将被传递到 API_SemaphoreBPendEx 函数的输出参数 ppvMsgPtr

API_SemaphoreBPendEx 和 API_SemaphoreBPostEx 函数增加了消息传递功能,通过参数 pvMsgPtr 可以在信号量中传递额外消息。

由于 API_SemaphoreBPendEx 和 API_SemaphoreBPostEx 函数组合已经起到传统 RTOS 的邮箱的作用,所以 SylixOS 没有提供邮箱的API。

以下程序展示了 SylixOS 二进制信号量的使用,内核模块在装载时创建两个线程和一个 SylixOS 二进制信号量,两个线程分别对变量 _G_iCount 进行自增操作和打印,使用 SylixOS 二进制信号量作为访问 _G_iCount 的互斥手段,内核模块在卸载时删除两个线程和释放二进制信号量所占用的系统资源。

#define  __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <module.h>
LW_HANDLE           _G_hThreadAId = LW_OBJECT_HANDLE_INVALID;
LW_HANDLE           _G_hThreadBId = LW_OBJECT_HANDLE_INVALID;
static INT          _G_iCount     = 0;
static LW_HANDLE    _G_hSemB;
static PVOID  tTestA (PVOID pvArg)
{
    INT  iError;
    while (1) {
        iError = API_SemaphoreBPend(_G_hSemB, LW_OPTION_WAIT_INFINITE);
        if (iError != ERROR_NONE) {
            break;
        }
        _G_iCount++;
        printk("tTestA(): count = %d\n", _G_iCount);
        API_SemaphoreBPost(_G_hSemB);
        API_TimeMSleep(500);
    }
    return  (LW_NULL);
}
static PVOID  tTestB (PVOID pvArg)
{
    INT  iError;
    while (1) {
        iError = API_SemaphoreBPend(_G_hSemB, LW_OPTION_WAIT_INFINITE);
        if (iError != ERROR_NONE) {
            break;
        }
        _G_iCount++;
        printk("tTestB(): count = %d\n", _G_iCount);
        API_SemaphoreBPost(_G_hSemB);
        API_TimeMSleep(500);
    }
    return  (LW_NULL);
}
VOID  module_init (VOID)
{
    printk("SemaphoreB_module init!\n");
    _G_hSemB = API_SemaphoreBCreate("SemaphoreB",
                                    LW_TRUE,
                                    LW_OPTION_WAIT_FIFO |
                                    LW_OPTION_OBJECT_LOCAL,
                                    LW_NULL);
    if (_G_hSemB == LW_OBJECT_HANDLE_INVALID) {
        printk("SemaphoreB create failed.\n");
        return;
    }
    _G_hThreadAId = API_ThreadCreate("t_testa", tTestA, LW_NULL, LW_NULL);
    if (_G_hThreadAId == LW_OBJECT_HANDLE_INVALID) {
        printk("t_testa create failed.\n");
        return;
    }
    _G_hThreadBId = API_ThreadCreate("t_testb", tTestB, LW_NULL, LW_NULL);
    if (_G_hThreadBId == LW_OBJECT_HANDLE_INVALID) {
        printk("t_testb create failed.\n");
        return;
    }
}
VOID  module_exit (VOID)
{
    if (_G_hThreadAId != LW_OBJECT_HANDLE_INVALID) {
        API_ThreadDelete(&_G_hThreadAId, LW_NULL);
    }
    if (_G_hThreadBId != LW_OBJECT_HANDLE_INVALID) {
        API_ThreadDelete(&_G_hThreadBId, LW_NULL);
    }
    API_SemaphoreBDelete(&_G_hSemB);
    printk("SemaphoreB_module exit!\n");
}

在 SylixOS Shell 下装载模块:

#insmod ./SemaphoreB.ko
SemaphoreB_module init!
module SemaphoreB.ko register ok, handle: 0x13338f0
tTestA(): count = 1
tTestB(): count = 2
tTestA(): count = 3
tTestB(): count = 4
tTestA(): count = 5

在 SylixOS Shell 下卸载模块:

#rmmod SemaphoreB.ko
SemaphoreB_module exit!
module /lib/modules/SemaphoreB.ko unregister ok.

计数型信号量

计数型信号量通常用于多个线程共享使用某资源。

一个 SylixOS 计数型信号量可以调用 API_SemaphoreCCreate 函数进行创建,如果创建成功将返回一个计数型信号量的句柄。

#include <SylixOS.h> 
LW_OBJECT_HANDLE  API_SemaphoreCCreate (CPCHAR                pcName,
                                        ULONG                 ulInitCounter, 
                                        ULONG                 ulMaxCounter,
                                        ULONG                 ulOption,
                                        LW_OBJECT_ID         *pulId);

函数 API_SemaphoreCCreate 原型分析:

  • 此函数成功返回计数型信号量的句柄,失败返回 NULL 并设置错误号。
  • 参数 pcName 是计数型信号量的初始值。
  • 参数 ulInitCounter 是计数型信号量的初始值。
  • 参数 ulMaxCounter 是计数型信号量的最大值。
  • 参数 ulOption 是计数型信号量的创建选项如下表所示。
  • 输出 pulId 参数返回计数型信号量的 ID (同返回值),可以为 NULL

计数型信号量的取值范围为 0<= 计数值( ulInitCounter )< ulMaxCounter 。特殊地,如果 ulInitCounter 的值为 0,则可以应用于多个线程之间的同步。

一个不再使用的计数型信号量,可以调用以下函数将其删除。删除后的信号量系统自动回收其占用的系统资源(试图使用被删除的计数型信号量将出现未知的错误)。

宏名含义
LW_OPTION_WAIT_PRIORITY按优先级顺序等待
LW_OPTION_WAIT_FIFO按先入先出顺序等待
LW_OPTION_OBJECT_GLOBAL全局对象
LW_OPTION_OBJECT_LOCAL本地对象
#include <SylixOS.h> 
ULONG  API_SemaphoreCDelete (LW_OBJECT_HANDLE  *pulId);
  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pulId 是计数型信号量的句柄。

设备驱动如果需要等待一个计数型信号量,可以调用 API_SemaphoreCPend 函数,需要注意的是,中断服务程序不能调用 API_SemaphoreCPend 函数等待一个计数型信号量,因为 API_SemaphoreCPend 函数在计数型信号量值为 0 时会阻塞当前线程。

#include <SylixOS.h> 
ULONG  API_SemaphoreCPend (LW_OBJECT_HANDLE  ulId, ULONG  ulTimeout); 
ULONG  API_SemaphoreCTryPend (LW_OBJECT_HANDLE  ulId);

以上两个函数原型分析:

  • 函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是计数型信号量的句柄。
  • 参数 ulTimeout 是等待的超时时间,单位为时钟嘀嗒 Tick

API_SemaphoreCPend 和 API_SemaphoreCTryPend 的区别在于,如果计数型信号量当前的值为 0,API_SemaphoreCTryPend 会立即退出,并返回 ERROR_THREAD_WAIT_TIMEOUT,而 API_SemaphoreCPend 则会阻塞直到被唤醒。

中断服务程序可以使用 API_SemaphoreCTryPend 函数尝试等待计数型信号量,API_SemaphoreCTryPend 函数在计数型信号量的值为 0 时会立即返回,不会阻塞当前线程。

释放一个计数型信号量可以调用 API_SemaphoreCPost 函数。

#include <SylixOS.h> 
ULONG  API_SemaphoreCPost (LW_OBJECT_HANDLE  ulId);

函数 API_SemaphoreCPost 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是计数型信号量的句柄。

一次释放计数型信号量的多个计数可以调用 API_SemaphoreCRelease 函数。

#include <SylixOS.h> 
ULONG  API_SemaphoreCRelease (LW_OBJECT_HANDLE       ulId, 
                              ULONG                  ulReleaseCounter, 
                              ULONG                 *pulPreviousCounter);

函数 API_SemaphoreCRelease 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是计数型信号量的句柄。
  • 参数 ulReleaseCounter 是释放计数型信号量的次数。
  • 输出参数 pulPreviousCounter 用于接收原来的信号量技术,可以为 NULL

调用 API_SemaphoreCClear 函数将清除计数型信号量,这将使计数型信号量的初始值置为 0。

#include <SylixOS.h> 
ULONG  API_SemaphoreCClear (LW_OBJECT_HANDLE  ulId);

函数 API_SemaphoreCClear 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是计数型信号量的句柄。

调用 API_SemaphoreCFlush 函数将释放等待在指定计数型信号量上的所有线程。

#include <SylixOS.h> 
ULONG  API_SemaphoreCFlush (LW_OBJECT_HANDLE       ulId, 
                            ULONG                  *pulThreadUnblockNum);

函数 API_SemaphoreCClear 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是计数型信号量的句柄。
  • 输出参数 pulThreadUnblockNum 用于接收被解除阻塞的线程数,可以为 NULL

以下两个函数可以获得指定计数型信号量的状态信息。

#include <SylixOS.h> 
ULONG  API_SemaphoreCStatus (LW_OBJECT_HANDLE        ulId,
                             ULONG                  *pulCounter,
                             ULONG                  *pulOption,
                             ULONG                  *pulThreadBlockNum); 
ULONG  API_SemaphoreCStatusEx (LW_OBJECT_HANDLE        ulId,
                               ULONG                  *pulCounter,
                               ULONG                  *pulOption,
                               ULONG                  *pulThreadBlockNum,
                               ULONG                  *pulMaxCounter);

以上两个函数原型分析:

  • 以上两个函数均返回错误号。
  • 参数 ulId 是计数型信号量的句柄。
  • 输出参数 pulCounter 用于接收计数型信号量当前的值。
  • 输出参数 pulOption 用于接收计数型信号量的创建选项。
  • 输出参数 pulThreadBlockNum 用于接收当前阻塞在该计数型信号量的线程数。
  • 输出参数 pulMaxCounter 用于接收该计数型信号量的最大计数值。

API_SemaphoreGetName 函数可以获得指定计数型信号量的名字。

#include <SylixOS.h> 
ULONG  API_SemaphoreGetName (LW_OBJECT_HANDLE  ulId, PCHAR  pcName);

函数 API_SemaphoreGetName 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是计数型信号量的句柄。
  • 输出参数 pcName 是计数型信号量的名字, pcName 应该指向一个大小为 LW_CFG_OBJECT_NAME_SIZE 的字符数组。

以下程序展示了 SylixOS 计数型信号量的使用,内核模块在装载时创建两个线程和一个 SylixOS 计数型信号量;计数型信号量作为资源剩余计数,资源的初始数目为 5,最大数目为 100;线程 A 是资源的消费者,线程 B 是资源的生产者,内核模块在卸载时删除两个线程和释放计数型信号量所占用的系统资源。

#define  __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <module.h>
LW_HANDLE           _G_hThreadAId = LW_OBJECT_HANDLE_INVALID;
LW_HANDLE           _G_hThreadBId = LW_OBJECT_HANDLE_INVALID;
static LW_HANDLE    _G_hSemC;
static PVOID  tTestA (PVOID pvArg)
{
    INT  iError;
    while (1) {
        iError = API_SemaphoreCPend(_G_hSemC, LW_OPTION_WAIT_INFINITE);
        if (iError != ERROR_NONE) {
            break;
        }
        printk("tTestA(): get a resource\n");
    }
    return  (LW_NULL);
}
static PVOID  tTestB (PVOID pvArg)
{
    while (1) {
        API_TimeSSleep(1);
        API_SemaphoreCPost(_G_hSemC);
    }
    return  (LW_NULL);
}
VOID  module_init (VOID)
{
    printk("SemaphoreC_module init!\n");
    _G_hSemC = API_SemaphoreCCreate("SemaphoreC",
                                    5,
                                    100,
                                    LW_OPTION_WAIT_FIFO |
                                    LW_OPTION_OBJECT_LOCAL,
                                    LW_NULL);
    if (_G_hSemC == LW_OBJECT_HANDLE_INVALID) {
        printk("SemaphoreC create failed.\n");
        return;
    }
    _G_hThreadAId = API_ThreadCreate("t_testa", tTestA, LW_NULL, LW_NULL);
    if (_G_hThreadAId == LW_OBJECT_HANDLE_INVALID) {
        printk("t_testa create failed.\n");
        return;
    }
    _G_hThreadBId = API_ThreadCreate("t_testb", tTestB, LW_NULL, LW_NULL);
    if (_G_hThreadBId == LW_OBJECT_HANDLE_INVALID) {
        printk("t_testb create failed.\n");
        return;
    }
}
VOID  module_exit (VOID)
{
    if (_G_hThreadAId != LW_OBJECT_HANDLE_INVALID) {
        API_ThreadDelete(&_G_hThreadAId, LW_NULL);
    }
    if (_G_hThreadBId != LW_OBJECT_HANDLE_INVALID) {
        API_ThreadDelete(&_G_hThreadBId, LW_NULL);
    }
    API_SemaphoreCDelete(&_G_hSemC);
    printk("SemaphoreC_module exit!\n");
}

在 SylixOS Shell 下装载模块:

#insmod ./SemaphoreC.ko
SemaphoreC_module init!
module SemaphoreC.ko register ok, handle: 0x13338f0
tTestA(): get a resource
tTestA(): get a resource
tTestA(): get a resource
tTestA(): get a resource
tTestA(): get a resource
tTestA(): get a resource

在 SylixOS Shell 下卸载模块:

#rmmod SemaphoreC.ko
SemaphoreC_module exit!
module /lib/modules/SemaphoreC.ko unregister ok.

互斥信号量

二进制信号量创建时设置参数 bInitValue 为 TRUE,可以用于互斥访问共享资源。实际上,SylixOS 的二进制信号量实现的互斥性是将一个变量初始化标记为 1,等待信号量(Wait)时将该变量减 1(此时等于 0),如果另一个线程再次等待该信号量将阻塞,直到该信号量被释放(变量加 1),这样就实现了共享资源的互斥访问。

互斥信号量用于共享资源需要互斥访问的场合,可以理解为初始值为 TRUE 的带优先级天花板和优先级继承机制(意在解决优先级反转问题)的二进制信号量。因为互斥信号量需要记录拥有者线程和调整优先级,所以中断服务程序不能等待和释放互斥信号量,并且只有互斥信号量的拥有者线程才有权释放互斥信号量。

一个 SylixOS 互斥信号量必须要调用 API_SemaphoreMCreate 函数创建之后才能使用,如果创建成功,该函数将返回一个互斥信号量的句柄。

#include <SylixOS.h> 
LW_OBJECT_HANDLE  API_SemaphoreMCreate(CPCHAR               pcName,
                                       UINT8                ucCeilingPriority,
                                       ULONG                ulOption,
                                       LW_OBJECT_ID        *pulId);

函数 API_SemaphoreMCreate 原型分析:

  • 此函数成功返回互斥信号量的句柄,失败返回 NULL 并设置错误号。
  • 参数 pcName 是互斥信号量的名字。
  • 参数 ucCeilingPriority 在使用优先级天花板算法时有效,此参数为天花板优先级。
  • 参数 ulOption 是互斥信号量的创建选项。
  • 输出参数 pulId 返回互斥信号量的句柄(同返回值),可以为 NULL

创建选项包含了二进制信号量的创建选项,此外还可以使用如下表所示的互斥信号量特有的创建选项。

宏名含义
LW_OPTION_INHERIT_PRIORITY优先级继承算法
LW_OPTION_PRIORITY_CEILING优先级天花板算法
LW_OPTION_NORMAL递归加锁时不检查(不推荐)
LW_OPTION_ERRORCHECK递归加锁时报错
LW_OPTION_RECURSIVE支持递归加锁

需要注意,LW_OPTION_INHERIT_PRIORITY 和 LW_OPTION_PRIORITY_CEILING 只能二选一,同样 LW_OPTION_NORMAL 和 LW_OPTION_ERRORCHECK 以及 LW_OPTION_RECURSIVE 只能三选一。

一个不再使用的互斥信号量,可以调用以下函数将其删除。删除后的信号量系统自动回收其占用的系统资源(试图使用被删除的互斥信号量将出现未知的错误)。

#include <SylixOS.h> 
ULONG  API_SemaphoreMDelete(LW_OBJECT_HANDLE  *pulId); 

函数 API_SemaphoreMDelete 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pulId 是互斥信号量的句柄。

线程如果需要等待一个互斥信号量,可以调用 API_SemaphoreMPend 函数。释放一个互斥信号量使用 API_SemaphoreMPost 函数。

#include <SylixOS.h> 
ULONG  API_SemaphoreMPend(LW_OBJECT_HANDLE      ulId,
                          ULONG                 ulTimeout);

函数 API_SemaphoreMPend 原型分析:

  • 此函数成功返回 ERROR_NONE,失败返回错误号。
  • 参数 ulId 是互斥信号量的句柄。
  • 参数 ulTimeout 是等待的超时时间,单位为时钟嘀嗒 Tick
#include <SylixOS.h> 
ULONG  API_SemaphoreMPost(LW_OBJECT_HANDLE  ulId); 

函数 API_SemaphoreMPost 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是互斥信号量的句柄。

需要注意的是,只有互斥信号量的拥有者才能释放该互斥信号量。

下面两个函数可以获得互斥信号量的状态信息。

#include <SylixOS.h> 
ULONG  API_SemaphoreMStatus(LW_OBJECT_HANDLE       ulId,
                           BOOL                   *pbValue,
                           ULONG                  *pulOption,
                           ULONG                  *pulThreadBlockNum); 
ULONG  API_SemaphoreMStatusEx(LW_OBJECT_HANDLE     ulId,
                              BOOL                *pbValue,
                              ULONG               *pulOption,
                              ULONG               *pulThreadBlockNum,
                              LW_OBJECT_HANDLE    *pulOwnerId); 

以上两个函数原型分析:

  • 函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是互斥信号量的句柄。
  • 输出参数 pbValue 用于接收互斥信号量当前的状态。
  • 输出参数 pulOption 用于接收互斥信号量的创建选项。
  • 输出参数 pulThreadBlockNum 用于接收当前阻塞在该互斥信号量的线程数。
  • 输出参数 pulOwnerId 用于接收当前拥有该互斥信号量的线程的句柄。

如果想获得一个互斥信号量的名字,可以调用一下函数。

#include <SylixOS.h> 
ULONG  API_SemaphoreGetName (LW_OBJECT_HANDLE  ulId, PCHAR  pcName); 

函数 API_SemaphoreGetName 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是互斥信号量的句柄。
  • 输出参数 pcName 是互斥信号量的名字, pcName 应该指向一个大小为 LW_CFG_OBJECT_NAME_SIZE 的字符数组。

以下程序展示了 SylixOS 互斥信号量的使用,内核模块在装载时创建了两个不同优先级的线程和一个 SylixOS 互斥信号量,两个线程分别对变量 _G_iCount 进行自增操作和打印,使用 SylixOS 互斥信号量作为访问变量 _G_iCount 的互斥手段,其中互斥信号量使用优先级继承算法,内核模块在卸载时删除两个线程和释放互斥信号量所占用的系统资源。

#define  __SYLIXOS_KERNEL
#include <SylixOS.h>
#include <module.h>
LW_HANDLE           _G_hThreadAId = LW_OBJECT_HANDLE_INVALID;
LW_HANDLE           _G_hThreadBId = LW_OBJECT_HANDLE_INVALID;
static INT          _G_iCount     = 0;
static LW_HANDLE   _G_hSemM;
static PVOID  tTestA (PVOID pvArg)
{
    INT  iError;
    while (1) {
        iError = API_SemaphoreMPend(_G_hSemM, LW_OPTION_WAIT_INFINITE);
        if (iError != ERROR_NONE) {
            break;
        }
        _G_iCount++;
        printk("tTestA(): count = %d\n", _G_iCount);
        API_SemaphoreMPost(_G_hSemM);
        API_TimeMSleep(500);
    }
    return  (LW_NULL);
}
static PVOID  tTestB (PVOID pvArg)
{
    INT  iError;
    while (1) {
        iError = API_SemaphoreMPend(_G_hSemM, LW_OPTION_WAIT_INFINITE);
        if (iError != ERROR_NONE) {
            break;
        }
        _G_iCount++;
        printk("tTestB(): count = %d\n", _G_iCount);
        API_SemaphoreMPost(_G_hSemM);
        API_TimeMSleep(500);
    }
    return  (LW_NULL);
}
VOID  module_init (VOID)
{
    LW_CLASS_THREADATTR  threadattr;
    printk("SemaphoreM_module init!\n");
    _G_hSemM = API_SemaphoreMCreate("SemaphoreM",
                                           LW_PRIO_HIGH,
                                           LW_OPTION_WAIT_FIFO |
                                           LW_OPTION_OBJECT_LOCAL |
                                           LW_OPTION_INHERIT_PRIORITY |
                                           LW_OPTION_ERRORCHECK,
                                           LW_NULL);
    if (_G_hSemM == LW_OBJECT_HANDLE_INVALID) {
        printk("SemaphoreM create failed.\n");
        return;
    }
    API_ThreadAttrBuild(&threadattr,
                            4 * LW_CFG_KB_SIZE,
                            LW_PRIO_NORMAL - 1,
                            LW_OPTION_THREAD_STK_CHK,
                            LW_NULL);
    _G_hThreadAId = API_ThreadCreate("t_testa", tTestA, &threadattr, LW_NULL);
    if (_G_hThreadAId == LW_OBJECT_HANDLE_INVALID) {
        printk("t_testa create failed.\n");
        return;
    }
    API_ThreadAttrBuild(&threadattr,
                            4 * LW_CFG_KB_SIZE,
                            LW_PRIO_NORMAL,
                            LW_OPTION_THREAD_STK_CHK,
                            LW_NULL);
    _G_hThreadBId = API_ThreadCreate("t_testb", tTestB, &threadattr, LW_NULL);
    if (_G_hThreadBId == LW_OBJECT_HANDLE_INVALID) {
        printk("t_testb create failed.\n");
        return;
    }
}
VOID  module_exit (VOID)
{
    if (_G_hThreadAId != LW_OBJECT_HANDLE_INVALID) {
        API_ThreadDelete(&_G_hThreadAId, LW_NULL);
    }
    if (_G_hThreadBId != LW_OBJECT_HANDLE_INVALID) {
        API_ThreadDelete(&_G_hThreadBId, LW_NULL);
    }
    API_SemaphoreMDelete(&_G_hSemM);
    printk("SemaphoreM_module exit!\n");
}

在 SylixOS Shell 下装载模块:

#insmod ./SemaphoreM.ko
SemaphoreM_module init!
module SemaphoreM.ko register ok, handle: 0x13338f0
tTestA(): count = 1
tTestB(): count = 2
tTestA(): count = 3
tTestB(): count = 4
tTestA(): count = 5
tTestB(): count = 6

在 SylixOS Shell 下卸载模块:

#rmmod  SemaphoreM.ko
SemaphoreM_module exit!
module /lib/modules/SemaphoreM.ko unregister ok.

读写信号量

当出现多个读者,单个写者的情况时,单纯地使用互斥信号量将极大地减弱多线程操作系统的处理性能。为了满足这种高并发的处理速度问题,SylixOS 引入了读写信号量。

一个 SylixOS 读写信号量必须要调用 API_SemaphoreRWCreate 函数创建之后才能使用,如果创建成功,该函数将返回一个读写信号量的句柄。

#include <SylixOS.h> 
LW_OBJECT_HANDLE  API_SemaphoreRWCreate (CPCHAR                 pcName,
                                                 ULONG                  ulOption,
                                                 LW_OBJECT_ID        *pulId); 

函数 API_SemaphoreRWCreate 原型分析:

  • 此函数成功返回读写信号量的句柄,失败返回 NULL 并设置错误号。
  • 参数 pcName 是读写信号量的名字。
  • 参数 ulOption 是读写信号量的创建选项。
  • 输出参数 pulId 返回读写信号量的句柄(同返回值),可以为 NULL

一个不再使用的读写信号量,可以调用一下函数将其删除。删除后的信号量系统自动回收其占用的系统资源(试图使用被删除的读写信号量将出现未知的错误)。

#include <SylixOS.h> 
ULONG  API_SemaphoreRWDelete (LW_OBJECT_HANDLE  *pulId); 

函数 API_SemaphoreRWDelete 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 pulId 是读写信号量的句柄。

线程如果需要等待一个读信号量,可以调用 API_SemaphoreRWPendR 函数,如果需要等待一个写信号量,可以调用 API_SemaphoreRWPendW 函数。释放一个读写信号量使用 API_SemaphoreRWPost 函数。

#include <SylixOS.h> 
ULONG  API_SemaphoreRWPendR (LW_OBJECT_HANDLE  ulId, ULONG  ulTimeout); 

函数 API_SemaphoreRWPendR 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是读信号量的句柄。
  • 参数 ulTimeout 是等待的超时时间,单位为时钟嘀嗒 Tick
#include <SylixOS.h> 
ULONG  API_SemaphoreRWPendW (LW_OBJECT_HANDLE  ulId, ULONG  ulTimeout); 

函数 API_SemaphoreRWPendW 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是写信号量的句柄。
  • 参数 ulTimeout 是等待的超时时间,单位为时钟嘀嗒 Tick
#include <SylixOS.h> 
ULONG  API_SemaphoreRWPost (LW_OBJECT_HANDLE  ulId); 

函数 API_SemaphoreRWPost 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是读写信号量的句柄。

SylixOS 读写信号量满足写优先的原则,也就是说,如果已经存在写信号量,则不能再申请读信号量,直到写信号量被释放。但是当已经存在读信号量时,可以再次请求读信号量。这种机制满足了读的高并发性。

需要注意的是,只有读写信号量的拥有者才能释放该读写信号量。

调用 API_SemaphoreRWStatus 函数可以获得读写信号量的详细信息:

#include <SylixOS.h> 
ULONG  API_SemaphoreRWStatus (LW_OBJECT_HANDLE        ulId,
                              ULONG                  *pulRWCount,
                              ULONG                  *pulRPend,
                              ULONG                  *pulWPend,
                              ULONG                  *pulOption,
                              LW_OBJECT_HANDLE       *pulOwnerId); 

函数 API_SemaphoreRWStatus 原型分析:

  • 此函数成功返回 ERROR_NONE ,失败返回错误号。
  • 参数 ulId 是读写信号量的句柄。
  • 参数 pulRWCount 返回当前有多少线程正在并发操作读写信号量,此参数可以为 NULL。
  • 参数 pulRPend 返回当前读操作阻塞的数量,此参数可以为 NULL
  • 参数 pulWPend 返回当前写操作阻塞的数量,此参数可以为 NULL
  • 参数 pulOption 返回当前读写信号量的选项参数,此参数可以为 NULL
  • 参数 pulOwnerId 返回当前写信号量的拥有者 ID,此参数可以为 NULL
文档内容是否对您有所帮助?
有帮助
没帮助