栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

Linux 4.xx内核+Go 1.15出core排查

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Linux 4.xx内核+Go 1.15出core排查

Go runtime出core

在使用gozero框架的开发数据库管理服务的过程中,经常出core。通过dlv查看core的位置,各个地方都有,典型的有以下两种:

  1. runtime在执行init函数时,某个空串对应的地址上其长度不对,应该是0,却是一个负值。
  2. runtime中初始化timer时,timer的某个应该为正整数的属性,变成了负值。

明确是踩了内存,但一时间找不到思路。

0x01 怀疑是CGO

由于CGO和Go本身使用的内存模型不同,CGO比较容易出现踩内存的问题。加上出core的程序,为了使用一个公共库,服务链接了一个CGO库,所以开始也怀疑是CGO的问题。一开始也不能稳定地复现出来。

  1. 不使用CGO编译一个可执行程序。在命令行中,开启ulimited后,反复调用,出core。使用的复现脚本如下。
while [ 1 ]; do
   ./main
 done

这种方式可以复现之后,就排除了CGO的问题。

0x02 怀疑是三方库的问题

使用如下的代码,可以稳定复现。

package main

import _ "gopkg.in/yaml.v2"

func main() {
}

对于出现的core,使用dlv和gdb都看过,同样是没有头绪。后来同事发现说,踩的内存模式是16bytes一组的。把怀疑点指向了Go中使用的AVX寄存器,intel处理器中,AVX寄存器有128bit,和256bit的模式,而且Go的内存分配时,也正是使用了AVX寄存器来初始化分配的内存。

尝试使用以下方法确认是否是AVX寄存器内容异常的锅。
将Go运行时中,初始化内存的Plan9汇编代码进行了修改,如下所示,不使用AVX寄存器。

TEXT ·TestClear(SB), NOSPLIT, $0-16
	MOVQ	ptr+0(FP), DI
	MOVQ	n+8(FP), BX
	XORQ	AX,AX
redo:
	TESTQ	BX, BX
	JEQ	_0
	CMPQ	BX, $2
	JBE	_1or2
	CMPQ	BX, $4
	JBE	_3or4
	CMPQ	BX, $8
	JB	_5through7

// 8的整数倍
_8:
	MOVQ 	AX, (DI)
	SUBQ	$8, BX
	ADDQ 	$8, DI
	JMP redo

_1or2:
	MOVB	AX, (DI)
	MOVB	AX, -1(DI)(BX*1)
	RET
_0:
	RET
_3or4:
	MOVW	AX, (DI)
	MOVW	AX, -2(DI)(BX*1)
	RET
_5through7:
	MOVL	AX, (DI)
	MOVL	AX, -4(DI)(BX*1)
	RET

重新编译后,仍然会出core。再次失去了方向。

0x03 柳暗花明

当时已经容易复现出来了,PM也没有太着急了。然后尝试在Go社区看有没有人遇到同样的异常。搜runtime的issue出来的也有许多。只到偶尔看到了这个issue https://hub.fastgit.org/golang/go/issues/40722,随机崩溃的异常,跟我遇到的有点相似。其实仔细一看,并不是十分符合。主要差别点有:

  1. issue中说到的内核版本是3.18。而我们的内核版本是4.xx的。
  2. 它的异常是bad p,我们的异常更随机,都是非法长度非法内存类的异常。

不过仔细看了issue中社区大牛的回复,说是5.x内核与go 1.14之后的版本相处的不太好,主要是因为内核在FPU寄存器使用上有个BUG[https://lkml.org/lkml/2019/11/27/362],需要是GCC9才能触发。社区专门有个页面跟踪这个问题https://golang.org/wiki/LinuxKernelSignalVectorBug。但与我们的情况又不完全相符,我们的内核是4.xx,GCC是5.x的。在查看了patch之后,发现我们在用的内核源码中,也有同样的逻辑,也就是说问题可能是存在的。

自己尝试合入了内核的patch后,编译出了vm,替换到系统上重启生效后,真的就不出core了。问题解决了,还是要分析下那个内核问题。

0x04 内核问题

Go 1.14之后引入了异步抢占调度特性。这个特性是非常好的,可以最大限度地利用CPU,实现高并发。简单来说是,当一个G阻塞时,它占用着一个内核线程,单纯从用户态,无法让这个线程中断执行。Go的调度器很不幸就是工作在用户态的。所以为了实现异步抢占,Go 1.14之后引入基于信号中断机制的抢占机制。当一个G运行在一个M上时,向M发送抢占信号,强制M退出当前上下文,进入到中断处理逻辑中,这时调度器就可以调度其他G了。详细分析可以参考这篇bloghttps://www.cnblogs.com/luozhiyun/p/14589730.html。

按照内核patch的描述,可知内核某些场景下,会把fpu寄存器内容保存在内存中,如果刚好保存后,触发了缺页中断,这时其他进程可能会修改了fpu寄存器的内容。缺页中断返回后,内核将控制权交还给用户态,就会导致之前保存的fpu内容不是进程期望的。而Go的异步抢占刚好要使用信号机制,对fpu寄存器(包含AVX YMM/XMM寄存器)有依赖,所以就会有各种异常了。

0x05 总结

这个问题的解决过程虽然有点运气成份,但之前的思路是正确的。一开始能稳定复现后,就确定不是Go自己的问题的。处理这种随机出现的异常时,就要尽可能地加快它的复现频率,试探出它的复现条件。思路就会慢慢出现了。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/945658.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号