This is a creation in Article, where the information may have evolved or changed.
Overview
The Golang is a lightweight thread in the perfectly formed, while the Golang management process is bound to involve the transition between the threads: the blocking process is switched out, and the running coprocessor is switched in. In this section, we will carefully analyze how the process is switched.
Tls
Thread Local Storage:
GETG ()
GoGet () is used to get the current thread being executed by the process G. The process g is stored in TLS.
McAll ()
McAll is called when Golang needs to perform a co-process switchover to hold information that is being switched out and to execute a new function on the G0 thread stack of the current threads. In general, schedule () is executed once in the new function to pick a new thread to run. Next we'll look at the implementation of McAll.
Call timing
System Call Back
When the thread executing the system call returns from the system call, it may be necessary to execute a new schedule, at which point the McAll may be called to complete the work, as follows:
func exitsyscall(dummy int32) { ...... // Call the scheduler. mcall(exitsyscall0) ......}
In Exitsyscall0, if the current coprocessor is likely to be discarded and executed once schedule, the new thread is picked to occupy M.
Due to blocking abort execution
For some reason, the current execution of the process may be blocked, such as when the pipeline read and write conditions are not met, the current process will be blocked until the condition is satisfied.
In the Gopark () function, the mcall is called to discard the current coprocessor and perform a co-scheduling of the process.
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason string, traceEv byte, traceskip int) { mp := acquirem() gp := mp.curg status := readgstatus(gp) if status != _Grunning && status != _Gscanrunning { throw("gopark: bad g status") } mp.waitlock = lock mp.waitunlockf = *(*unsafe.Pointer)(unsafe.Pointer(&unlockf)) gp.waitreason = reason mp.waittraceev = traceEv mp.waittraceskip = traceskip releasem(mp) // can't do anything that might move the G between Ms here. mcall(park_m)}
The Park_m function, which we'll analyze later, discards the previously executed coprocessor and invokes a schedule () to pick a new coprocessor to execute.
Principle of execution
We mainly describe the timing of the mcall being called, and now we want to look at the implementation principle of McAll.
The function prototypes for McAll are:
func mcall(fn func(*g))
The FN parameter here refers to the process that is running before calling McAll.
As we said earlier, the primary role of McAll is the co-process switchover, which saves the currently executing coprocessor state and then m->g0 the new function on the stack of the heap. In the new function, the previously running coprocessor is discarded and a schedule () is called once to pick up the new run.
Func McAll (fn func (*g))//Switch to M->g0 ' s stack, call FN (g). Fn must never return. It should Gogo (&g->sched)//To keep running G. TEXT runtime McAll (SB), nosplit, $0-8//di stored parameters fn movq F N+0 (FP), DI get_tls (CX)//Get the currently running coprocessor G Info//save its status in g.sched variable movq g (CX), AX/save state in G->SC Hed movq 0 (SP), BX//caller ' s PC movq BX, (G_SCHED+GOBUF_PC) (AX) Leaq fn+0 (FP), BX//caller ' s P movq BX, (g_sched+gobuf_sp) (ax) movq ax, (g_sched+gobuf_g) (ax) movq BP, (G_SCHED+GOBUF_BP) (AX)// Switch to M->g0 & it stack, call FN movq g (CX), BX movq g_m (BX), BX movq m_g0 (BX), SI CMPQ Si, ax//if G = = M->g0 call Badmcall JNE 3 (PC) movq $runtime Badmcall (SB), ax JMP ax Movq SI, G ( CX)//G = m->g0//switch to M->G0 stack movq (g_sched+gobuf_sp) (SI), SP//SP = M->G0->SCHED.SP//Parameter Ax for the previously run Pushq Ax Movq DI, DX MOVQ 0 (di), di//Execute function on M->G0 stack fn call DI popq ax movq $runtime badmcall2 (SB), Ax JMP Axret
How to get current coprocessor execution information
The first two sentences may be more obscure to understand:
Buf+0 (FP) is actually the first parameter to get Gosave (gobuf address), reference A Quick Guide to Go ' s assembler
The
FP Pseudo-register is a virtual frame pointer used to refer to function arguments. The compilers maintain a virtual frame pointer and refer to the arguments on the stack as offsets from that Pseudo-registe R. Thus 0 (FP) is the first argument to the function, 8 (FP) was the second (on a 64-bit machine), and so on. However, when referring to a function argument this, it's necessary to place a name at the beginning, as in First_arg +0 (FP) and Second_arg+8 (FP).
Leaq buf+0 (FP), BX is to get to the first parameter of the storage address, and according to the Golang stack layout, this address is actually the caller's SP, as follows:
The next few sentences are easier to understand, and after the first sentence gets the address of the gobuf, some of the relevant members are then set to the appropriate value. The key is the following sentence
get_tls(CX)MOVQ g(CX), BX MOVQ BX, gobuf_g(AX)
The purpose of these sentences is to get the G that the current thread is running from TLS, and then store it in the GOBUF member G.
Gosave ()
The gosave is called during the Golang process switch to hold the information that is being switched out, so that the execution context of the process can be quickly resumed the next time the process is re-dispatched.
The data structures associated with the scheduling are as follows:
type g struct { stack stack stackguard0 uintptr stackguard1 uintptr ...... sched gobuf ......}// gobuf记录与协程切换相关信息 type gobuf struct { sp uintptr pc uintptr g guintptr ctxt unsafe.Pointer ret uintreg lr uintptr bp uintptr }
Gosave is written in assembly language, the performance is relatively high, but it is not so easy to understand.
What is the call path for Todo:gosave ()?
// void gosave(Gobuf*)// save state in Gobuf; setjmp TEXT runtime·gosave(SB), NOSPLIT, $0-8 MOVQ buf+0(FP), AX // gobuf LEAQ buf+0(FP), BX // caller's SP MOVQ BX, gobuf_sp(AX) MOVQ 0(SP), BX // caller's PC MOVQ BX, gobuf_pc(AX) MOVQ $0, gobuf_ret(AX) MOVQ $0, gobuf_ctxt(AX) MOVQ BP, gobuf_bp(AX) get_tls(CX) MOVQ g(CX), BX
The first two sentences may be more obscure to understand:
Buf+0 (FP) is actually the first parameter to get Gosave (gobuf address), reference A Quick Guide to Go ' s assembler
The
FP Pseudo-register is a virtual frame pointer used to refer to function arguments. The compilers maintain a virtual frame pointer and refer to the arguments on the stack as offsets from that Pseudo-registe R. Thus 0 (FP) is the first argument to the function, 8 (FP) was the second (on a 64-bit machine), and so on. However, when referring to a function argument this, it's necessary to place a name at the beginning, as in First_arg +0 (FP) and Second_arg+8 (FP).
Leaq buf+0 (FP), BX is to get to the first parameter of the storage address, and according to the Golang stack layout, this address is actually the caller's SP, as follows:
The next few sentences are easier to understand, and after the first sentence gets the address of the gobuf, some of the relevant members are then set to the appropriate value. The key is the following sentence
get_tls(CX)MOVQ g(CX), BX MOVQ BX, gobuf_g(AX)
The purpose of these sentences is to get the G that the current thread is running from TLS, and then store it in the GOBUF member G.
Gogo ()
The Gogo function is the reverse, which is used to recover the execution state from the gobuf and jump to the last command to continue execution. Therefore, the code is relatively easy to understand, we will not be too much to repeat, as follows:
Gogo () Main call path: Schedule () –>execute () –>googo ()
// void gogo(Gobuf*)// restore state from Gobuf; longjmp TEXT runtime·gogo(SB), NOSPLIT, $0-8 MOVQ buf+0(FP), BX // gobufMOVQ gobuf_g(BX), DX MOVQ 0(DX), CX get_tls(CX)MOVQ DX, g(CX)MOVQ gobuf_sp(BX), SP // restore SP MOVQ gobuf_ret(BX), AX MOVQ gobuf_ctxt(BX), DX MOVQ gobuf_bp(BX), BP MOVQ $0, gobuf_sp(BX)MOVQ $0, gobuf_ret(BX)MOVQ $0, gobuf_ctxt(BX)MOVQ $0, gobuf_bp(BX)// 恢复出上一次执行指令,并跳转至该指令处MOVQ
The last sentence here jumps to the statement that the association is dispatched to continue execution, and it is important to note that the function no longer returns the caller.