追溯Go中sysmon的启动过程

在Go中有一个特殊的线程,它不与其他任何P进行绑定。在一个死循环之中不停的执行一系列的监控操作,通过这些监控操作来更好的服务于整个Go进程,它就是——sysmon监控线程。

你可能会好奇它的作用,这里简单总结一下:

释放闲置超过5分钟的span物理内存超过2分钟没有垃圾回收,强制启动垃圾回收将长时间没有处理的netpoll结果添加到任务队列向长时间执行的G任务发起抢占调度收回因syscall而长时间阻塞的P因此可以看出,sysmon线程就像监工一样,监控着整个进程的状态。你会不会跟我一样好奇这个线程是怎么启动起来的,一起来追溯吧。

1. 准备工作

Go源码:v1.16.5IDE:goland操作系统:Centos知识储备:了解Go启动过程,见笔者文章《Go程序启动过程的一次追溯》Go的启动过程大概分为三个阶段

1. Go程序的引导过程

2. runtime的启动以及初始化过程(runtime.main)

3. 执行用户代码(main.main)

2. sysmon启动过程追溯

由Go的启动过程大概可以猜出来,sysmon的启动过程在runtime的启动以及初始化过程之中。所以,我们从runtime.main开始一步步的追溯代码,来寻找sysmon的启动步骤。

runtime/proc.go

func main() {

...

if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon

// For runtime_syscall_doAllThreadsSyscall, we

// register sysmon is not ready for the world to be

// stopped.

// !!! 找到了 启动sysmon的代码

// 在系统栈内生成一个新的M来启动sysmon

atomic.Store(&sched.sysmonStarting, 1)

systemstack(func() {

newm(sysmon, nil, -1)

})

}

...

}

// 创建一个新的系统线程

// Create a new m. It will start off with a call to fn, or else the scheduler.

// fn needs to be static and not a heap allocated closure.

// May run with m.p==nil, so write barriers are not allowed.

//

// id is optional pre-allocated m ID. Omit by passing -1.

//go:nowritebarrierrec

func newm(fn func(), _p_ *p, id int64) {

// 获取GPM中M结构体,并进行部分字段的初始化

// allocm方法非常重要!!!

// 该方法获取并初始化M的结构体,还在M里面设置了系统线程将要执行的方法fn,这里是sysmon

mp := allocm(_p_, fn, id)

...

// M在Go中属于用户态代码中的一个结构体,跟系统线程是一对一的关系

// 每个系统线程怎么执行代码,从哪里开始执行,则是由M的结构体中参数来指明

// 创建GPM中结构体M结构体之后,开始创建对应的底层系统线程

newm1(mp)

}

// 给M分配一个系统线程

// Allocate a new m unassociated with any thread.

// Can use p for allocation context if needed.

// fn is recorded as the new m's m.mstartfn.

// id is optional pre-allocated m ID. Omit by passing -1.

//

// This function is allowed to have write barriers even if the caller

// isn't because it borrows _p_.

//

//go:yeswritebarrierrec

func allocm(_p_ *p, fn func(), id int64) *m {

...

// 创建新的M,并且进行一些初始化操作

mp := new(m)

// M 的执行方法, 在runtime.mstart()方法中最终调用fn

mp.mstartfn = fn

...

}

// 楷书创建系统线程的逻辑

func newm1(mp *m) {

...

// !!!创建系统线程!!!

newosproc(mp)

...

}runtime/os_linux.go

// 通过clone创建系统线程

// May run with m.p==nil, so write barriers are not allowed.

//go:nowritebarrier

func newosproc(mp *m) {

...

// Disable signals during clone, so that the new thread starts

// with signals disabled. It will enable them in minit.

//

// 注意:

// 第5个参数 mstart 是在 runtime.mstart

ret := clone(cloneFlags, stk, unsafe.Pointer(mp), unsafe.Pointer(mp.g0), unsafe.Pointer(funcPC(mstart)))

...

}

//go:noescape

//clone没有具体方法体,具体实现使用汇编编写

func clone(flags int32, stk, mp, gp, fn unsafe.Pointer) int32clone()函数在linux系统中,用来创建轻量级进程

runtime/sys_linux_arm64.s

// 注意 这里的void (*fn)(void) 就是 runtime.mstart 方法的地址入口

//

// int64 clone(int32 flags, void *stk, M *mp, G *gp, void (*fn)(void));

TEXT runtime·clone(SB),NOSPLIT|NOFRAME,$0

...

// Copy mp, gp, fn off parent stack for use by child.

MOVD mp+16(FP), R10

MOVD gp+24(FP), R11

MOVD fn+32(FP), R12 // R12寄存器存储fn的地址

...

// 判断是父进程,则直接返回

// 子进程则跳到 child

// In parent, return.

CMP ZR, R0

BEQ child

MOVW R0, ret+40(FP)

RET

child:

// In child, on new stack.

MOVD -32(RSP), R10

MOVD $1234, R0

CMP R0, R10

BEQ good

...

good:

...

CMP $0, R10

BEQ nog

CMP $0, R11

BEQ nog

...

nog:

// Call fn, 调用 fn,即 runtime.mstart

MOVD R12, R0 // R12中存放的是fn的地址

BL (R0) // BL是一个跳转指令,跳转到fn

...runtime.proc.go

// mstart是一个M的执行入口

// mstart is the entry-point for new Ms.

//

// This must not split the stack because we may not even have stack

// bounds set up yet.

//

// May run during STW (because it doesn't have a P yet), so write

// barriers are not allowed.

//

//go:nosplit

//go:nowritebarrierrec

func mstart() {

...

mstart1()

...

}

// 开始执行M的具体方法

func mstart1() {

_g_ := getg()

...

// M中mstartfn指向 runtime.sysmon, 即 fn = runtime.sysmon

if fn := _g_.m.mstartfn; fn != nil {

// 即:执行 runtime.sysmon

// sysmon方法是一个死循环,所以说执行sysmon的线程会一直在这里

fn()

}

...

}最终执行的sysmon方法

// Always runs without a P, so write barriers are not allowed.

//

//go:nowritebarrierrec

func sysmon() {

...

for {

...

// 获取超过10ms的netpoll结果

//

// poll network if not polled for more than 10ms

lastpoll := int64(atomic.Load64(&sched.lastpoll))

if netpollinited() && lastpoll != 0 && lastpoll+10*1000*1000 < now {

atomic.Cas64(&sched.lastpoll, uint64(lastpoll), uint64(now))

list := netpoll(0) // non-blocking - returns list of goroutines

if !list.empty() {

// Need to decrement number of idle locked M's

// (pretending that one more is running) before injectglist.

// Otherwise it can lead to the following situation:

// injectglist grabs all P's but before it starts M's to run the P's,

// another M returns from syscall, finishes running its G,

// observes that there is no work to do and no other running M's

// and reports deadlock.

incidlelocked(-1)

injectglist(&list)

incidlelocked(1)

}

}

...

// 抢夺syscall长时间阻塞的P,向长时间阻塞的P发起抢占调度

//

// retake P's blocked in syscalls

// and preempt long running G's

if retake(now) != 0 {

idle = 0

} else {

idle++

}

// 检查是否需要强制执行垃圾回收

// check if we need to force a GC

if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {

lock(&forcegc.lock)

forcegc.idle = 0

var list gList

list.push(forcegc.g)

injectglist(&list)

unlock(&forcegc.lock)

}

...

}

...

}

总结

由以上可知,sysmon线程的创建过程经过几个阶段:

创建M结构体,对该结构初始化并绑定系统线程将要执行的方法sysmon为M创建对应的底层系统线程(不同的操作系统生成方式不同)引导系统线程从mstart方法开始执行sysmon逻辑(sysmon方法是死循环)sysmon线程启动之后就进入监控整个Go进程的逻辑中,至于sysmon都做了些什么,有机会再一起探讨。

原文地址:https://www.toutiao.com/article/6979916787639337509/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340