Go中有一个特殊的线程,不绑定任何其他P,在死循环中不断进行一系列的监控操作,这些监控操作用于更好的服务于整个Go流程。它是sysmon监控线程。大家可能对它的作用很好奇,这里简单总结一下:释放空闲超过5分钟且超过2分钟没有垃圾回收的span物理内存,强制启动垃圾回收添加netpoll长时间未处理的结果放到任务队列中长期执行G任务发起抢占式调度,回收因为syscall而长时间阻塞的P。因此,可以看出sysmon线程就像一个主管,监视着整个进程的状态。是不是和我一样好奇这个话题是怎么开始的呢,一起来追根溯源吧。一、准备Go源码:v1.16.5IDE:goland操作系统:Centos知识储备:了解Go的启动过程,看作者文章《Go程序启动过程的一次追溯》Go的启动过程大致分为三个阶段:Go程序启动过程运行时启动并且初始化进程(runtime.main)执行用户代码(main.main)2.sysmon的启动过程可以从Go的启动过程中猜到。sysmon启动过程在runtime启动和初始化过程中。因此,我们从runtime.main开始一步步追查代码,找到sysmon的启动步骤。runtime/proc.gofuncmain(){...ifGOARCH!="wasm"{//nothreadsonwasmyet,sonosysmon//对于runtime_syscall_doAllThreadsSyscall,我们//registersysmonisnotreadyfortheworldtobe//stopped.//!!!找到启动sysmon的代码//在system在栈中生成一个新的M启动sysmonatomic.Store(&sched.sysmonStarting,1)systemstack(func(){newm(sysmon,nil,-1)})}...}//创建一个新的系统线程//Createanewm.Itwillstartoffwithacalltofn,orelsethescheduler.//fnneedstobestaticandnotaheapallocatedclosure.//Mayrunwithm.p==nil,sowritebarriersarenotallowed.////idisoptionalpre-allocatedmID.Omitbypassing-1.//go:nowritebarrierrecfuncnewm(),fn_punc{//获取M结构在GPM中,初始化一些字段//allocm方法很重要!!!//这个方法获取并初始化了M的结构体,同时也设置了M中系统线程要执行的方法fn,这里是sysmonmp:=allocm(_p_,fn,id)...//M属于Go中user状态代码中的一个结构体与系统线程是一对一的关系//每个系统线程如何执行代码,从哪里开始执行,由M结构体中的参数指定//创建在GPM结构体M结构体之后,开始创建对应的底层系统线程newm1(mp)}//分配一个系统线程给M//Allocateanewmunassociatedwithanythread.//Canusepforallocationcontextifneeded.//fnisrecordedasthenewm'sm.mstartfn.//idisoptionalpre-分配edmID.Omitbypassing-1.////这个函数允许有writebarriersevenifthecaller//不是因为它借用了_p_.////go:yeswritebarrierrecfuncallocm(_p_*p,fnfunc(),idint64)*m{...//Create一个newM,并进行一些初始化操作mp:=new(m)//M的执行方法,在runtime.mstart()方法中,最后调用fnmp.mstartfn=fn...}//逻辑funcnewm1(mp*m)在常规脚本中创建系统线程{...//!!!创建系统线程!!!newosproc(mp)...}runtime/os_linux.go//通过clone创建系统线程//Mayrunwithm.p==nil,sowritebarriersarenotallowed.//go:nowritebarrierfuncnewosproc(mp*m){...//Disablesignalsduringclone,sothatthenewthreadstarts//withsignalsdisabled.Itwillenabletheminminit.////注意://第五个参数mstart在runtime.mstartret:=clone(cloneFlags,stk,unsafe.Pointer(mp),unsafe.Pointer(mp.g0),unsafe.Pointer(funcPC(mstart)))...}//go:noescape//clone没有具体方法体,具体实现用汇编写funcclone(flagsint32,stk,mp,gp,fnunsafe.Pointer)int32clone()函数在linux系统中,用于创建轻量进程runtime/sys_linux_arm64.s//注意这里的void(*fn)(void)是runtime.mstart方法的地址入口////int64clone(int32flags,void*stk,M*mp,G*gp,void(*fn)(void));TEXTruntime·clone(SB),NOSPLIT|NOFRAME,$0...//Copymp,gp,fnoffparentstackforusebychild.MOVDmp+16(FP),R10MOVDgp+24(FP),R11MOVDfn+32(FP),R12//R12寄存器存放fn的地址...//判断是父进程,则直接返回//子进程跳转到child//Inparent,return.CMPZR,R0BEQchildMOVWR0,ret+40(FP)RETchild://Inchild,onnewstack.MOVD-32(RSP),R10MOVD$1234,R0CMPR0,R10BEQgood...good:...CMP$0,R10BEQnogCMP$0,R11BEQnog...nog://Callfn,调用fn,即runtime.mstartMOVDR12,R0//R12存放fnBL的地址(R0)//BL是跳转指令,跳转到fn...runtime.proc.go//mstart是M的一个执行入口//mstartistheentry-pointfornewMs.////Thismustnotsplitthestackbecausewemaynotevenhavestack//boundssetupyet.////mayrunduringSTW(因为它没有aPyet),sowrite//不允许有barriers///go:nosplit//go:nowritebarrierrecfuncmstart(){...mstart1()...}//启动M的具体方法funcmstart1(){_g_:=getg()...//M中的mstartfn指向runtime.sysmon,即fn=runtime.sysmoniffn:=_g_.m.mstartfn;fn!=nil{//执行runtime.sysmon//sysmon方法是一个死循环,所以执行sysmon的线程会一直在这里fn()}...}sysmon方法finallyexecuted//总是在没有outaP的情况下运行,所以writebarriersarenotallowed.////go:nowritebarrierrecfuncsysmon(){...for{...//获取超过10ms的netpoll结果////pollnetworkifnotpolledformorethan10mslastpoll:=int64(atomic.Load64(&sched.lastpoll))ifnetpollinited()&&lastpoll!=0&&lastpoll+10*1000*1000
