4. 互斥锁保护共享资源

任务说明

本案例在STM32F103开发板上,使用RT-Thread Nano实时操作系统创建两个LED闪烁任务。任务1采用忙等待延时(空循环占用CPU),任务2采用阻塞延时(调用系统延时函数主动释放CPU)。通过观察两个LED的闪烁行为以及空闲线程的运行情况,直观对比两种延时方式对CPU资源占用的差异。

原理简述

忙等待延时通过空循环消耗CPU时间,延时期间任务一直占用处理器,导致低优先级任务或空闲线程无法运行。阻塞延时则调用rt_thread_delay(),将当前任务挂起并触发线程调度,CPU转去执行其他就绪任务或空闲线程,延时结束后任务自动恢复就绪。两种方式都能实现周期性执行,但对系统实时性和CPU利用率的影响截然不同。

硬件连接

STM32F103 Cortex-M3 @72MHz PA1 → LED1 PA2 → LED2 330Ω 330Ω LED1 GND LED2 GND 任务1:忙等待延时(PA1/LED1) 任务2:阻塞延时(PA2/LED2)

代码实现

创建文件main.c,完整代码如下:

#include "board.h"
#include "rtthread.h"

#define THREAD_PRIORITY1  8
#define THREAD_PRIORITY2  9
#define THREAD_STACK_SIZE 512

/* 忙等待延时函数(空循环) */
static void delay_busy(volatile uint32_t count)
{
    while (count-- > 0) {
        __NOP();
    }
}

/* 任务1:忙等待延时,LED1每500ms翻转一次 */
static void thread1_entry(void *param)
{
    rt_uint32_t i;
    
    while (1) {
        rt_pin_write(PA1, PIN_HIGH);
        /* 忙等待约500ms(72MHz下粗略估算) */
        for (i = 0; i < 36; i++) {
            delay_busy(100000);
        }
        rt_pin_write(PA1, PIN_LOW);
        for (i = 0; i < 36; i++) {
            delay_busy(100000);
        }
    }
}

/* 任务2:阻塞延时,LED2每500ms翻转一次 */
static void thread2_entry(void *param)
{
    while (1) {
        rt_pin_write(PA2, PIN_HIGH);
        rt_thread_delay(rt_tick_from_millisecond(500));  /* 阻塞延时500ms */
        rt_pin_write(PA2, PIN_LOW);
        rt_thread_delay(rt_tick_from_millisecond(500));
    }
}

int main(void)
{
    rt_thread_t thread1, thread2;
    
    /* 配置LED引脚为输出 */
    rt_pin_mode(PA1, PIN_MODE_OUTPUT);
    rt_pin_mode(PA2, PIN_MODE_OUTPUT);
    
    /* 创建任务1(忙等待) */
    thread1 = rt_thread_create("busy",
                               thread1_entry,
                               RT_NULL,
                               THREAD_STACK_SIZE,
                               THREAD_PRIORITY1,
                               10);
    if (thread1 != RT_NULL) {
        rt_thread_startup(thread1);
    }
    
    /* 创建任务2(阻塞延时) */
    thread2 = rt_thread_create("block",
                               thread2_entry,
                               RT_NULL,
                               THREAD_STACK_SIZE,
                               THREAD_PRIORITY2,
                               10);
    if (thread2 != RT_NULL) {
        rt_thread_startup(thread2);
    }
    
    return 0;
}

关键说明

  • 任务1优先级为8,任务2优先级为9(数值越小优先级越高)。任务1使用忙等待时始终占用CPU,任务2即使优先级较低也无法运行,LED2不闪烁。
  • 任务1的忙等待循环通过粗略估算实现约500ms延时,实际时间受编译器优化和系统时钟影响,不精确。
  • 任务2调用rt_thread_delay()主动挂起,调度器切换到空闲线程或其他就绪任务,LED2能够正常闪烁。
  • 若将任务1的优先级调低(如改为10),则任务2的阻塞延时期间,任务1的忙等待会抢占CPU,导致LED2闪烁周期被拉长,进一步体现忙等待对系统调度的干扰。

编译与运行

  1. 使用Keil MDK或GCC工具链编译工程,下载到STM32F103开发板。
  2. 打开串口调试助手(波特率115200),连接开发板USART1。

验证方法

观察现象:

  • 若LED1和LED2均以约1Hz频率交替闪烁(亮500ms灭500ms),说明两个任务都在正常运行。
  • 若LED2不闪烁或闪烁极慢,而LED1正常闪烁,说明任务1的忙等待占用了几乎全部CPU时间,导致低优先级任务2无法获得调度。

判定标准:当任务1优先级高于任务2时,LED2应几乎不闪烁(忙等待独占CPU);将任务1优先级改为低于任务2后重新编译运行,LED2恢复约1Hz闪烁,证明阻塞延时能正确释放CPU。通过此对比即可验证忙等待与阻塞延时的本质区别。