欢迎光临
我们一直在努力

FreeRTOS死锁是怎么产生的?一文彻底理解任务死锁机制

FreeRTOS死锁是怎么产生的?一文彻底理解任务死锁机制

在这里插入图片描述

在嵌入式实时系统开发中,FreeRTOS多任务并发运行提高了系统效率,但同时也引入了任务之间资源竞争的问题。其中最严重的问题之一就是——死锁(Deadlock)。

当系统出现死锁时,任务不会崩溃,也不会报错,而是表现为“程序突然卡住,某些功能永远不再执行”。

本文通过几个典型场景,结合 FreeRTOS 中 Mutex、信号量、队列、任务通知等机制,分析死锁产生的原因以及如何避免。


一、什么是FreeRTOS死锁?

简单来说:

死锁就是多个任务互相等待对方释放资源,导致所有任务都无法继续运行。

例如:

  • 任务A拿着资源1,等待资源2
  • 任务B拿着资源2,等待资源1

此时:

任务A → 等待任务B释放资源2

任务B → 等待任务A释放资源1

形成一个闭环。

最终结果:

任务A无法继续
任务B无法继续

系统卡死

FreeRTOS官方也指出,死锁通常发生在多个任务使用 Mutex 等互斥资源时,如果任务之间获取资源的顺序不一致,就可能形成互相等待。([GitHub][1])


二、死锁产生的四个必要条件

死锁并不是随机产生的,通常需要同时满足以下4个条件。

1. 互斥条件(Mutual Exclusion)

意思:

一个资源同一时间只能被一个任务占用。

例如:

xSemaphoreTake(Mutex, portMAX_DELAY);

任务A获取Mutex:

任务A
|

Mutex

此时任务B无法使用该资源。

FreeRTOS中的:

  • Mutex
  • 二值信号量

都具有互斥特性。([GitHub][1])


2. 请求并保持条件(Hold and Wait)

意思:

一个任务已经占有资源,同时还继续请求其他资源。

例如:

任务A:

take(Mutex1);

take(Mutex2);

执行过程:

任务A

已经拥有 Mutex1

继续申请 Mutex2

如果Mutex2被其他任务占用:

任务A等待Mutex2

但是不会释放Mutex1

这就是:

持有 + 等待


三、最经典案例:两个任务交叉申请Mutex导致死锁

场景:

系统有两个互斥资源:

Mutex1

Mutex2

两个任务:

任务A:

take(Mutex1);

take(Mutex2);

任务B:

take(Mutex2);

take(Mutex1);


运行过程

第一步

任务A运行:

任务A获取 Mutex1

状态:

Mutex1

任务A占用


第二步

任务B运行:

任务B获取 Mutex2

状态:

Mutex2

任务B占用


第三步

任务A继续:

申请:

Mutex2

但是:

Mutex2已经被任务B占用

所以:

任务A进入阻塞


第四步

任务B继续:

申请:

Mutex1

但是:

Mutex1已经被任务A占用

所以:

任务B进入阻塞

最终:

任务A等待任务B

任务B等待任务A

形成:

环路等待

结果:

死锁


四、持锁后进入阻塞,也可能产生死锁

很多开发者认为:

“我只有一个Mutex,不会死锁。”

实际上并不一定。

例如:

xSemaphoreTake(Mutex, portMAX_DELAY);

//等待消息
xQueueReceive(queue,
&data,
portMAX_DELAY);

xSemaphoreGive(Mutex);

问题:

任务获取Mutex以后:

进入等待状态。

但是:

Mutex没有释放。


此时另一个任务:

xSemaphoreTake(Mutex,
portMAX_DELAY);

会一直等待。

如果:

这个任务负责发送队列消息:

任务A等待消息

消息需要任务B发送

任务B等待Mutex

Mutex被任务A占用

形成:

任务A等待任务B

任务B等待任务A

最终死锁。


五、任务之间等待信号/消息导致死锁

FreeRTOS中常见通信方式:

  • 信号量
  • 队列
  • Task Notification

例如:

任务A:

等待任务B通知

任务B:

等待任务A消息

状态:

任务A

等待B

任务B

等待A

没有任何任务主动执行:

双方永久阻塞


六、死锁形成的根本原因总结

原因说明
资源互斥 一个资源只能一个任务使用
请求并保持 拿着资源继续等待其他资源
不可剥夺 资源只能主动释放
环路等待 任务之间形成等待闭环

四个条件同时满足:

产生死锁


七、FreeRTOS中常见死锁代码问题

1. Mutex嵌套顺序不一致

错误:

任务A:

Mutex1

Mutex2

任务B:

Mutex2

Mutex1


正确:

所有任务统一顺序:

Mutex1

Mutex2

例如:

所有任务规定:

先申请外设锁

再申请数据锁


2. 持锁状态调用阻塞函数

错误:

Take Mutex;

vTaskDelay();

QueueReceive();

Give Mutex;

问题:

阻塞期间:

Mutex一直占用。


正确:

Take Mutex;

//快速访问资源

Give Mutex;

QueueReceive();

原则:

不要持有锁等待事件。


3. 无限等待

错误:

xSemaphoreTake(
Mutex,
portMAX_DELAY
);

如果出现异常:

任务永久等待。

建议:

xSemaphoreTake(
Mutex,
100
);

设置超时时间。


八、如何避免FreeRTOS死锁?

方法1:统一资源申请顺序

例如:

规定:

所有任务

Mutex A

Mutex B

Mutex C

禁止:

B → A


方法2:缩短Mutex占用时间

不要:

获取Mutex

大量计算

等待消息

释放

应该:

获取Mutex

快速操作

释放Mutex


方法3:避免不必要共享资源

例如:

不要:

多个任务直接操作LCD

可以:

任务A

消息队列

LCD任务

由一个任务管理资源。


方法4:等待增加超时机制

不要:

portMAX_DELAY

推荐:

xSemaphoreTake(
mutex,
timeout
);

发生异常时:

主动恢复。


九、总结

FreeRTOS死锁本质不是系统错误,而是:

任务之间资源管理设计不合理导致的互相等待。

最常见的三种情况:

① Mutex交叉申请

任务A拿1等2

任务B拿2等1

② 持锁等待事件

拿锁

等待消息

别人无法获得锁

③ 任务互相等待通知

A等B

B等A

在实际嵌入式项目中,避免死锁最重要的原则:

统一资源申请顺序,减少持锁时间,不要持锁阻塞等待。


赞(0)
未经允许不得转载:171主机测评 » FreeRTOS死锁是怎么产生的?一文彻底理解任务死锁机制
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址