runtime及其初始化(一)
来源:1-4 Go 程序是怎么跑起来的
东泽XD
2021-06-03 20:06:35
: from runtime/proc.go line 592
The bootstrap sequence is:/call osinit
call schedinit
make & queue new G
call runtime·mstartThe new G calls runtime·main.
上述是package runtime下runtime bootstrap sequence注释,围绕这几个过程提出几个概念性问题
资料://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 调度处理器资源。
create a new goroutine to start program
入口为MOVQ $runtime·mainPC(SB), AX1. 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代码中找到显式代码,能指明一下么。
The bootstrap sequence
中make & queue new G
在rt0_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存的是地址还是偏移量我也不清楚)?
// start this M
CALL runtime·mstart(SB)
CALL runtime·abort(SB) // mstart should never return
Q3.1: mstart的M是之前的m0么?
Q3.1: 在mstart中是如何跳转到之前生成的G的
1回答
你需要先理解 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 呢。
Q2: ....
A: 这种 $开头的都是一些常量,(SB) 这种在汇编里一般表示的是全局变量,这里 runtime.mainPC 说白了就是跟编译器约好的一个名字,表示的是 runtime.main 函数的地址。SB 这种是 Go 里的伪寄存器,实际上是一个不存在的东西:
只是约定用来定义全局变量的。
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,那总是能找到他的。。
相似问题