runtime及其初始化(一)

来源:1-4 Go 程序是怎么跑起来的

东泽XD

2021-06-03 20:06:35

为了正确对后续问题进行提问的铺垫提问(老师概括性地回答即可,必要的地方可以指出代码位置和探索方式),主要是对rt0_go过程(包括rt0_go结束)中涉及runtime.main与main.main的GMP、schedule产生的疑问

: from runtime/proc.go line 592
The bootstrap sequence is:/

call osinit
call schedinit
make & queue new G
call runtime·mstart

The new G calls runtime·main.

上述是package runtime下runtime bootstrap sequence注释,围绕这几个过程提出几个概念性问题

  • 问题抛出1: 为了探究ch1课件中runtime.main在赣神魔的问题,先向上寻找runtime之前是谁在做什么并引起了一系列问题。我在找到了这段注释之后对schedinit进行了资料查找,发现schedinit是作为调度器(schedule)的初始化函数并且:
资料://From https://draveness.me/golang/docs/part3-runtime/ch06-concurrency
我们从环境变量 GOMAXPROCS 获取了程序能够同时运行的最大处理器数之后就会调用
runtime.procresize 更新程序中处理器的数量,在这时整个程序不会执行任何用户 Goroutine,
调度器也会进入锁定状态,runtime.procresize 的执行过程如下:
 1. 如果全局变量 allp 切片中的处理器数量少于期望数量,会对切片进行扩容;
 2. 使用 new 创建新的处理器结构体并调用 runtime.p.init 初始化刚刚扩容的处理器;
 4. 通过指针将线程 m0 和处理器 allp[0] 绑定到一起;
 5. 调用 runtime.p.destroy 释放不再使用的处理器结构;
 6. 通过截断改变全局变量 allp 的长度保证与期望处理器数量相等;
 7. 将除 allp[0] 之外的处理器 P 全部设置成 _Pidle 并加入到全局的空闲队列中;

调用 runtime.procresize 是调度器启动的最后一步,
在这一步过后调度器会完成相应数量处理器的启动,
等待用户创建运行新的 Goroutine 并为 Goroutine 调度处理器资源。

D1:经过dlv调试,对schedinit进行断点调试,发现其由rt0_go栈跳转,
执行了CALL runtime·args(SB)、CALL runtime·osinit(SB) 、CALL runtime·schedinit(SB)、
下一步将create a new goroutine to start program 入口为MOVQ $runtime·mainPC(SB), AX
此时输出全局runtime.g0 runtime.m0发现:
1. m0与g0绑定
2. m0中`P`与`nextp`均为0,curg为nil

Q1:在schedinit中调用的procresize对allp切片进行调整并初始化p,将allp[0]与m0绑定在一起
Q1.1:为什么是allp[0]与m0绑定
Q1.2: 在此之前的rt0_go调用中是否载体都是m0?若是,那这个点之前的m0都没有绑定p,为什么仍能执行?(curg也没有好奇怪)
Q1.3:资料中描述调用 runtime.procresize 是调度器启动的最后一步,但我并未在procresize代码中找到显式代码,能指明一下么。
runtime.m0
runtime.m0


D2: The bootstrap sequencemake & queue new Grt0_go找到了相应过程
	// create a new goroutine to start program
	MOVQ	$runtime·mainPC(SB), AX		// entry
	PUSHQ	AX
	PUSHQ	$0			// arg size
	CALL	runtime·newproc(SB)
	POPQ	AX
	POPQ	AX

Q2: 这里runtime.mainPC(SB)执行完SB中存储的是哪个代码段的入口(SB存的是地址还是偏移量我也不清楚)?


D3: mstart我实在是看得不是太懂,只能看到mstart1中显式执行schedule,只能问些粒度大的问题,希望曹大别嫌烦= =。
// start this M
CALL	runtime·mstart(SB)

CALL	runtime·abort(SB)	// mstart should never return

Q3.1: mstart的M是之前的m0么?
Q3.1: 在mstart中是如何跳转到之前生成的G的


下次就问runtime.main了

ch1中课件

写回答

1回答

Xargin

2021-06-05

你需要先理解 m、线程这个概念还是一种多任务的抽象,操作系统执行代码的时候,在用户这边不定义线程也是可以执行的,比如:https://github.com/cch123/llp-trans/blob/d6b7f46c72e83ac9145d5534c6bc4e690da8d815/part1/assembly-language/example-calculating-string-length.md。 类似这个链接里的汇编程序,只是做一些运算,不会创建任何线程(用户态看来),但仍然可以用 nasm 编译成为可执行文件来执行。 

Q1:....... 

A: 这不是个疑问句?

Q1.1:..... 

A:这种是个约定,p 理论上还能扩/缩容,如果你刚才是 4,现在是 2,那 m0 和 P 的绑定关系就会被调来调去。非要和 p[n-1] 绑定理论上也可以实现的~ 


Q1.2: ,... 

A: 理解了开头的前提条件以后,你就知道代码执行的时候是可以没有线程的概念的,所以 rt0_go 开头的这些汇编代码,在用户态是没线程概念的。到创建好各种 M 以后,后续的代码才都是在线程 M 上执行的。 


Q1.3:.....。 

A: 像这种博客文章里的描述,你就没必要咬文嚼字,这意思只是想说 procresize 是 schedinit 里调用的最后一个函数而已。。非要咬文嚼字的话,后面还有三个 if 呢。 


http://img1.sycdn.imooc.com/climg/60ba4fc20981537b00000000.jpg

Q2: .... 

A: 这种 $开头的都是一些常量,(SB) 这种在汇编里一般表示的是全局变量,这里 runtime.mainPC 说白了就是跟编译器约好的一个名字,表示的是 runtime.main 函数的地址。SB 这种是 Go 里的伪寄存器,实际上是一个不存在的东西: 


http://img1.sycdn.imooc.com/climg/60ba545009d0cb3517120318.jpg


只是约定用来定义全局变量的。 


Q3.1: mstart的M是之前的m0么? 

A. 启动阶段的 mstart 应该确实是启动的 m0,本质是用 clone 系统调用创建了一个线程,并把 runtime.mstart 的地址传进去。

线程创建完成后,会从 mstart 函数开始执行用户的代码。


Q3.2: 在mstart中是如何跳转到之前生成的G的

A. mstart 在把线程启动完毕之后,一定是去调用 schedule。 在 m0 启动之前,已经通过 newproc 创建了一个 runtime.main 入口的 goroutine。 m0 通过 mstart 启动之后,最后去执行 schedule,这时候应该只有一个 goroutine,那总是能找到他的。。

1

Go高级工程师实战营

慕课网与 GoCN 社区官方联手打造,定义行业Go高级人才培养标准,4个月,快速晋升为P6+/D7级高级人才。

458 学习 · 266 问题

查看课程