2. 进程启动时都做了什么?
下面先看一段程序启动的代码
// runtime/asm_amd64.s
TEXT runtime·rt0_go(SB),NOSPLIT,$0
......此处省略 N 多代码......
ok:
// set the per-goroutine and per-mach "registers"
get_tls(BX) // 将 g0 放到 tls(thread local storage) 里
LEAQ runtime·g0(SB), CX
MOVQ CX, g(BX)
LEAQ runtime·m0(SB), AX
// save m->g0 = g0 // 将全局 M0 与全局 G0 绑定
MOVQ CX, m_g0(AX)
// save m0 to g0->m
MOVQ AX, g_m(CX)
CLD // convention is D is always left cleared
CALL runtime·check(SB)
MOVL 16(SP), AX // copy argc
MOVL AX, 0(SP)
MOVQ 24(SP), AX // copy argv
MOVQ AX, 8(SP)
CALL runtime·args(SB) // 解析命令行参数
CALL runtime·osinit(SB) // 只初始化了 CPU 核数
CALL runtime·schedinit(SB) // 内存分配器、栈、P、GC 回收器等初始化
// create a new goroutine to start program
MOVQ $runtime·mainPC(SB), AX //
PUSHQ AX
PUSHQ $0 // arg size
CALL runtime·newproc(SB) // 创建一个新的 G 来启动 runtime.main
POPQ AX
POPQ AX
// start this M
CALL runtime·mstart(SB) // 启动 M0,开始等待空闲 G,正式进入调度循环
MOVL $0xf1, 0xf1 // crash
RET
在启动过程里主要做了这三个事情(这里只跟调度相关的):
- 初始化固定数量的 P
- 创建一个新的 G 来启动 runtime.main, 也就是 runtime 下的 main 方法
- 创建全局 M0、全局 G0,启动 M0 进入第一个调度循环
M0 是什么?程序里会启动多个 M,第一个启动的叫 M0。
G0 是什么?G 分三种,第一种是执行用户任务的叫做 G,第二种执行 runtime 下调度工作的叫 G0,每个 M 都绑定一个 G0。第三种则是启动 runtime.main 用到的 G。写程序接触到的基本都是第一种
我们按照顺序看是怎么完成上面三个事情的。
2.1 runtime.osinit(SB) 方法针对系统环境的初始化
这里实质只做了一件事情,就是获取 CPU 的线程数,也就是 Top 命令里看到的 CPU0、CPU1、CPU2…的数量。
// runtime/os_linux.go
func osinit() {
ncpu = getproccount()
}
2.2 runtime.schedinit(SB) 调度相关的一些初始化
// runtime/proc.go
// 设置最大 M 数量
sched.maxmcount = 10000
// 初始化当前 M,即全局 M0
mcommoninit(_g_.m)
// 查看应该启动的 P 数量,默认为 cpu core 数.
// 如果设置了环境变量 GOMAXPROCS 则以环境变量为准,最大不得超过_MaxGomaxprocs(1024) 个
procs := ncpu
if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
procs = n
}
if procs > _MaxGomaxprocs {
procs = _MaxGomaxprocs
}
// 调整 P 数量,此时由于是初始化阶段,所以 P 都是新建的
if procresize(procs) != nil {
throw("unknown runnable goroutine during bootstrap")
}
这里 sched.maxmcount 设置了 M 最大的数量,而 M 代表的是系统内核线程,因此可以认为一个进程最大只能启动 10000 个系统线程。
procresize 初始化 P 的数量,procs 参数为初始化的数量,而在初始化之前先做数量的判断,默认是 ncpu(与 CPU 核数相等)。也可以通过环境变量 GOMAXPROCS 来控制 P 的数量。_MaxGomaxprocs 控制了最大的 P 数量只能是 1024。
有些人在进程初始化的时候经常用到 runtime.GOMAXPROCS() 方法,其实也是调用的 procresize 方法重新设置了最大 CPU 使用数量。
2.3 runtime·mainPC(SB) 启动监控任务
// runtime/proc.go
// The main goroutine.
func main() {
......
// 启动后台监控
systemstack(func() {
newm(sysmon, nil)
})
......
}
在 runtime 下会启动一个全程运行的监控任务,该任务用于标记抢占执行过长时间的 G,以及检测 epoll 里面是否有可执行的 G。下面会详细说到。
2.4 最后 runtime·mstart(SB) 启动调度循环
前面都是各种初始化操作,在这里开启了调度器的第一个调度循环。(这里启动的 M 就是 M0)
下面来围绕 G、M、P 三个概念介绍 Goroutine 调度循环的运作流程。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论