RT-Thread 之信號量使用
1. 信號量概述
信號量(Semaphore)是 RT-Thread 中核心的同步與互斥 IPC 機制,根據初始值可分為兩類,適用場景不同:
- 二值信號量(初始值 = 1):實現互斥訪問,確保同一時間只有一個線程占用共享資源(如硬件外設、全局變量)。
- 計數信號量(初始值 > 1):實現資源計數,允許指定數量的線程同時訪問共享資源(如有限的緩沖區(qū)、總線接口)。
典型應用場景:多個傳感器采集線程競爭 I2C 總線時,用初始值為 1 的二值信號量做互斥鎖,避免總線數據沖突;多線程讀取環(huán)形緩沖區(qū)時,用計數信號量統(tǒng)計可用數據量,實現生產者 - 消費者同步。
2. 信號量核心 API 函數
信號量的操作圍繞 “創(chuàng)建 / 初始化 / 獲取 / 釋放 / 刪除 / 脫離” 展開,需注意動態(tài)創(chuàng)建與靜態(tài)初始化的區(qū)別(動態(tài)依賴內存堆,靜態(tài)基于已分配的全局 / 靜態(tài)變量)。
2.1 信號量的創(chuàng)建與初始化
| 類型 | 函數原型 | 關鍵參數說明 |
|---|---|---|
| 動態(tài)創(chuàng)建 | rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag) |
- name:信號量名稱(用于調試)- value:初始值(1 = 互斥,>1 = 計數)- flag:等待隊列排序方式(RT_IPC_FLAG_FIFO= 先進先出;RT_IPC_FLAG_PRIO= 按線程優(yōu)先級) |
| 靜態(tài)初始化 | rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag) |
- sem:全局 / 靜態(tài)定義的信號量對象(需提前分配內存)- 其他參數同動態(tài)創(chuàng)建 |
2.2 信號量的獲取
獲取信號量即 “申請資源”,若信號量值 > 0 則直接占用(值 - 1),若值 = 0 則線程進入阻塞狀態(tài),直到超時或被喚醒。
| 函數 | 功能描述 |
|---|---|
rt_sem_take(rt_sem_t sem, rt_int32_t timeout) |
帶超時獲取:timeout= 等待時間(單位:tick,RT_WAITING_FOREVER= 永久等待,0= 無等待) |
rt_sem_trytake(rt_sem_t sem) |
無等待獲取:若信號量不可用,直接返回錯誤(RT_EBUSY),不阻塞線程 |
2.3 信號量的釋放與銷毀
釋放信號量即 “歸還資源”,會將信號量值 + 1,并按創(chuàng)建時的flag喚醒等待隊列中的線程;銷毀則釋放信號量占用的資源。
| 操作 | 函數原型 | 適用場景 |
|---|---|---|
| 釋放 | rt_err_t rt_sem_release(rt_sem_t sem) |
線程使用完資源后,歸還信號量 |
| 動態(tài)刪除 | rt_err_t rt_sem_delete(rt_sem_t sem) |
銷毀動態(tài)創(chuàng)建的信號量(釋放堆內存) |
| 靜態(tài)脫離 | rt_err_t rt_sem_detach(rt_sem_t sem) |
脫離靜態(tài)初始化的信號量(不釋放內存) |
3. 信號量使用示例
3.1 源代碼
#include "thread_task.h"
#include "main.h"
#include <stdio.h>
#include "rtthread.h"
#include <rthw.h>
/******************************************** 線程 1 ******************************************************/
#define THREAD_1_PRIORITY 4 /* 線程優(yōu)先級(值越小優(yōu)先級越高) */
#define THREAD_1_STACK_SIZE 512 /* 線程棧空間大小(單位:字節(jié)) */
#define THREAD_1_TIMESLICE 10 /* 線程時間片個數(單位:tick) */
static struct rt_thread *thread_1_handle; /* 線程句柄 */
/******************************************** 線程 2 ******************************************************/
#define THREAD_2_PRIORITY 5 /* 線程優(yōu)先級(低于線程1) */
#define THREAD_2_STACK_SIZE 512 /* 線程棧空間大小 */
#define THREAD_2_TIMESLICE 10 /* 線程時間片個數 */
static struct rt_thread *thread_2_handle; /* 線程句柄 */
struct rt_semaphore sem_test; /* 靜態(tài)信號量對象(全局定義,提前分配內存) */
/**
* @brief LED閃爍函數(固定閃爍6次,即3個完整周期)
* @param time:每次翻轉后的延遲時間(單位:tick)
*/
void LED_toggle(uint16_t time)
{
for(uint8_t i = 0; i < 6; i++) // 翻轉6次 = 3個亮滅周期
{
HAL_GPIO_TogglePin(GPIOC, LED1_Pin);
rt_thread_delay(time); // 延遲等待,模擬資源占用
}
}
/**
* @brief 線程1入口函數(高優(yōu)先級,10Hz閃爍LED)
* @param param:線程參數
*/
void thread_1_entry(void* param)
{
while(1)
{
/* 帶100ms超時獲取信號量:非關鍵任務,超時后放棄,避免阻塞 */
if (rt_sem_take(&sem_test, 100) == RT_EOK)
{
LED_toggle(100); // 10Hz = 周期100ms → 每次延遲100ms(亮50ms+滅50ms)
rt_sem_release(&sem_test); // 釋放信號量,歸還資源
}
rt_thread_delay(200); // 線程執(zhí)行間隔,降低CPU占用
}
}
/**
* @brief 線程2入口函數(低優(yōu)先級,1Hz閃爍LED)
* @param param:線程參數
*/
void thread_2_entry(void* param)
{
while(1)
{
/* 永久等待獲取信號量:關鍵任務,必須拿到資源才執(zhí)行 */
if (rt_sem_take(&sem_test, RT_WAITING_FOREVER) == RT_EOK)
{
LED_toggle(1000); // 1Hz = 周期1000ms → 每次延遲1000ms(亮500ms+滅500ms)
rt_sem_release(&sem_test); // 釋放信號量,歸還資源
}
rt_thread_delay(300); // 線程執(zhí)行間隔
}
}
/**
* @brief 初始化信號量并創(chuàng)建啟動線程
*/
void ThreadStart(void)
{
rt_base_t level = rt_hw_interrupt_disable();
/* 靜態(tài)初始化信號量) */
rt_sem_init(
&sem_test, /* 信號量對象 */
"sem_test", /* 信號量名稱(用于finsh調試) */
1, /* 初始值=1 → 二值信號量(互斥模式) */
RT_IPC_FLAG_FIFO /* 等待隊列按FIFO排序 */
);
/* 動態(tài)創(chuàng)建并啟動線程1 */
thread_1_handle = rt_thread_create(
"thread_1", /* 線程名稱 */
thread_1_entry, /* 線程入口函數 */
RT_NULL, /* 線程參數 */
THREAD_1_STACK_SIZE, /* 線程棧大小 */
THREAD_1_PRIORITY, /* 線程優(yōu)先級 */
THREAD_1_TIMESLICE /* 線程時間片 */
);
if (thread_1_handle != RT_NULL)
rt_thread_startup(thread_1_handle);
/* 動態(tài)創(chuàng)建并啟動線程2 */
thread_2_handle = rt_thread_create(
"thread_2", /* 線程名稱 */
thread_2_entry, /* 線程入口函數 */
RT_NULL, /* 線程參數 */
THREAD_2_STACK_SIZE, /* 線程棧大小 */
THREAD_2_PRIORITY, /* 線程優(yōu)先級 */
THREAD_2_TIMESLICE /* 線程時間片 */
);
if (thread_2_handle != RT_NULL)
rt_thread_startup(thread_2_handle);
rt_hw_interrupt_enable(level); // 恢復中斷
}
3.2 代碼執(zhí)行流程
3.2.1 初始階段(資源初始化)
- 調用
ThreadStart時,先關閉中斷,確保信號量初始化不被打斷; - 靜態(tài)初始化信號量
sem_test,初始值 = 1(二值互斥模式),等待隊列按 FIFO 排序; - 動態(tài)創(chuàng)建線程 1(優(yōu)先級 4)和線程 2(優(yōu)先級 5),并啟動線程;
- 恢復中斷,線程進入就緒狀態(tài),RT-Thread 調度器開始調度。
3.2.2 競爭階段(高優(yōu)先級線程優(yōu)先)
- 線程 1 優(yōu)先級(4)高于線程 2(5),調度器優(yōu)先切換線程 1 執(zhí)行;
- 線程 1 調用
rt_sem_take,信號量值 1→0,獲取成功,開始執(zhí)行LED_toggle(100)(10Hz 閃爍 6 次,耗時 600ms); - 線程 2 同時調用
rt_sem_take,信號量值為 0,進入永久阻塞狀態(tài),等待信號量釋放。
3.2.3 優(yōu)先級調度階段(高優(yōu)先級線程搶占)
- 線程 1 閃爍完成后,調用
rt_sem_release釋放信號量,值 0→1,同時喚醒阻塞的線程 2; - 此時線程 1 因優(yōu)先級更高,調度器優(yōu)先將線程 1 切換為就緒狀態(tài),線程 1 再次調用
rt_sem_take,優(yōu)先獲取信號量,繼續(xù)執(zhí)行; - 線程 2 被喚醒后,發(fā)現信號量已被線程 1 占用,再次進入阻塞狀態(tài),等待下一次信號量釋放。
3.2.4 交替執(zhí)行特征(LED 表現)
- 線程 1(高優(yōu)先級)獲取信號量的概率更高,LED 頻繁以 10Hz 快速閃爍;
- 僅當線程 1 釋放信號量后,進入
rt_thread_delay(200)延遲時(線程 1 阻塞),線程 2 才能獲取信號量,執(zhí)行 1Hz 慢速閃爍; - 最終 LED 表現:以 10Hz 快速閃爍為主,偶爾插入 1Hz 慢速閃爍,無閃爍混亂(信號量互斥生效)。
3.2.5 同步效果
- 信號量確保 LED 硬件資源同一時間僅被一個線程控制,避免并發(fā)操作導致的閃爍頻率混亂;
- 優(yōu)先級機制保證高優(yōu)先級線程(線程 1)的實時性,滿足快速響應需求;
- 線程 1 的 100ms 超時獲取,避免因資源長期被占用導致自身阻塞;線程 2 的永久等待,確保關鍵閃爍任務不丟失。

浙公網安備 33010602011771號