说说wakep
和m的创建吧
wakep
调用时机和作用
- 作用:wakep的作用是添加一个P来执行goroutinue
- 时机:在有G变为runnable的时候 如:newproc ready
wakep中调用startm
来启动一个新的m
startm
- 首先如果传递过来P是nil,则需要获取一个idle P,如果获取不到,直接返回
- 调用
mget
获得一个已经睡眠m
- 如果没有获得m,则调用
newm
创建一个新的m
- 如果是创建的m则直接返回,后面再具体将newm的功能。如果是获取的m,则调用notewakeup来唤醒m(因为m在mput的时候已经睡眠了)
mget
mget
比较简单,就是从midle中取出一个通过mput放入的m,如果没有则返回nil
newm
func newm(fn func(),_p_ *p)
先看看注释:
创建一个g并执行fm或者是一次调度,所谓创建m就是启动一个系统线程。
- 调用allocm创建一个m并设置m.nextp为传进来的参数p,allocm还为m创建g0,并设置一个栈
- 这里有cgo的东西,暂时先不说,我也不懂,呵呵。
- 如果不是cgo,调用newosproc
newosproc
这个函数就有点系统相关了,这里看os_windows.go下的。
这个函数会调用一个系统调用来创建线程
这里创建的线程的启动函数为tstart_stdcall
所以我们先不纠结系统调用的问题,直接看线程启动之后会干嘛:
这个函数是用汇编写的(sys_windows_amd64.s)
在这个函数中,第一参数是存放在CX中。参数就是前面创建的m
- 获取g0
- 在这个函数中会重新设置栈给g0
- 这里把当前线程的栈顶给g的栈底,然后预留64K的空间给g0,并设置栈的stack_hi和stack_lo,以及stackguard0和stackguard1.
- 设置线程本地存储 将m的tls设置到0x28(GS),然后设置m给g0.m,设置g0给tls,一遍g(tls)可以取到当前g。
- 调用runtime.stackcheck
- 调用runtime.mstart
stackcheck就是检查当前的SP是不是[stack_hi,stack_lo)区间
然后看mstart
mstart
这里lo != 0, ==0那一段代码得意思就是在这里设置设置栈
意思是说cgo的时候可能值设置了栈大小到stack.hi
所以这里先创建一个局部变量size保存stack.hi,因为size是局部变量,所以就把size的栈地址设置为stack.hi,然后根据size计算出stack.lo
调用mstart1
mstart1会进行一些初始化并保存g0的栈等,
如果这里是m0,还会初始化信号量等。
如果m有startfn这个函数,也就是之前调用newm的时候传递了函数过来的话,就限制性这个函数。sysmon就是通过这种方式执行的。
这里如果是helpgc,则直接停掉这个m,或者如果不是m0,会把m.nextp设置给m.p
然后调用schedule来调度任务。
schedule
这个函数主要是找到一个runnable的g 然后调用execute来启动g
findrunnable
- 如果gcwatting stopm
- fingwait没看懂
- 从本地P队列获取g
- 从全局队列获取g
- 如果没有人在进行netpoll,尝试netpoll发现g
- 如果当前所有的P都是idle状态, 跳过从别的P偷取工作流的流程,否则进行偷取
- 如果spinning的M大于busy的P,则直接让m睡眠
- 又是GC的worker
- 设置P为idle
- 暂时让nmspinning-1,然后再次检查所有P上的runq,发现有P上有多余的goroutinue,则让M重新得到一个P,然后再次进行上面的查找g的流程。
- 又是GC
- 如果没有人在netpool,让当前M永久阻塞在netpool睡眠。
- 如果有人在netpool了,则直接stopm,让m睡眠
execute
- 先把g的状态设置为running
- 设置栈为正常状态而不是preempt状态
- 调用gogo切换SP和PC等。