案例:应用系统卡死
问题现象
某软件在服务器运行,出现卡死问题。
定位过程
- 配置kdump服务,配置内核参数。
- 复现问题,确认生成vmcore文件,使用crash调试,如下图所示,确认空指针问题。
- 查看报错堆栈,如下图所示,怀疑“inet_sock_destruct”函数出错。
- 反汇编“inet_sock_destruct”,如下图所示。
- 查看“inet_sock_destruct”函数源码,如下图所示。
结合汇编和源码分析可知,x2寄存器为空指针地址,意味着“__skb_dequeue”为空,但前面业务是有做“__skb_dequeue”是否为空的判断,按照正常业务逻辑,不应该进入kfree_skb函数。这说明同时有其他线程在操作“__skb_dequeu”e,在本次线程完成“__skb_dequeue”为非空判断后,其他线程操作使“__skb_dequeue”变为空,导致本次线程后续操作报错。
- 为进一步分析该问题,需要了解还有其他什么线程会对“__skb_dequeue”进行操作。这需要在内核增加调试信息,保存每次对__skb_dequeue操作时的堆栈信息。如下图所示,修改代码,增加保存对“__skb_dequeue”操作时的堆栈信息。
- 重新编译内核,运行程序,生成新的vmcore文件,使用Crash调试,按照上述定位思路再定位。
- 分析堆栈调用逻辑为: 。
- 重新运行,生成第三份vmcore文件,使用crash调试,按照上述定位思路再定位。
- 从两次vmcore文件的分析,确认有两条路径同时操作“__skb_dequeue”,具体调用关系如下图所示。
当多核处理不同逻辑时,可能存在对“__skb_dequeue”操作不同步的情况,从而导致队列被重复释放,导致死机问题出现。
- 为保证操作的一致性,不出现重复释放的问题,在释放操作sk_free前入加内存屏障,重新编译内核,运行,确认问题解决。
1 2 3 4 5 6 7 8
void __sock_wfree(struct sk_buff *skb) { struct sock *sk = skb->sk; if (refcount_sub_and_test(skb->truesize, &sk->sk_wmem_alloc)) { smp_rmb(); // 添加内存屏障验证问题解决 __sk_free(sk); } }
- 后续排查内核社区,发现社区在5.1版本在“refcount_sub_and_test”函数中加了内存屏障修复相关问题,也可以通过更新内核版本解决该问题。
父主题: 系统卡死