0. Preface
Recall.previous session The main thread, m0, is ready to work. The main thread, m0, is poised and ready to do its work. g0 provides the execution environment for m0, and P is bound to m0, providing m0 with a live, or goroutine. So the question is, where is the live? Where is the work for m0?
In this talk, we'll cover the first job executed by m0, the main goroutine. main gouroutine is the goroutine that executes the main function, as opposed to the goroutine that executes the main function using thego
goroutine created by the keyword, there are some differences in their execution (more on that later).
1. main goroutine creation
Continuing from the previous lecture, after the scheduler is initialized, it executes into theasm_amd64.s/rt0_go:352:
TEXT runtime·rt0_go(SB),NOSPLIT|NOFRAME|TOPFRAME,$0
...
// create a new goroutine to start program
352 MOVQ $runtime·mainPC(SB), AX // entry
353 PUSHQ AX
354 CALL runtime·newproc(SB)
355 POPQ AX
// dlv Go to command execution.
dlv exec ./hello
Type 'help' for list of commands.
(dlv) b /usr/local/go/src/runtime/asm_amd64.s:352
Breakpoint 1 set at 0x45433c for runtime.rt0_go() /usr/local/go/src/runtime/asm_amd64.s:352
(dlv) c
(dlv) si
> runtime.rt0_go() /usr/local/go/src/runtime/asm_amd64.s:353 (PC: 0x454343)
Warning: debugging optimized function
asm_amd64.s:349 0x454337 e8e4290000 call $
asm_amd64.s:352 0x45433c* 488d05659d0200 lea rax, ptr [rip+0x29d65]
=> asm_amd64.s:353 0x454343 50 push rax
Analyzed in conjunction with CPU execution instructions and Go plan9 assembly code.
First, the$runtime·mainPC(SB)
The address is passed to the AX register, and the CPU executes the instructionmov qword ptr [rsp+0x8], rax
. Useregs
You can see the value of rax, which is$runtime·mainPC(SB)
The address of the
(dlv) regs
Rip = 0x0000000000454343
Rsp = 0x00007ffd58324080
Rax = 0x000000000047e0a8 // rax = $(SB) = [rsp+0x8]
in that case$(SB)
What does the address of refer to? Let's see.$(SB)
Definition of:
// mainPC is a function value for , to be passed to newproc.
// The reference to is made via ABIInternal, since the
// actual function (not the ABI0 wrapper) is needed by newproc.
DATA runtime·mainPC+0(SB)/8,$runtime·main<ABIInternal>(SB)
GLOBL runtime·mainPC(SB),RODATA,$8
$(SB)
is a program designed to perform The function value of the
carry on withPUSH AX
commander-in-chief (military)(SB)
onto the stack. Note that the stack here is the g0 stack, which is the stack on which the main thread m0 is running.
Moving on down the road:
=> asm_amd64.s:354 0x45ca64 e8f72a0000 call $
asm_amd64.s:355 0x45ca69 58 pop rax
call (programming)$
function.newproc
is the function that creates the goroutine. We use thego
The goroutines created by the keyword are converted by the compiler and eventually called into thenewproc
Creates the goroutine. as you can imagine, this function is very important.
Entering this function we are still operating on the g0 stack.
// Create a new g running fn.
// Put it on the queue of g's waiting to run.
// The compiler turns a go statement into a call to this.
func newproc(fn *funcval) {
gp := getg() // gp = g0
pc := getcallerpc() // Get the address of the caller's instruction,That is, calling the newproc from call Function return address of the instruction stack
systemstack(func() {
newg := newproc1(fn, gp, pc) // establish g
...
})
}
newproc
call (programming)newproc1
Create the goroutine, and introduce the passes to thenewproc1
specificationsfn
,gp
cap (a poem)pc
。
in the first placefn
Yes, it contains function value, print the value of the
fn
Below:
(dlv) print fn
(*)(0x47e0a8)
* {fn: 4386432}
As can be seen.fn
is a pointer to the structurefuncval
address (that is, the previously described$(SB)
Address0x47e0a8
), which are contained within the structurefn
It is the actual implementation of the The address of the function:
type funcval struct {
fn uintptr
// variable-size, fn-specific data here
}
The second argument, gp, is equal to g0, which provides the runtime environment for the main thread, m0, and pc, which is the return address of the function that was stacked by the call instruction when newproc was called.
Parameters are done. Look at them.systemstack
function.systemstack
wouldgoroutine
runningfn
The call runs to the system stack (g0 stack), where m0 is already running on the g0 stack and does not need to be called. If the goroutine is not on the g0 stack, e.g. m0 is running on the g1 stack, then thesystemstack
It cuts the g1 stack to the g0 stack, then runs fn and returns to the g1 stack. For more details, seehere are。
Now enternewproc1(fn, gp, pc)
ferret outnewproc1
How a new goroutine is created.
func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
mp := acquirem() // acquirem gets the current thread the goroutine is bound to, here m0
pp := () // acquire the P that the thread is bound to, here pp = allp[0]
// Get the free goroutine from P's local gFree queue or the global gFree queue, or nil if it's not available.
// This is the main goroutine creation phase, there are no free goroutines.
newg := gfget(pp)
if newg == nil {
newg = malg(stackMin) // malg creates new goroutine
casgstatus(newg, _Gidle, _Gdead) // the initial state of the created goroutine is _Gidle, here update the goroutine state to _Gdead
allgadd(newg) // Add the new goroutine to the global variable allgs.
}
...
}
first callgfget
Get the idle goroutine in the current thread P or in the global idle queue, if there is none then call themalg(stackMin)
Create a new goroutine.malg(stackMin)
hit the nail on the headstackMin
Equal to 2048, or 2K. viewmalg
Did what:
func malg(stacksize int32) *g {
newg := new(g) // new establish g
if stacksize >= 0 { // stacksize = 2048
stacksize = round2(stackSystem + stacksize) // stackSystem = 0, stacksize = 2048
systemstack(func() {
= stackalloc(uint32(stacksize)) // call (programming) stackalloc new acquisition goroutine a wooden or bamboo pen for sheep or cattle,meso- (chemistry) goroutine a wooden or bamboo pen for sheep or cattle大小为 2K
})
newg.stackguard0 = + stackGuard
newg.stackguard1 = ^uintptr(0)
// Clear the bottom word of the stack. We record g
// there on gsignal stack during VDSO on ARM and ARM64.
*(*uintptr)(()) = 0
}
return newg
}
malg
Create a new goroutine with a goroutine stack size of 2KB.
then callcasgstatus
Update the status of the goroutine to_Gdead
. Then call theallgadd
function will create the goroutine and the global variableallgs
Associated:
func allgadd(gp *g) {
lock(&allglock) // allgs is a global variable, add a lock to the global variable.
allgs = append(allgs, gp) // add newg:gp to allgs
if &allgs[0] ! = allgptr { // allgptr is a pointer to allgs[0], here nil
atomicstorep((&allgptr), (&allgs[0])) // allgptr = &allgs[0]
}
(&allglen, uintptr(len(allgs))) // update global variable allglen = len(allgs)
unlock(&allglock) // unlock the variable
}
Keep reading.newproc1
of the implementation process:
func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
...
totalSize := uintptr(4* + ) // extra space in case of reads slightly beyond frame
totalSize = alignUp(totalSize, )
sp := - totalSize // sp is a pointer to the top of the stack
// set up All members of the 0,Followed by reassigning them
memclrNoHeapPointers((&), ())
= sp
= sp
// denote newg The instructions are executed from this address when they are scheduled to run.
= abi.FuncPCABI0(goexit) + // +PCQuantum so that previous instruction is in same function
= guintptr((newg))
gostartcallfn(&, fn)
This code essentially gives the Assignment.
of the structure is as follows:
type gobuf struct {
sp uintptr // pointer to the top of the stack of the goroutine. pc uintptr // address of the instruction that executes the goroutine.
pc uintptr // address of the instruction that executes the goroutine
g guintptr // Address of the goroutine.
ctxt // address of the structure funcval that wraps the function executed by the goroutine
ret uintptr // Return address
lr uintptr
bp uintptr
}
The main members are shown in the comments, and the thread will know where to run the code from by using this structure.
In the assignment When, this code is interesting:
= abi.FuncPCABI0(goexit) +
It is a combination ofgoexit
The address of the function + 1 after passing theView at this time
The value of the
4530: = abi.FuncPCABI0(goexit) + // +PCQuantum so that previous instruction is in same function
=>4531: = guintptr((newg))
(dlv) print
{sp: 824633976800, pc: 4540513, g: 0, ctxt: (0x0), ret: 0, lr: 0, bp: 0}
(dlv) print (4540513)
(0x454861)
In reality, it will be0x454861
Pass it on.Let's forget about that for a moment.
0x454861
, then read on. Callinggostartcallfn(&, fn)
function:
func gostartcallfn(gobuf *gobuf, fv *funcval) {
var fn
if fv ! = nil {
fn = () // Assign to fn, which is actually the address of fn.
} else {
fn = ((nilfunc))
}
gostartcall(gobuf, fn, (fv))
}
func gostartcall(buf *gobuf, fn, ctxt ) {
sp := // take top-of-stack pointer from g1
sp -= // take the top-of-stack pointer and subtract 1 byte from it.
*(*uintptr)((sp)) = // Decrease 1 byte of space for abi.FuncPCABI0(goexit) + = sp // Decrease 1 byte of space for abi.
= sp // Use the subtracted 1 byte of sp as the new top of the stack.
= uintptr(fn) // repoint pc to fn
= ctxt // point pc to funcval again
}
Seeing this we understand why we need to add a layer ofgoexit
and subtracts 1 from the top-of-stack pointer to make the new top of the stack. This is because the new top of the stack will be executed when you return to thegoexit
This is what the scheduler expects every goroutine to do after executing thegoexit
It's the only way to really quit.
Okay, let's get back tonewproc1
Keep looking down:
func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
...
= // newg's parent id, = 0
= callerpc // caller's pc
= // = = = &
...
casgstatus(newg, _Gdead, _Grunnable) // Update the status of newg to _Grunnable.
// Get the new one via goidcache, where the main goroutine's goid is 1.
...
releasem(mp)
return newg
}
At this point our new goroutine is created. To recap, we first request 2KB of stack space for the new goroutine, and then we create the environment for executing the goroutine in the new goroutine.The thread is based on the
Finally, set the state of the goroutine to _Grunnable to indicate that the goroutine is ready to run.
We draw the memory distribution based on the above analysis as follows:
2. Summary
The logic of creating the main gouroutine is basically finished here. In the next section, we will continue to describe how the main gouroutine is run.