How to defer chain is traversed

Began to write articles last year, the first chapter is about the defer, comparative literature and art name: "easy to resolve defer the Golang the tender trap" , also Tucao. Because of this article, to "Go night reading" told one. But at the time purely application level, has not yet jumped into the pit Go source code, the article looked relatively fresh, no source also a large section of resolution.

Since Cao after listening to "Go night reading" Go compilation of sharing, as well as reading articles Awa Zhang's Go scheduler source code analysis, a variety of source code, compile everywhere ......

Last Europe God wrote "Go GC 20 Questions" , there is no line of source text, read as a whole down very fun. This also today to try such an approach, however, we start with a small start theme: defer how the list is traversed and executed.

About defer source code analysis of the article, there are many on the network. However, few fully understand that this topic, in addition to Awa Zhang.

We know that some of the resources in order to perform cleanup operations before exiting the function, such as closing files, releasing connections. Will be written on multiple defer statement in a function, the function is defered to "last out" order in RETto be executed before instruction.

In a chain of function calls, multiple functions in multiple defer statements appear. For example: a() -> b() -> c()in each function in both defer statement, which will defer statement creates a corresponding number of _deferstructures, the structures in the form of a linked list of goroutinethe structure. Looks like this:

defer hanging g

In blessing compiler, the defer statement deferporc first call function, a new new _deferstructure, linked to the g. Of course, new priorities here will be taken from the current binding defer pool of P's, did not get to go global will defer pool's take, there is no word on a new, very familiar with the routine.

After doing so, waits for executing the function body, before the RET instruction (return note not before), calls the deferreturnfunction finishes _defertraversing the list, been performed on all this chain deferedfunction (e.g., closing files, release connections, etc.). The problem here is that in deferreturnthe end, will use the function jmpdeferbefore being defered jump to the function, then control is transferred to the user-defined function. It's just the execution of a function to be defered, this strand is defered other functions, how to get enforced?

The answer is that control will be handed over to runtime again and perform deferreturn function again, defer to complete traversal of the list. That all this is how to do it?

This compilation from the stack frame Go talking about. Look at an assembly function declaration:

TEXT runtime·gogo(SB), NOSPLIT, $16-8

The last two numbers indicate the size of the stack frame for a function gogo 16B, i.e., a function of local variables and parameters for the subroutine call and return values ​​need to prepare the space 16B of the stack; add size parameters and return values ​​are 8B. Indeed statement gogo function like this:

// func gogo(buf *gobuf)

Parameters and the size of the return value is to the caller "see", the caller can stack structure according to this figure: the need to be ready to adjust function parameters and return values.

A typical scenario of function call parameters layout diagram below:

Function call parameter layout

Left, ready to call a subroutine calling function parameters and return values, execute CALLthe instruction, the return address onto the stack, the equivalent to the implementation PUSH IP, after the value of the BP register stack, performs the equivalent PUSH BP, then jmp to the called function.

FIG return addressrepresents After completion of sub-function execution returns to the instruction statement to call a subroutine to be executed in the upper layer function, it belongs to the caller stack frame. BP is the caller stack frame belonging to the called function.

After the subroutine completes, execution RETinstructions: first subfunction value at the bottom of the stack assigned to the BP register in the CPU, so the upper function of BP BP point; then return addressassigned to the IP register, then back to the left as shown in SP s position. Corresponds to the reduction of the entire field of subroutine calls, like everything had happened; then, CPU continues to execute the next instruction in the IP register.

Back to defer up, in fact, in the construction _defertime structure, you need to save the current function of the SP, was defered function pointer to _deferstructure. Defered and will be a function of the parameters required to copy _defer position adjacent structure. The final call to the function is defered of the time, this time with is the copy of the value, equivalent to using a snapshot of it, if this parameter is not a pointer or reference type, it will produce a number of bug unexpected.

Finally, in deferreturn function, these are defered functions to be performed, _deferthe list will be gradually "consumed" finished.

Use a Awa Zhang article in the example:

package main

import "fmt"

func sum(a, b int) {
    c := a + b
    fmt.Println("sum:" , c)
}

func f(a, b int) {
    defer sum(a, b)

    fmt.Printf("a: %d, b: %d\n", a, b)
}

func main() {
    a, b := 1, 2
    f(a, b)
}

Executing the ffunction, the function will eventually enter deferreturn:

func deferreturn(arg0 uintptr) {
    gp := getg()
	d := gp._defer
	if d == nil {
		return
	}
	
	......
	
	switch d.siz {
	case 0:
		// Do nothing.
	case sys.PtrSize:
		*(*uintptr)(unsafe.Pointer(&arg0)) = *(*uintptr)(deferArgs(d))
	default:
		memmove(unsafe.Pointer(&arg0), deferArgs(d), uintptr(d.siz)) // 移动参数
	}
	fn := d.fn
	d.fn = nil
	gp._defer = d.link
	freedefer(d)
	
	_ = fn.fn
	jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))
}

Inevitably depend on the code, otherwise it is difficult to speak clearly.

Because we are traversing the _deferlinked list, so we have to have a termination condition:

d := gp._defer
if d == nil {
		return
}

That is, when _defer list is empty, terminate traversal. Will see later in the code, after each finished execute a function to be defered, will be _defer structure is removed from the list and recovered, so _defer list gets shorter.

switchDo sentence Lane is ready to be defered a function of a, b two int type parameters (example is the sum function) needed. Parameters come from it? From _defer structure adjacent to the location, remember, this is the copy function in the past in deferproc. deferArgs(d)Return is then copy the destination address. And now you go where you want to copy? The answer unsafe.Pointer(&arg0)is: . We know, arg0 is the parameter deferreturn function, we know that in the Go compilation, a function of the parameters is by its calling function to prepare. Thus arg0 address is actually put on the stack location parameter function of its upper layer (where f is the function).

Finally function by jmpdeferjumping to the sum function being defered:

jmpdefer(fn, uintptr(unsafe.Pointer(&arg0)))

The core is that things do jmpdefer:

TEXT runtime·jmpdefer(SB), NOSPLIT, $0-16
    MOVQ	fv+0(FP), DX	// fn // defer 的函数的地址
    MOVQ	argp+8(FP), BX
    LEAQ	-8(BX), SP	// caller sp after CALL
    MOVQ	-8(SP), BP	// restore BP as if deferreturn returned (harmless if framepointers not in use)
    SUBQ	$5, (SP)	// return to CALL again
    MOVQ	0(DX), BX
    JMP	BX	// but first run the deferred function

First, the sum function into address registers DX, to finally performed by JMP instruction.

MOVQ	argp+8(FP), BX
LEAQ	-8(BX), SP	// caller sp after CALL // 执行 CALL 指令后 f 函数的栈顶

These two lines actually adjusted the value of the current at the SP register, because argp + 8 (FP) is actually the second parameter jmpdefer (which in deferreturn function), the function f which points just copy the stack frame parameters sum function over. And -8(BX)it represents the return to the calling function f deferreturn address is actually the address of the next instruction deferreturn function.

Next, MOVQ -8(SP), BPit resets the instruction register BP, BP F to point to the stack frame. In this way, SP, BP register back to the state before the f function call deferreturn: f deferreturn just ready to call parameters, and the return value onto the stack. Equivalent abandoned the stack frame deferreturn function, however, and indeed useless.

Then SUBQ $5, (SP)the return address is reduced 5B, exactly the length of the instruction is a CALL. What does this mean? After executing the deferreturn function, execution flow returns to CALL deferreturnthe next instruction, this value will decrease 5B, it went back to the CALL deferreturncommand, in order to achieve a "recursive" function call deferreturn effect. Of course, the stack was not growing!

Execution jmpdefer

jmpdefer The last function will execute sum function, it looks like a function f personally call the sum function as parameters, return values ​​are ready.

Until the sum function executed, execution flow will jump to call deferreturnan instruction at a re-entering deferreturn function, through all _defer finished structure, after executing all of the functions are defered, really complete execution deferretrun function.

Recall deferreturn

Here, the full text is over. We can see that the key to achieving defer to traverse the list is jmpdefer function does some "shady" work, calls the return address reduced by 5 bytes deferreturn function so that function is defered the implementation of the latter, he returned the CALL deferreturninstruction that, in order to achieve "recursively" invoke deferreturn function _defer complete traversal of the list.

Reference material

[Analysis] Source Awa Zhang defer https://mp.weixin.qq.com/s/iEtMbRXW4yYyCG0TTW5y9g

[Awa Zhang panic & recover] https://mp.weixin.qq.com/s/0JTBGHr-bV4ikLva-8ghEw

[Base] Awa Zhang defer https://mp.weixin.qq.com/s/QmeQTONUuWlr_sRNP8b5Tw

[Analysis] compilation https://segmentfault.com/a/1190000019804120?utm_medium=referral&utm_source=tuicool

[Cao] Go compilation Share https://github.com/cch123/asmshare/blob/master/layout.md

[Compilation] Go Cao https://xargin.com/plan9-assembly

[Cao Dali get written in assembler goid] https://github.com/cch123/goroutineid

Guess you like

Origin www.cnblogs.com/qcrao-2018/p/12550380.html