说说wakep
和m的创建吧
wakep
调用时机和作用
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)
先看看注释:
// 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.
创建一个g并执行fm或者是一次调度,所谓创建m就是启动一个系统线程。
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
这里有可能会只是trance或者gc的g,这部分还不太了解。
下面是先从全局队列里面取一次,因为如果不先取全局队列,那么有可能两个g互相继续而导致全局的g没有机会运行。
然后尝试从本地(P)的队列获取一个g
- 调用findrunnable,这个函数会阻塞知道找到一个可运行的g
- 如果找到一个g且为m为spinning,则清除状态
- 调用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等。