错误:程序直接卡死,用gdb调试发现是锁死
(gdb) bt #0 futex_wait (private=0, expected=2, futex_word=0xaaaaf5c81b10) at ../sysdeps/nptl/futex-internal.h:146 #1 __GI___lll_lock_wait (futex=futex@entry=0xaaaaf5c81b10, private=private@entry=0) at ./nptl/lowlevellock.c:49 #2 0x0000ffffa7188c74 in lll_mutex_lock_optimized (mutex=0xaaaaf5c81b10) at ./nptl/pthread_mutex_lock.c:48 #3 ___pthread_mutex_lock (mutex=0xaaaaf5c81b10) at ./nptl/pthread_mutex_lock.c:93 #4 0x0000ffff73dffcf4 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so #5 0x0000ffff73debea4 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so #6 0x0000ffff73d05634 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so #7 0x0000ffff7348487c in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so #8 0x0000ffff7348e254 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so #9 0x0000ffff733e0bcc in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so #10 0x0000ffff847ead14 in ??? () at /lib/aarch64-linux-gnu/libEGL_mesa.so.0 #11 0x0000ffff847e2a3c in ??? () at /lib/aarch64-linux-gnu/libEGL_mesa.so.0 #12 0x0000ffffb4108a18 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0 #13 0x0000ffffb39b6874 in g_object_unref () at /lib/aarch64-linux-gnu/libgobject-2.0.so.0 #14 0x0000ffffb5884290 in video_output_dispose(_GObject*) () at /home/holomotion/local/bin/ntsports/client_versions/lib/libmedia_kit_video_plugin.so
卡死点非常清晰:
media_kit_video_plugin.so 在 video_output_dispose() 里做销毁(#14),往下进入g_object_unred->libEGL_mesa.sp->rockchip_dri.so,最终卡在pthread_metex_lock(futex_wait)。
这说明正在销毁EGL/GL相关资源时,驱动里还有渲染中的帧/上下文在用,典型“还在画,先被拆”情形。
GL:提供一套图形渲染API,允许开发者用软件控制GPU绘制三维或二维的图形。开发者可以用GL来创建顶点、纹理、光照、着色器(shander)等。
EGL:负责图形上下文管理和显示系统交互。在OpenGL ES中,你需要一个EGL上下文才能开始渲染。它负责把GL渲染的内容输出到屏幕(framebuffer、窗口、离屏缓冲等)。
- 创建渲染上下文(EGLContext)
- 创建渲染表面(EGLSurface),例如屏幕窗口、离屏纹理;
- 绑定上下文和表面,使GL的命令能渲染到目标;
- 同步和交换缓冲区(swap buffers)
根据卡死原因,本人排查了所有实现video播放的逻辑。 发现主要有两个原因: 1、关闭硬件解码,资源暴涨; 2、短时间可能销毁、创建、静默多个video,资源占用。
开启硬件解码
Video(
controller: VideoController(widget.player,
configuration: VideoControllerConfiguration(
enableHardwareAcceleration:
Config.instance.enableHardwareAcceleration,
hwdec: 'no',
)),
fill: widget.fill,
fit: widget.fit,
controls: media_kit_video_controls.NoVideoControls,
);
hwdec:'no'代表强制禁用了硬件解码,这会导致高分辨率视频用软件解码把CPU卡死。
在Linux桌面上,如果视频是1080p/4K,高码率的话,CPU很容易跑满,Flutter UI就会卡死。
直接去掉。
创建全局单例 Player
class GlobalVideoPlayer {
GlobalVideoPlayer._internal() {
player = Player();
}
late final Player player;
static final GlobalVideoPlayer instance = GlobalVideoPlayer._internal();
}
class ReusableVideo extends StatefulWidget {
const ReusableVideo({
super.key,
this.fit = BoxFit.contain,
this.fill = Colors.white,
});
final BoxFit fit;
final Color fill;
@override
State<ReusableVideo> createState() => _ReusableVideoState();
}
class _ReusableVideoState extends State<ReusableVideo> {
late final Player _player;
@override
void initState() {
super.initState();
_player = GloablVideoPlayer.instance.player;
_closeVolume();
}
Future<void> _closeVolume() async {
await _player.setVolume(0);
}
@override
Widget build(BuildContext context) {
return Video(
controller: VideoController(_player,
configuration: VideoControllerConfiguration(
enableHardwareAcceleration:
Config.instance.enableHardwareAcceleration)),
fill: widget.fill,
fit: widget.fit,
controls: media_kit_video_controls.NoVideoControls,
);
}
}
直接调用就好了。
效果实现了,程序还在测试中...
测试了两天后,没在发生 libmedia_kit_video_plugin.so的卡死
但是最常见的卡死有发生,如下:
(gdb) bt #0 futex_wait (private=0, expected=2, futex_word=0xaaaae522e360) at ../sysdeps/nptl/futex-internal.h:146 #1 __GI___lll_lock_wait (futex=futex@entry=0xaaaae522e360, private=private@entry=0) at ./nptl/lowlevellock.c:49
#2 0x0000ffff7c7a8c74 in lll_mutex_lock_optimized (mutex=0xaaaae522e360) at ./nptl/pthread_mutex_lock.c:48
#3 ___pthread_mutex_lock (mutex=0xaaaae522e360) at ./nptl/pthread_mutex_lock.c:93
#4 0x0000ffff4908fcf4 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#5 0x0000ffff4907bea4 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#6 0x0000ffff48fb1114 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#7 0x0000ffff48fb19d8 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#8 0x0000ffff48fb2fe4 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#9 0x0000ffff48f952b4 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#10 0x0000ffff487e3118 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#11 0x0000ffff48670f9c in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#12 0x0000ffff60b6cd34 in ??? () at /lib/aarch64-linux-gnu/libEGL_mesa.so.0
#13 0x0000ffff60b62ed4 in ??? () at /lib/aarch64-linux-gnu/libEGL_mesa.so.0
#14 0x0000ffff75b14efc in ??? () at /lib/aarch64-linux-gnu/libEGL.so.1
#15 0x0000ffff8972c808 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#16 0x0000ffff896efe94 in gdk_gl_context_make_current () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#17 0x0000ffff896ffdd0 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#18 0x0000ffff896fffa0 in gdk_window_end_draw_frame () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#19 0x0000ffff89b5216c in ??? () at /lib/aarch64-linux-gnu/libgtk-3.so.0
#20 0x0000ffff899e070c in gtk_main_do_event () at /lib/aarch64-linux-gnu/libgtk-3.so.0
#21 0x0000ffff896e89b0 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#22 0x0000ffff896fc590 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#23 0x0000ffff897011fc in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#24 0x0000ffff89701400 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#25 0x0000ffff88fe8010 in ??? () at /lib/aarch64-linux-gnu/libgobject-2.0.so.0
#26 0x0000ffff88fe8178 in g_signal_emit_valist () at /lib/aarch64-linux-gnu/libgobject-2.0.so.0
#27 0x0000ffff88fe8234 in g_signal_emit () at /lib/aarch64-linux-gnu/libgobject-2.0.so.0
#28 0x0000ffff896f6ff8 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#29 0x0000ffff896e140c in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#30 0x0000ffff88e928c8 in ??? () at /lib/aarch64-linux-gnu/libglib-2.0.so.0
#31 0x0000ffff88e91790 in ??? () at /lib/aarch64-linux-gnu/libglib-2.0.so.0
#32 0x0000ffff88ef37a8 in ??? () at /lib/aarch64-linux-gnu/libglib-2.0.so.0
#33 0x0000ffff88e90b68 in g_main_context_iteration () at /lib/aarch64-linux-gnu/libglib-2.0.so.0
#34 0x0000ffff891388c0 in g_application_run () at /lib/aarch64-linux-gnu/libgio-2.0.so.0
#35 0x0000aaaae48e2204 in main ()
(gdb)
warning: 146 ../sysdeps/nptl/futex-internal.h: 没有那个文件或目录
(gdb) bt
#0 futex_wait (private=0, expected=2, futex_word=0xaaaae0c46ae0) at ../sysdeps/nptl/futex-internal.h:146
#1 __GI___lll_lock_wait (futex=futex@entry=0xaaaae0c46ae0, private=private@entry=0) at ./nptl/lowlevellock.c:49
#2 0x0000ffffae278c74 in lll_mutex_lock_optimized (mutex=0xaaaae0c46ae0) at ./nptl/pthread_mutex_lock.c:48
#3 ___pthread_mutex_lock (mutex=0xaaaae0c46ae0) at ./nptl/pthread_mutex_lock.c:93
#4 0x0000ffff7a3afcf4 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#5 0x0000ffff7a39bea4 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#6 0x0000ffff7a2d1114 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#7 0x0000ffff7a2d19d8 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#8 0x0000ffff7a2d2fe4 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#9 0x0000ffff7a2b52b4 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#10 0x0000ffff79a4f97c in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#11 0x0000ffff79986664 in ??? () at /usr/lib/aarch64-linux-gnu/dri/rockchip_dri.so
#12 0x0000ffff98b8c434 in ??? () at /lib/aarch64-linux-gnu/libEGL_mesa.so.0
#13 0x0000ffff98b7d0ec in ??? () at /lib/aarch64-linux-gnu/libEGL_mesa.so.0
#14 0x0000ffff98b6d2fc in ??? () at /lib/aarch64-linux-gnu/libEGL_mesa.so.0
#15 0x0000ffffbb1f8968 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#16 0x0000ffffbb1cfe70 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#17 0x0000ffffbb1cffa0 in gdk_window_end_draw_frame () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#18 0x0000ffffbb62216c in ??? () at /lib/aarch64-linux-gnu/libgtk-3.so.0
#19 0x0000ffffbb4b070c in gtk_main_do_event () at /lib/aarch64-linux-gnu/libgtk-3.so.0
#20 0x0000ffffbb1b89b0 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#21 0x0000ffffbb1cc590 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#22 0x0000ffffbb1d11fc in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#23 0x0000ffffbb1d1400 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#24 0x0000ffffbaab8010 in ??? () at /lib/aarch64-linux-gnu/libgobject-2.0.so.0
#25 0x0000ffffbaab8178 in g_signal_emit_valist () at /lib/aarch64-linux-gnu/libgobject-2.0.so.0
#26 0x0000ffffbaab8234 in g_signal_emit () at /lib/aarch64-linux-gnu/libgobject-2.0.so.0
#27 0x0000ffffbb1c6ff8 in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#28 0x0000ffffbb1b140c in ??? () at /lib/aarch64-linux-gnu/libgdk-3.so.0
#29 0x0000ffffba9628c8 in ??? () at /lib/aarch64-linux-gnu/libglib-2.0.so.0
#30 0x0000ffffba961790 in ??? () at /lib/aarch64-linux-gnu/libglib-2.0.so.0
#31 0x0000ffffba9c37a8 in ??? () at /lib/aarch64-linux-gnu/libglib-2.0.so.0
#32 0x0000ffffba960b68 in g_main_context_iteration () at /lib/aarch64-linux-gnu/libglib-2.0.so.0
#33 0x0000ffffbac088c0 in g_application_run () at /lib/aarch64-linux-gnu/libgio-2.0.so.0
#34 0x0000aaaac86d2204 in main ()
(gdb)
这个卡死已经不一样了,不是在销毁media_kit_video的对象时卡主,而是卡在GTK/GDK正在做gdk_gl_context_make_current(把某个GL上下文设为当前)的过程中,底层进入EGL_mesa -> rockchip_dri.so,最终在pthread_mutex_lock上等锁:
- #16
gdk_gl_context_make_current - #12–#4
libEGL_mesa.so/rockchip_dri.so - #0–#3 futex 等锁
这通常意味着:同时存在两个(或更多)OpenGL/EGL 上下文在 GTK/Flutter 的同一进程里争用驱动内部的一把锁。典型触发条件:
- 同屏存在多个 GL 源:例如 Video(media_kit 的 GLArea)+ 摄像头 Texture(你自己的 GL 纹理)+ Flutter 自己的 GL 合成;
- 页面切换/尺寸变化时,旧的 GL widget 正在结束一帧,新的 GL widget 又开始
make_current; - 还有一种是 在同一帧里仍有帧回调在推纹理(markTextureFrameAvailable / 渲染线程),GTK/GDK 又在做 end_draw_frame / make_current(#18→#16),两者撞锁。
撞锁
问题1:在同一帧里仍有帧回调在推纹理(markTextureFrameAvailable / 渲染线程),GTK/GDK 又在做 end_draw_frame / make_current(#18→#16),两者撞锁的时间线?
问题2:什么时候触发end_draw_frame 什么时候触发 make_current?
1. GTK/GDK 的渲染循环
GTK3/GTK4 基于 GDK,有一个 frame clock,每一帧都会经历:
- prepare → 确定需要重绘的区域
- paint → widget 渲染内容
- end_draw_frame → 最后收尾,把这帧交给后端(EGL/GL)
在 GL backend 下,end_draw_frame() 会:
- 调用
gdk_gl_context_make_current(context)(#16) 把 EGL 上下文设为当前; - 把这帧的 GL 指令提交到 GPU;
- 最后
eglSwapBuffers或类似操作。
所以可以理解为:每一帧 GTK 会在渲染结束阶段调用 make_current → swap。
2. Flutter 插件推纹理的时间线
Flutter 的 Texture 机制是这样的:
- 你通过
TextureRegistry.createSurfaceTexture()得到一个textureId; - 插件端有个 native 渲染器(比如摄像头/视频),会在某个线程里绘制到 EGLSurface/FBO 上;
- 每次有新的一帧渲染完毕,插件要调用
MarkTextureFrameAvailable(textureId); - Flutter engine 收到这个回调 → 把对应的 GL 纹理更新到合成树;
- 下一次 Flutter rasterizer 在 vsync 驱动的 drawFrame 中,会把这个纹理采样绘制进 Scene。
换句话说:插件在推帧的那一刻,必然需要“make_current 到它自己的 EGLContext / Surface” → 做 GL 操作 → release。
3. 两个时间线的冲突点
于是有了这样一种情况:
- GTK 主循环 正在做一帧 →
end_draw_frame()里要make_currentGTK 自己的 EGLContext; - 插件渲染线程 在差不多同一时刻也跑起来 → 要
make_current插件的 EGLContext 去绘制摄像头帧; - Rockchip/Mesa 驱动内部对这些 EGLContext 切换用了一个全局 mutex;
- 两边同时进来 → 死锁或长时间卡在 futex。
所以我说的 “在同一帧里,GTK/GDK 正在收尾,而插件还在推纹理” ,指的就是这两条时间线重叠。
4. 什么时候触发 end_draw_frame / make_current
-
end_draw_frame:
每次 GTK 窗口重绘的收尾阶段都会触发,一般跟系统 vsync 节奏(60Hz 或 30Hz)保持一致。触发原因可能是:- widget 更新(你
setState导致重绘) - 窗口暴露/尺寸变化
- 定时刷新的动画
- widget 更新(你
-
插件的
make_current:
每次插件要绘制一帧新纹理时触发(比如摄像头 30fps / 视频解码 25fps)。这条时间线独立于 GTK,不保证和 GTK 的 vsync 对齐。
所以两个节奏是异步的,一旦同时命中某一瞬间,就会在 EGL mutex 上撞到。
5. 怎么避免撞锁
- 错开时间线:
在页面切换/移除 widget 的时候,先停掉帧回调(不再调用markTextureFrameAvailable),再等 GTK 完成那一帧的end_draw_frame→ 这就是我上次建议加addPostFrameCallback + 延迟 30–50ms的原因。 - 单源化:
同屏只留一个 GL source,减少两条独立渲染线程同时跑的机会。 - 节流:
插件端推帧不要跟 GTK 主循环一样跑满 60fps,降到 25–30fps 就已经能显著减少撞锁概率。
👉 总结成一句话:
GTK 的 end_draw_frame → make_current 是系统帧循环的收尾;插件的 make_current 是你主动推纹理的时机。这两者如果同时抢驱动的 EGL mutex,就会卡死。解决办法就是错开它们(先停推帧,再等 GTK 收尾),或者避免同时存在多个 GL 源。
GL source是什么?
1. 什么是 GL source?
这里我说的 GL source 其实指的是 一个独立的 OpenGL/EGL 渲染来源,也就是 一条能往 GPU 提交绘制指令的“源头” 。
比如:
- GTK/GDK:窗口系统本身会有一个 EGLContext,它负责绘制 GTK widget、合成 Flutter embedder 输出 → 这是一个 GL source。
- Flutter engine:Flutter 的 rasterizer(Skia + OpenGL 后端)本身也会占用一个 EGLContext → 这是另一个 GL source。
- 插件(video/camera/media_kit) :如果插件内部用 EGL/FBO 渲染视频帧,那它自己也会创建一个 EGLContext / Surface 来绘制 → 这是第三个 GL source。
2. 为什么多个 GL source 会有冲突?
- EGL 驱动(比如 Rockchip/Mesa)内部,同一个 GPU 设备上的所有 EGLContext 切换都是串行的。
- 每个
makeCurrent()/eglSwapBuffers()背后都会上一个全局锁(就是你看到的 futex)。 - 当有多个 GL source(GTK、Flutter、插件)在不同线程里频繁切换时,很容易撞锁 → 卡死。
换句话说:GL source 就像多个厨师同时抢一个灶台,每次换人都要停顿、锁上 → 如果两个同时要炒菜,就卡在门口了。
3. 怎么理解到你的场景?
在 Flutter Linux + GTK + media_kit 里,通常至少有 三个 GL source:
- GTK/GDK → 负责整个窗口渲染循环(#18 → #16 调用
make_current)。 - Flutter engine → rasterizer 绘制 Flutter widget 树。
- media_kit/video 插件 → 用 FFmpeg + OpenGL,把视频帧画到 FBO,再推给 Flutter
Texture。
所以一旦 GTK 在收尾 (end_draw_frame) + 插件刚好推新纹理 (markTextureFrameAvailable) → 两个 GL source 都要切换 EGLContext,就会撞到全局 mutex。
4. 如何减少 GL source 冲突?
- 尽量减少 GL source 数量
→ 插件能复用 Flutter 的 EGLContext 最好,不要自己新建。 - 串行化渲染
→ 在 Flutter vsync 循环里调度插件绘制,避免独立线程乱抢。 - 降频 / 节流
→ 插件推帧不需要 60fps,25–30fps 就够,降低锁竞争。 - 退出页面时及时停掉插件推帧
→ 避免 “页面 dispose 了,但视频线程还在画”。
✅ 总结:
GL source = 一个独立的 EGL/OpenGL 渲染上下文(渲染来源)。
GTK/GDK、Flutter、插件各自都是 GL source。多个 GL source 在不同线程同时抢 GPU context 时,就会卡在驱动里的 futex 锁。
🧩 GDB 死锁调试笔记(图形驱动相关)
一、常用调试命令
1️⃣ 查看所有线程的调用栈
(gdb) bt # 当前线程的调用栈
(gdb) info threads # 查看所有线程
(gdb) thread apply all bt # 打印所有线程的调用栈
2️⃣ 查看特定锁状态
当怀疑死锁与某个 pthread_mutex_t 有关时,可直接查看锁结构体内容:
(gdb) print *(pthread_mutex_t*)0xaaaaf68f4000
(gdb) print *(pthread_mutex_t*)0xaaaaf68ea938
💡 说明:
0xaaaaf68f4000和0xaaaaf68ea938为锁的内存地址。- 输出内容可判断锁的
__data.__owner、__count、__lock状态,从而判断是否被占用或死锁。
3️⃣ 查看具体线程状态
查看某个线程(如线程号 49)的详细信息与调用堆栈:
(gdb) thread 49
(gdb) bt
二、调试分析结论
-
死锁并非源于业务逻辑锁(文件操作锁)。
- 调查的两个锁地址均指向图形驱动(非应用代码)。
-
问题出在图形驱动内部。
- 死锁发生在 Mesa 驱动 与 GTK/Flutter 图形栈 的锁顺序不一致上。
- 属于 驱动内部锁竞争/顺序反转问题。
-
硬件加速是根本原因。
- 硬件平台:Rockchip
- 问题来源:Rockchip 平台的
Mesa实现存在线程锁定 bug,导致 GPU 渲染线程与 GUI 主线程相互等待。
三、进一步建议
-
⚙️ 临时解决方案:
- 禁用 GPU 硬件加速,改为软件渲染(CPU)。
- 可通过环境变量:
测试发现,禁止后Flutter不在渲染页面。export LIBGL_ALWAYS_SOFTWARE=1 - 或 Flutter 启动参数关闭 GPU 加速。
-
🧠 长期解决方向:
- 使用更新的 Mesa 驱动版本。
- 或切换到稳定的图形栈(如 Wayland + EGL 软件栈)。
- 若为嵌入式 Flutter,可考虑
--enable-software-rendering参数运行调试。
✅ 小结
| 问题点 | 结论 |
|---|---|
| 死锁线程 | Mesa + Flutter 图形栈 |
| 锁地址 | 图形驱动内部 Mutex |
| 根因 | 硬件加速引起的驱动层锁争用 |
| 临时方案 | 禁用 GPU 硬件加速 |
| 永久方案 | 升级 Mesa 驱动或切换软件渲染方式 |