freertos 临界区和开关中断
在实时操作系统(RTOS)中,确保任务或中断服务例程(ISR)访问共享资源时的正确性和一致性是非常重要的。进入临界区和开关中断是实现这一目标常用的两种技术,它们的目的相似,但实现方式和适用场景有所不同。
在实时操作系统(RTOS)中,确保任务或中断服务例程(ISR)访问共享资源时的正确性和一致性是非常重要的。进入临界区和开关中断是实现这一目标常用的两种技术,它们的目的相似,但实现方式和适用场景有所不同。
进入临界区
临界区是指一个访问共享资源(如全局变量、硬件资源等)的程序代码段,这部分代码在任意时刻只能由一个任务或中断服务例程执行。进入临界区的目的是防止多任务或多中断在访问共享资源时发生冲突。
在RTOS中,进入临界区通常通过以下步骤实现:
- 禁止任务切换:在进入临界区之前,系统会禁止任务切换,这样可以防止在执行临界区代码时被其他任务打断。
- 执行临界区代码:在确保没有其他任务可以执行的情况下,执行访问共享资源的代码。
- 允许任务切换:执行完临界区代码后,重新允许任务切换。
进入临界区并不关闭中断,它只是阻止了其他任务的执行。在中断仍然开启的情况下,中断服务例程仍然可以执行,这意味着中断服务例程也需要考虑临界区的保护。
开关中断
开关中断是指在中断服务例程或任务中暂时关闭中断,以保护临界区代码不被中断。
开关中断的步骤如下:
- 关闭中断:在访问共享资源之前关闭中断,确保在执行临界区代码时不会被任何中断打断。
- 执行临界区代码:在关闭中断的情况下安全地执行访问共享资源的代码。
- 打开中断:执行完临界区代码后,重新开启中断。
区别
-
适用范围:
- 进入临界区通常用于多任务环境中,确保任务间的同步和互斥。
- 开关中断用于确保在执行某些重要代码时不会被任何中断打断,适用于中断服务例程和任务。
-
执行效率:
- 进入临界区可能只涉及任务调度器的控制,而不一定涉及硬件层面的中断控制,效率可能更高。
- 开关中断涉及硬件层面的操作,频繁地开关中断可能会影响系统的响应性和中断延迟。
-
资源保护:
- 进入临界区通常是为了保护任务间的共享资源。
- 关闭中断是为了保护代码段,防止被中断服务例程打断。
-
实现复杂性:
- 进入临界区的实现通常依赖于RTOS提供的API。
- 开关中断直接与硬件操作相关,需要谨慎使用,以避免系统错误。
taskYIELD() 任务切换。
taskENTER_CRITICAL() 进入临界区,用于任务中。
taskEXIT_CRITICAL() 退出临界区,用于任务中。
taskENTER_CRITICAL_FROM_ISR() 进入临界区,用于中断服务函数中。
taskEXIT_CRITICAL_FROM_ISR() 退出临界区,用于中断服务函数中。
taskDISABLE_INTERRUPTS() 关闭中断。
taskENABLE_INTERRUPTS() 打开中断。
vTaskStartScheduler() 开启任务调度器。
vTaskEndScheduler() 关闭任务调度器。
vTaskSuspendAll() 挂起任务调度器。
xTaskResumeAll() 恢复任务调度器。
vTaskStepTick() 设置系统节拍值。
/* 假设我们有一个共享资源,这里是一个全局变量 */
volatile int sharedResource = 0;
void functionThatAccessesSharedResource(int value)
{
/* 进入临界区,关闭中断 */
vPortEnterCritical();
/* 在这里执行对共享资源的操作 */
sharedResource = value; /* 写入共享资源 */
// int temp = sharedResource; /* 读取共享资源 */
/* 离开临界区,恢复中断 */
vPortExitCritical();
/* 可以在这里使用 temp 变量进行其他操作 */
}
分析portENTER_CRITICAL和portEXIT_CRITICAL的代码:
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
进入临界段和退出临界段是通过函数调用开关中断函数 portENABLE_INTERRUPTS 和 portDISABLE_INTERRUPTS 实现的。
在RTOS中,进入临界区的目的是为了保护一段代码,防止它在中途被中断或被其他任务抢占,从而确保对共享资源的访问是原子的。在临界区内,应该避免执行任何可能导致任务阻塞的操作,包括延时处理。
以下是为什么在临界区内不应该加延时处理的原因:
-
阻塞行为:如果在临界区内调用延时(如
vTaskDelay),则当前任务将被阻塞,并且RTOS调度器将切换到另一个就绪状态的任务。但是,由于当前任务仍然处于临界区,它并没有退出临界区,这可能导致其他任务或中断无法访问它们需要的共享资源。 -
临界区时长:临界区应该尽可能地短,以减少对系统响应性的影响。长时间占用临界区可能会导致系统性能下降,因为其他任务和中断被阻止访问共享资源。
-
调度器锁定:在许多RTOS中,进入临界区通常会导致调度器被锁定,这意味着任务切换不会发生,直到退出临界区。如果在临界区内执行延时,则调度器将保持锁定状态,这会阻止其他任务运行,即使它们已经准备好执行。
如果你需要在任务中添加延时,应该在做完临界区的操作后,退出临界区,然后再执行延时。
举例说明:
#include "FreeRTOS.h"
#include "task.h"
#include<stdio.h>
#include "timers.h"
void vApplicationMallocFailedHook() {
while(1);
}
void vApplicationStackOverflowHook(TaskHandle_t xTask, char * pcTaskName ) {
while(1);
}
#include "FreeRTOS.h" // 包含FreeRTOS头文件
volatile int global_counter = 0; // 假设这是一个需要在多任务环境中共享的计数器
#include "FreeRTOS.h"
#include "task.h"
void vTaskIncrementCounter(void *pvParameters) {
for (;;) {
// 进入临界区
vPortEnterCritical();
// 安全地修改全局变量
while(1)
{
global_counter++;
if(global_counter % 5 == 0)
{
break;
}
//vTaskDelay(pdMS_TO_TICKS(1000));//硬件延时处理,临界区内部不能加这样的处理
}
// 离开临界区
vPortExitCritical();
printf("The global counter write value is %d\n", global_counter);
// 暂停任务一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void vTaskReadCounter(void *pvParameters) {
int counter_value;
for (;;) {
// 进入临界区
vPortEnterCritical();
// 安全地读取全局变量
counter_value = global_counter;
// 离开临界区
vPortExitCritical();
// 可以在这里使用 counter_value,例如打印它
printf("The global counter read value is %d\n", counter_value);
// 暂停任务一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
int main(void) {
// 创建任务
xTaskCreate(vTaskIncrementCounter, "IncrementTask", 1000, NULL, 1, NULL);
xTaskCreate(vTaskReadCounter, "ReadTask", 1000, NULL, 1, NULL);
// 启动调度器
vTaskStartScheduler();
// 如果一切正常,以下代码不会被执行
for (;;);
}
lark@lark-VMware-Virtual-Platform:~/public/freertos_linux/build$ ./main
The global counter read value is 1
The global counter write value is 5
The global counter read value is 5
The global counter write value is 10
The global counter read value is 10
The global counter write value is 15
The global counter read value is 15
The global counter write value is 20
The global counter read value is 20
The global counter write value is 25
The global counter read value is 25
The global counter write value is 30
The global counter read value is 30
The global counter write value is 35
The global counter read value is 35
补充:在RTOS中,如果在临界区中需要执行硬件操作,并且该操作可能需要一些时间来完成(例如,等待硬件响应),那么你应该遵循以下原则来处理:
-
最小化临界区:确保临界区只包含必要的操作,尤其是那些需要原子性保护的操作。
-
在临界区外等待:如果硬件操作需要等待,那么应该在退出临界区后进行等待。
void TaskWithHardwareOperation(void *pvParameters) {
for (;;) {
// 进入临界区
taskENTER_CRITICAL();
// 执行需要保护的硬件操作,但不要在临界区内等待
StartHardwareOperation(); // 假设这个函数启动硬件操作,但不等待其完成
// 离开临界区
taskEXIT_CRITICAL();
// 在退出临界区后等待硬件操作完成
WaitForHardwareOperationToComplete(); // 假设这个函数等待硬件操作完成
// 可以在这里处理硬件操作的结果
// 暂停任务一段时间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void StartHardwareOperation(void) {
// 启动硬件操作,但不等待其完成
// ...
}
void WaitForHardwareOperationToComplete(void) {
// 等待硬件操作完成
// 这可能涉及到轮询硬件状态,或者使用中断服务例程(ISR)和事件标志组来通知操作完成
// ...
}
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)