第8章 信号量与互斥锁

这一章的核心是:LiteOS 中任务之间如何同步,以及多个任务访问同一个临界资源时如何避免冲突。


一、本章总体框架

第八章节可以分成两大部分:

  1. 信号量
    • 信号量基本概念;
    • 二值信号量;
    • 计数信号量;
    • 信号量创建、获取、释放、删除;
    • 二值信号量同步实验;
    • 计数信号量实验。
  2. 互斥锁
    • 互斥锁基本概念;
    • 优先级翻转;
    • 优先级继承;
    • 互斥锁运行机制;
    • 互斥锁创建、获取、释放、删除;
    • 互斥锁实验。

这一章最重要的区别是:

信号量主要用于同步,也可以用于资源计数;互斥锁主要用于保护临界资源。


二、为什么需要信号量和互斥锁?

在 LiteOS 多任务系统中,多个任务可能同时运行,任务之间会有两类典型问题。

第一类是同步问题

比如:

  • 任务 A 负责采集温湿度;
  • 任务 B 负责刷新屏幕;
  • 屏幕不需要一直刷新,只需要等温湿度数据更新后再刷新。

这时候就需要任务 A 通知任务 B:

数据已经准备好了,你可以刷新了。

这就是同步。

第二类是互斥访问问题

比如:

  • 任务 A 要使用串口发送数据;
  • 任务 B 也要使用同一个串口发送数据;
  • 串口硬件只有一个,不能两个任务同时使用。

这时候就需要保证:

同一时刻只能有一个任务访问串口。

这就是互斥。

因此:

问题类型 解决机制
任务之间等待某个事件 信号量
中断通知任务处理数据 信号量
多个任务竞争有限资源 计数信号量
多个任务访问同一个临界资源 互斥锁

三、信号量的基本概念

信号量可以理解为一个非负整数计数器

它的值表示当前可用资源数量,或者表示某个事件是否发生。

当任务获取信号量时,信号量值减 1; 当任务释放信号量时,信号量值加 1。

如果信号量值为 0,任务再去获取它,就可能进入阻塞态。

信号量值含义的说明:

信号量值 含义
0 没有可用信号量,可能有任务阻塞等待
正值 有一个或多个可用信号量

可以这样理解:

信号量就像资源许可证。任务要使用资源或继续执行,必须先拿到许可证;没有许可证就等待。


四、信号量的分类

信号量分为两类:

  1. 二值信号量
  2. 计数信号量

1. 二值信号量

二值信号量只有两个取值:

01

也就是说,它只有“无效”和“有效”两种状态。

含义
0 没有信号量,任务获取不到
1 有信号量,任务可以获取

二值信号量常用于:

  • 任务与任务之间同步;
  • 中断与任务之间同步。

例如:

  • 串口中断接收到数据后释放信号量;
  • 数据处理任务获取信号量后开始处理数据。

这比任务一直轮询串口是否有数据更高效。


2. 计数信号量

计数信号量的值可以大于 1。

它常用于:

  • 事件计数;
  • 资源数量管理。

类比停车场:

假设停车场有 100 个车位,那么计数信号量初值可以设置为 100。

每进一辆车,相当于获取一个信号量,车位数减 1; 每离开一辆车,相当于释放一个信号量,车位数加 1; 当车位为 0 时,其他车不能进入。

对应到操作系统:

计数信号量可以限制同时访问某类资源的任务数量。


五、二值信号量的应用场景

1. 任务与任务之间同步

例如:

  • 任务 A:温湿度采集任务;
  • 任务 B:LCD 显示任务。

温湿度传感器每 1 秒采集一次数据,LCD 刷新周期可以是 100ms。 如果 LCD 每 100ms 都刷新一次,会浪费 CPU。

更合理的方式是:

  1. 温湿度任务采集完成;
  2. 温湿度任务释放一个二值信号量;
  3. LCD 任务获取信号量;
  4. LCD 任务刷新显示。

这样 LCD 只有在数据更新时才刷新。

这就是任务间同步。


2. 中断与任务之间同步

例如串口接收。

CPU 并不知道串口什么时候会收到数据。

如果任务一直查询串口:

while (1)
{
    if (串口收到数据)
    {
        处理数据();
    }
}

这种方式会浪费 CPU。

更好的方式是:

  1. 串口任务先等待信号量;
  2. 没有数据时,任务阻塞;
  3. 串口中断收到数据;
  4. 中断服务程序释放信号量;
  5. 串口任务被唤醒;
  6. 串口任务处理数据。

这就是中断与任务同步。


六、信号量用于同步和互斥的区别

信号量可以用于同步,也可以用于互斥,但实际应用中一般不建议用信号量做互斥,因为操作系统专门提供了互斥锁。

1. 信号量用于同步

创建时通常把信号量初值设为 0

例如:

LOS_BinarySemCreate(0, &BinarySem_Handle);

任务先获取信号量,如果没有信号量就阻塞。 另一个任务或中断满足条件后释放信号量,阻塞任务被唤醒。

特点:

先等待,后通知。


2. 信号量用于互斥

创建时通常把信号量初值设为 1

任务访问临界资源前先获取信号量,访问完成后释放信号量。

但问题是:

  • 信号量没有严格的所有者概念;
  • 可能出现释放不配对;
  • 嵌套使用时可能死锁;
  • 没有互斥锁的优先级继承机制。

所以更推荐:

同步用信号量,互斥用互斥锁。


七、二值信号量与互斥锁的区别

这是本章最容易考的内容之一。

对比项 二值信号量 互斥锁
主要用途 同步 保护临界资源
取值 0 或 1 开锁或闭锁
是否有所有者 没有严格所有者 有所有者
是否可由中断释放 可以 post 中断不能使用互斥锁
是否支持递归获取 不适合,可能死锁 支持递归访问
是否有优先级继承 没有
使用要求 生产者 post,消费者 pend 获取和释放必须配对
典型场景 串口中断通知任务 多任务访问串口、I2C、SPI

一句话记忆:

信号量是“通知机制”,互斥锁是“资源保护机制”。


八、信号量运行机制

1. 信号量初始化

系统初始化时,LiteOS 会为配置数量的信号量申请内存。

这个数量由宏控制:

LOSCFG_BASE_IPC_SEM_LIMIT

系统会把所有信号量初始化为未使用状态,并加入未使用信号量链表。

当用户创建信号量时,系统会从未使用链表中取出一个信号量控制块。


2. 二值信号量创建

二值信号量最大计数值为 1。

也就是说:

最大值 = 1

创建函数是:

LOS_BinarySemCreate(UINT16 usCount, UINT32 *puwSemHandle);

其中:

参数 含义
usCount 初始可用信号量个数,只能是 0 或 1
puwSemHandle 信号量 ID 指针

例如:

UINT32 BinarySem_Handle;
LOS_BinarySemCreate(0, &BinarySem_Handle);

如果初值为 0,表示创建后不可立即获取,适合同步场景。

如果初值为 1,表示创建后可以立即获取,类似资源初始可用。


3. 信号量获取 LOS_SemPend

获取信号量的函数是:

LOS_SemPend(UINT32 uwSemHandle, UINT32 uwTimeout);

它的作用是:

尝试获取指定信号量。

如果信号量可用:

  • 信号量计数减 1;
  • 函数返回 LOS_OK

如果信号量不可用:

  • 若等待时间为 0,立即返回失败;
  • 若设置等待时间,任务进入阻塞态;
  • 若设置永久等待,任务一直等到信号量被释放。

常用形式:

LOS_SemPend(BinarySem_Handle, LOS_WAIT_FOREVER);

意思是:

一直等待,直到获取到信号量。

注意:

LOS_SemPend() 不能在中断中调用。

因为获取不到信号量时可能阻塞,而中断服务程序不能阻塞。


4. 信号量释放 LOS_SemPost

释放信号量的函数是:

LOS_SemPost(UINT32 uwSemHandle);

它的作用是:

释放一个信号量,使等待该信号量的任务可以继续运行。

如果有任务阻塞在这个信号量上:

  • 系统会唤醒等待任务;
  • 任务进入就绪态;
  • 系统可能进行一次调度。

如果没有任务等待:

  • 信号量计数加 1;
  • 但不能超过最大值。

注意:

LOS_SemPost() 可以在中断中调用。

这也是信号量常用于中断与任务同步的重要原因。


5. 信号量删除 LOS_SemDelete

删除函数是:

LOS_SemDelete(UINT32 uwSemHandle);

注意:

如果有任务正在等待该信号量,不能删除。

也就是说:

信号量阻塞列表不为空时,不允许删除。

否则会导致等待任务状态异常。


九、信号量控制块

信号量控制块结构:

typedef struct {
    UINT16      usSemStat;
    UINT16      usSemCount;
    UINT16      usMaxSemCount;
    UINT16      usSemID;
    LOS_DL_LIST stSemList;
} SEM_CB_S;

可以理解为每个信号量的“管理档案”。

成员 含义
usSemStat 信号量状态,是否被使用
usSemCount 当前可用信号量个数
usMaxSemCount 最大信号量个数
usSemID 信号量 ID
stSemList 阻塞等待该信号量的任务列表

LiteOS 通过信号量控制块来判断:

  • 信号量是否存在;
  • 当前还有几个可用;
  • 哪些任务正在等待它。

十、信号量常见错误代码

考试不一定要求全背,但要理解常见错误。

错误代码 含义
LOS_ERRNO_SEM_NO_MEMORY 内存空间不足
LOS_ERRNO_SEM_INVALID 信号量参数或 ID 非法
LOS_ERRNO_SEM_PTR_NULL 传入空指针
LOS_ERRNO_SEM_ALL_BUSY 信号量控制块不可用
LOS_ERRNO_SEM_PEND_INTERR 中断中非法调用 LOS_SemPend
LOS_ERRNO_SEM_PEND_IN_LOCK 任务锁定状态下无法获取信号量
LOS_ERRNO_SEM_TIMEOUT 获取信号量超时
LOS_ERRNO_SEM_OVERFLOW 信号量释放超过最大值
LOS_ERRNO_SEM_PENDED 有任务等待信号量,不能删除

最重要的是:

LOS_SemPend() 不能在中断中调用,LOS_SemPost() 可以在中断中调用。


十一、信号量典型代码模式

1. 二值信号量同步模式

任务等待:

static void Receive_Task(void)
{
    while (1)
    {
        LOS_SemPend(BinarySem_Handle, LOS_WAIT_FOREVER);

        /* 收到信号量后处理数据 */
        Process_Data();
    }
}

中断释放:

void USART_IRQHandler(void)
{
    if (接收到数据)
    {
        LOS_SemPost(BinarySem_Handle);
    }
}

这个模式的核心是:

中断只负责通知,任务负责处理。


2. 计数信号量资源管理模式

假设有 3 个相同资源:

LOS_SemCreate(3, &CountSem_Handle);

任务使用资源前:

LOS_SemPend(CountSem_Handle, LOS_WAIT_FOREVER);

使用完成后:

LOS_SemPost(CountSem_Handle);

这表示最多允许 3 个任务同时访问某类资源。


十二、互斥锁的基本概念

互斥锁又叫互斥信号量,是一种特殊的二值信号量。

但它和普通二值信号量不同,它具有三个重要特性:

  1. 所有权
  2. 递归访问
  3. 优先级继承

互斥锁常用于保护临界资源,保证同一时刻只有一个任务访问该资源。

例如:

  • 串口;
  • I2C 总线;
  • SPI 总线;
  • 文件系统;
  • 全局变量;
  • 显示屏;
  • 共享缓冲区。

十三、什么是临界资源?

临界资源是指:

同一时刻只能被一个任务访问的资源。

例如串口硬件只有一个,如果两个任务同时使用串口发送数据,就可能出现数据混乱。

所以访问串口前需要先加锁:

LOS_MuxPend(Mutex_Handle, LOS_WAIT_FOREVER);

/* 使用串口 */
printf("Task output\r\n");

LOS_MuxPost(Mutex_Handle);

这就保证同一时刻只有一个任务可以进入这段代码。


十四、互斥锁的开锁和闭锁

互斥锁只有两种状态:

状态 含义
开锁 没有任务持有,任务可以获取
闭锁 已经被某个任务持有,其他任务不能获取

当任务成功获取互斥锁后,互斥锁进入闭锁状态。 当持有任务释放互斥锁后,互斥锁重新变为开锁状态。


十五、互斥锁的所有权

互斥锁和信号量最大的区别之一是:

互斥锁有所有者。

谁获取了互斥锁,谁就是锁的所有者。 只有持有该锁的任务才能释放它。

其他任务不能释放这个锁。 中断也不能释放这个锁。

这点和信号量不同。

信号量可以由其他任务或中断 post,但互斥锁必须由持有者释放。


十六、互斥锁的递归访问

互斥锁支持递归访问。

也就是说:

同一个任务已经持有某把互斥锁时,可以再次获取这把锁,不会被自己阻塞。

系统会记录这个任务获取锁的次数。

例如:

LOS_MuxPend(Mutex_Handle, LOS_WAIT_FOREVER);
LOS_MuxPend(Mutex_Handle, LOS_WAIT_FOREVER);

/* 临界区 */

LOS_MuxPost(Mutex_Handle);
LOS_MuxPost(Mutex_Handle);

获取了两次,就必须释放两次。

如果只释放一次,锁仍然处于闭锁状态。

注:

闭锁、解锁必须配对。


十七、为什么普通二值信号量不适合递归互斥?

如果用二值信号量做互斥:

LOS_SemPend(BinarySem_Handle, LOS_WAIT_FOREVER);
LOS_SemPend(BinarySem_Handle, LOS_WAIT_FOREVER);

第一次获取后,信号量变成 0。

第二次再获取时,即使是同一个任务,也会因为信号量不可用而阻塞。

于是任务等待自己释放信号量,但它已经阻塞,无法继续执行释放操作,就形成死锁

互斥锁因为有所有者和递归计数,所以可以避免这种情况。


十八、优先级翻转

优先级翻转是互斥锁章节的重点。

假设有三个任务:

任务 优先级
H 高优先级
M 中优先级
L 低优先级

过程如下:

  1. L 任务先运行,并获取某个临界资源;
  2. H 任务被唤醒,也想访问这个资源;
  3. 但资源被 L 持有,所以 H 被阻塞;
  4. 此时 M 任务被唤醒;
  5. M 的优先级高于 L,所以 M 抢占 L;
  6. L 无法继续运行,也就无法释放资源;
  7. H 虽然优先级最高,却一直等待。

这就是优先级翻转。

简单说:

高优先级任务被低优先级任务间接阻塞,而中优先级任务又抢占低优先级任务,导致高优先级任务长时间无法运行。

这违反了实时系统中“高优先级任务应优先响应”的原则。


十九、优先级继承

为了解决或缓解优先级翻转,互斥锁提供优先级继承机制。

优先级继承的思想是:

当低优先级任务持有高优先级任务需要的资源时,系统临时提升低优先级任务的优先级,使它尽快运行并释放资源。

仍然用 H、M、L 任务说明:

  1. L 持有互斥锁;
  2. H 要获取该锁,但锁被 L 持有;
  3. H 阻塞;
  4. 系统把 L 的优先级临时提升到 H 的优先级;
  5. M 被唤醒后,因为优先级不高于 L,无法抢占 L;
  6. L 继续运行并尽快释放锁;
  7. H 获得锁并运行;
  8. L 的优先级恢复原来的低优先级。

所以优先级继承的目的不是彻底消灭优先级翻转,而是:

尽量缩短高优先级任务被阻塞的时间。

LiteOS 的优先级继承机制不能从根本上解决所有优先级翻转问题,只能把影响降到最小。因此硬实时系统在设计时就应尽量避免优先级翻转。


二十、使用互斥锁的注意事项

注意几个重点:

  1. 获得互斥锁后,应尽快释放;
  2. 任务持有互斥锁期间,不要更改任务优先级;
  3. 优先级继承只能降低优先级翻转影响,不能完全消除;
  4. 互斥锁不能在中断服务程序中使用。

尤其要记住:

互斥锁不能在中断中使用。

原因是互斥锁有所有者,而中断上下文没有普通任务意义上的所有者;同时互斥锁获取失败时可能阻塞,而中断不能阻塞。


二十一、互斥锁运行机制

互斥锁的运行机制可以概括为:

  1. 创建互斥锁后,初始为开锁状态;
  2. 任务访问临界资源前,先获取互斥锁;
  3. 如果互斥锁可用,任务获得锁,锁变为闭锁状态;
  4. 如果互斥锁不可用,任务根据等待时间阻塞;
  5. 持有锁的任务访问临界资源;
  6. 访问完成后释放锁;
  7. 如果有任务等待该锁,系统唤醒等待任务;
  8. 可能触发任务调度。

二十二、互斥锁控制块

互斥锁控制块:

typedef struct {
    UINT8        ucMuxStat;
    UINT16       usMuxCount;
    UINT32       ucMuxID;
    LOS_DL_LIST  stMuxList;
    LOS_TASK_CB *pstOwner;
    UINT16       usPriority;
} MUX_CB_S;

各字段含义如下:

成员 含义
ucMuxStat 互斥锁状态,是否被使用
usMuxCount 持有次数,用于递归访问
ucMuxID 互斥锁 ID
stMuxList 阻塞等待该锁的任务列表
pstOwner 当前持有锁的任务控制块指针
usPriority 持有锁任务的初始优先级

互斥锁控制块比信号量控制块多了两个关键内容:

  1. 所有者 pstOwner
  2. 初始优先级 usPriority

这正是互斥锁能实现所有权和优先级继承的基础。


二十三、互斥锁常用函数

LiteOS 互斥锁常用函数有四个:

LOS_MuxCreate();  // 创建互斥锁
LOS_MuxPend();    // 获取互斥锁
LOS_MuxPost();    // 释放互斥锁
LOS_MuxDelete();  // 删除互斥锁

1. 创建互斥锁 LOS_MuxCreate

函数:

LOS_MuxCreate(UINT32 *puwMuxHandle);

示例:

UINT32 Mutex_Handle;
UINT32 uwRet;

uwRet = LOS_MuxCreate(&Mutex_Handle);
if (uwRet != LOS_OK)
{
    printf("互斥锁创建失败!\n");
}

创建成功后,系统返回互斥锁 ID。

以后获取、释放、删除互斥锁都需要使用这个 ID。


2. 获取互斥锁 LOS_MuxPend

函数:

LOS_MuxPend(UINT32 uwMuxHandle, UINT32 uwTimeout);

作用:

获取指定互斥锁。

如果锁处于开锁状态:

  • 当前任务获得锁;
  • 持有次数加 1;
  • 记录锁的所有者;
  • 返回 LOS_OK

如果锁已经被其他任务持有:

  • 如果等待时间为 0,立即返回失败;
  • 如果设置等待时间,则当前任务阻塞;
  • 如果需要,触发优先级继承。

示例:

LOS_MuxPend(Mutex_Handle, LOS_WAIT_FOREVER);

/* 访问临界资源 */

LOS_MuxPost(Mutex_Handle);

3. 释放互斥锁 LOS_MuxPost

函数:

LOS_MuxPost(UINT32 uwMuxHandle);

作用:

释放当前任务持有的互斥锁。

注意:

只有持有锁的任务才能释放锁。

如果当前任务不是锁的所有者,释放会失败。

释放时系统会:

  1. 检查当前任务是否为锁的持有者;
  2. 持有次数减 1;
  3. 如果持有次数仍大于 0,说明递归锁还没完全释放;
  4. 如果持有次数为 0,锁真正释放;
  5. 如果发生过优先级继承,恢复任务原始优先级;
  6. 如果有任务等待该锁,唤醒等待任务;
  7. 可能触发任务调度。

4. 删除互斥锁 LOS_MuxDelete

函数:

LOS_MuxDelete(UINT32 uwMuxHandle);

注意:

如果有任务持有该锁,或者有任务阻塞等待该锁,不能删除。

也就是说,删除互斥锁前要确保:

  • 没有任务持有它;
  • 没有任务等待它。

二十四、互斥锁错误代码

常见错误代码如下:

错误代码 含义
LOS_ERRNO_MUX_NO_MEMORY 内存请求失败
LOS_ERRNO_MUX_INVALID 互斥锁不可用或 ID 无效
LOS_ERRNO_MUX_PTR_NULL 传入空指针
LOS_ERRNO_MUX_ALL_BUSY 没有可用互斥锁控制块
LOS_ERRNO_MUX_UNAVAILABLE 获取锁失败,锁被其他任务使用
LOS_ERRNO_MUX_PEND_INTERR 在中断中使用互斥锁
LOS_ERRNO_MUX_PEND_IN_LOCK 调度未使能或任务锁定时等待锁
LOS_ERRNO_MUX_TIMEOUT 获取互斥锁超时
LOS_ERRNO_MUX_PENDED 删除正在使用或有任务等待的锁

重点记:

互斥锁不能在中断中使用,不能删除正在使用或有任务等待的互斥锁。


二十五、互斥锁典型代码模式

保护串口输出

UINT32 Mutex_Handle;

static void Task1(void)
{
    while (1)
    {
        LOS_MuxPend(Mutex_Handle, LOS_WAIT_FOREVER);

        printf("Task1 正在使用串口\r\n");

        LOS_MuxPost(Mutex_Handle);

        LOS_TaskDelay(1000);
    }
}
static void Task2(void)
{
    while (1)
    {
        LOS_MuxPend(Mutex_Handle, LOS_WAIT_FOREVER);

        printf("Task2 正在使用串口\r\n");

        LOS_MuxPost(Mutex_Handle);

        LOS_TaskDelay(500);
    }
}

这里串口就是临界资源。

如果不用互斥锁,两个任务可能同时 printf,导致串口输出混乱。

加锁后,同一时刻只有一个任务能输出。


二十六、信号量与互斥锁最终对比

这是本章最重要的复习表。

对比项 信号量 互斥锁
本质 计数器 特殊二值信号量
主要用途 同步、资源计数 临界资源互斥保护
类型 二值信号量、计数信号量 互斥锁
是否有所有者 通常没有
是否支持递归 不适合 支持
是否有优先级继承 没有
是否可在中断中释放 可以 LOS_SemPost 不可以
是否可在中断中获取 不可以 LOS_SemPend 不可以
典型应用 串口中断通知任务 串口、I2C、SPI、共享变量保护
操作函数 LOS_SemPend/Post LOS_MuxPend/Post

一句话总结:

信号量解决“等不等”的问题,互斥锁解决“谁能用”的问题。


二十七、本章重点背诵版

1. 信号量定义

信号量是一种任务间通信机制,可用于任务同步或资源计数。它本质上是一个非负整数,任务获取信号量时计数减 1,释放信号量时计数加 1;当信号量为 0 时,获取信号量的任务可能进入阻塞态。


2. 二值信号量

二值信号量只有 0 和 1 两种取值,常用于任务与任务之间、任务与中断之间的同步。若信号量值为 0,任务获取不到信号量会阻塞;当其他任务或中断释放信号量后,阻塞任务被唤醒。


3. 计数信号量

计数信号量可取多个正整数值,常用于资源数量管理和事件计数。例如停车场有 100 个车位,可以用初值为 100 的计数信号量表示可用车位数量。


4. 信号量常用函数

信号量常用函数包括:

LOS_BinarySemCreate();  // 创建二值信号量
LOS_SemCreate();        // 创建计数信号量
LOS_SemPend();          // 获取信号量
LOS_SemPost();          // 释放信号量
LOS_SemDelete();        // 删除信号量

5. 信号量使用注意事项

LOS_SemPend() 不能在中断服务程序中调用,因为获取不到信号量时可能阻塞;LOS_SemPost() 可以在中断中调用,因此信号量常用于中断通知任务。删除信号量前要确保没有任务阻塞等待该信号量。


6. 互斥锁定义

互斥锁是一种特殊的二值信号量,具有所有权、递归访问和优先级继承等特性,常用于保护临界资源,保证同一时刻只有一个任务访问共享资源。


7. 优先级翻转

优先级翻转是指高优先级任务因等待低优先级任务持有的资源而无法运行,而中优先级任务又抢占低优先级任务,导致高优先级任务长时间阻塞的现象。它会破坏实时系统的响应性。


8. 优先级继承

优先级继承是指当低优先级任务持有高优先级任务需要的互斥锁时,系统临时提升低优先级任务的优先级,使其尽快运行并释放资源,之后恢复原优先级,从而降低优先级翻转的影响。


9. 互斥锁常用函数

互斥锁常用函数包括:

LOS_MuxCreate();  // 创建互斥锁
LOS_MuxPend();    // 获取互斥锁
LOS_MuxPost();    // 释放互斥锁
LOS_MuxDelete();  // 删除互斥锁

10. 互斥锁使用注意事项

使用互斥锁时,获取锁后应尽快释放;持有锁期间不要随意修改任务优先级;互斥锁不能在中断服务程序中使用;获取和释放必须配对;删除互斥锁前必须确保没有任务持有或等待该锁。


二十八、考试可能这样问

题目1:什么是信号量?有什么作用?

答:

信号量是一种任务间通信机制,本质上是一个非负整数。任务获取信号量时计数减 1,释放信号量时计数加 1。当信号量为 0 时,任务获取不到信号量可能进入阻塞态。信号量可用于任务间同步、任务与中断间同步,也可用于资源数量管理。


题目2:二值信号量和计数信号量有什么区别?

答:

二值信号量只有 0 和 1 两种取值,主要用于任务同步或中断与任务同步;计数信号量可以取多个正整数值,主要用于资源数量管理和事件计数。例如二值信号量可用于串口中断通知接收任务,计数信号量可用于管理多个相同资源。


题目3:为什么不推荐用二值信号量实现互斥?

答:

二值信号量没有严格的所有者概念,也没有优先级继承机制,嵌套获取时容易导致死锁。而互斥锁专门用于临界资源保护,具有所有权、递归访问和优先级继承机制,因此实际应用中通常用互斥锁实现互斥访问。


题目4:LOS_SemPend 和 LOS_SemPost 有什么区别?

答:

LOS_SemPend() 用于获取信号量,获取成功后信号量计数减 1;如果没有可用信号量,任务可能进入阻塞态。LOS_SemPost() 用于释放信号量,释放后信号量计数加 1,或者唤醒等待该信号量的任务。LOS_SemPend() 不能在中断中调用,而 LOS_SemPost() 可以在中断中调用。


题目5:什么是互斥锁?适合什么场景?

答:

互斥锁是一种特殊的二值信号量,具有所有权、递归访问和优先级继承机制,主要用于保护临界资源,保证同一时刻只有一个任务访问共享资源。它适合用于串口、I2C、SPI、共享变量、文件系统等资源保护场景。


题目6:什么是优先级翻转?有什么危害?

答:

优先级翻转是指高优先级任务因等待低优先级任务持有的资源而被阻塞,而中优先级任务又抢占低优先级任务,导致高优先级任务长时间无法运行的现象。它会破坏实时系统中高优先级任务优先响应的原则,可能导致系统实时性下降甚至产生严重后果。


题目7:什么是优先级继承?

答:

优先级继承是互斥锁用于缓解优先级翻转的一种机制。当低优先级任务持有高优先级任务需要的互斥锁时,系统会临时提高低优先级任务的优先级,使其尽快运行并释放互斥锁。释放锁后,该任务优先级恢复为原来的值。


题目8:互斥锁为什么不能在中断中使用?

答:

互斥锁具有所有权,必须由持有该锁的任务释放,而中断服务程序不属于普通任务上下文,没有互斥锁所有者概念。同时,获取互斥锁失败时任务可能阻塞,而中断服务程序不能阻塞。因此互斥锁不能在中断中使用。


题目9:信号量和互斥锁有什么区别?

答:

信号量主要用于同步和资源计数,可以由任务或中断释放,常用于任务间或中断与任务间通知;互斥锁主要用于临界资源保护,具有所有权、递归访问和优先级继承机制,只能由持有锁的任务释放,不能在中断中使用。简单来说,信号量解决任务之间的同步问题,互斥锁解决共享资源互斥访问问题。


二十九、本章一句话总结

第八章节可以这样总结:

信号量是 LiteOS 中用于任务同步和资源计数的重要机制,二值信号量适合同步,计数信号量适合资源管理;互斥锁是用于保护临界资源的特殊同步机制,具有所有权、递归访问和优先级继承能力,能够减少优先级翻转带来的影响。信号量强调“通知和计数”,互斥锁强调“独占访问和资源保护”。