当前位置:网站首页>Go program lifecycle

Go program lifecycle

2022-06-24 03:05:00 wish42

This article is about (WHAT)

This article hopes to make it clear that Go What happens between the time the program starts writing the first line of code and the time the program exits completely , Of course, the execution logic of each program is very different , But what I want to make clear here is that all programs have something in common , The processing logic that all programs need to go through .

Why are you talking about this (WHY)

For other students ,

  • Understand compilation from the code level , link , Loading process ;
  • Understand the runtime from the code level ;
  • Understand what compile time decisions are , What is determined by the runtime ; For me personally , Read a lot of books and materials , Want to digest and understand , And how can it be digested , Ruminate the concepts and knowledge you read and output them in your own language and words , This is a very important milestone . How to say it (HOW) Try to understand the same thing from multiple angles according to the author's own understanding , I hope different angles can confirm each other , The end result is the same goal , understand , Digest the things we don't understand .

Next, let's enter the text .

From the simplest Hello Golang Speaking of .

package main

import "fmt"

func main() {
        fmt.Printf("Hello Golang!")
}

We usually have one go build To compile and generate a ELF Executable file .

So let's see go build What they have done for us .

 > # go build -a -n
# import config
packagefile fmt=$WORK/b002/_pkg_.a
packagefile runtime=$WORK/b007/_pkg_.a
EOF
/usr/local/go/pkg/tool/linux_amd64/compile -o $WORK/b001/_pkg_.a -trimpath "$WORK/b001=>" -p main -lang=go1.17 -complete -buildid z0ktmI1YTOzCHQM1j7-R/z0ktmI1YTOzCHQM1j7-R -goversion go1.17.2 -importcfg $WORK/b001/importcfg -pack -c=4 ./hello.go $WORK/b001/_gomod_.go
/usr/local/go/pkg/tool/linux_amd64/buildid -w $WORK/b001/_pkg_.a # internal
cat >$WORK/b001/importcfg.link << 'EOF' # internal
packagefile git.code.oa.com/laboratory/hello=$WORK/b001/_pkg_.a
packagefile fmt=$WORK/b002/_pkg_.a
packagefile runtime=$WORK/b007/_pkg_.a
packagefile errors=$WORK/b003/_pkg_.a
packagefile internal/fmtsort=$WORK/b015/_pkg_.a
packagefile io=$WORK/b027/_pkg_.a
packagefile math=$WORK/b018/_pkg_.a
packagefile os=$WORK/b028/_pkg_.a
packagefile reflect=$WORK/b016/_pkg_.a
packagefile strconv=$WORK/b020/_pkg_.a
packagefile sync=$WORK/b022/_pkg_.a
packagefile unicode/utf8=$WORK/b021/_pkg_.a
packagefile internal/abi=$WORK/b008/_pkg_.a
packagefile internal/bytealg=$WORK/b009/_pkg_.a
packagefile internal/cpu=$WORK/b010/_pkg_.a
packagefile internal/goexperiment=$WORK/b011/_pkg_.a
packagefile runtime/internal/atomic=$WORK/b012/_pkg_.a
packagefile runtime/internal/math=$WORK/b013/_pkg_.a
packagefile runtime/internal/sys=$WORK/b014/_pkg_.a
packagefile internal/reflectlite=$WORK/b004/_pkg_.a
packagefile sort=$WORK/b026/_pkg_.a
packagefile math/bits=$WORK/b019/_pkg_.a
packagefile internal/itoa=$WORK/b017/_pkg_.a
packagefile internal/oserror=$WORK/b029/_pkg_.a
packagefile internal/poll=$WORK/b030/_pkg_.a
packagefile internal/syscall/execenv=$WORK/b034/_pkg_.a
packagefile internal/syscall/unix=$WORK/b031/_pkg_.a
packagefile internal/testlog=$WORK/b035/_pkg_.a
packagefile internal/unsafeheader=$WORK/b005/_pkg_.a
packagefile io/fs=$WORK/b036/_pkg_.a
packagefile sync/atomic=$WORK/b024/_pkg_.a
packagefile syscall=$WORK/b032/_pkg_.a
packagefile time=$WORK/b033/_pkg_.a
packagefile unicode=$WORK/b025/_pkg_.a
packagefile internal/race=$WORK/b023/_pkg_.a
packagefile path=$WORK/b037/_pkg_.a
EOF
mkdir -p $WORK/b001/exe/
cd .
/usr/local/go/pkg/tool/linux_amd64/link -o $WORK/b001/exe/a.out -importcfg $WORK/b001/importcfg.link -buildmode=exe -buildid=yUoLSlHsIhJ4gtJbQMNi/z0ktmI1YTOzCHQM1j7-R/z0ktmI1YTOzCHQM1j7-R/yUoLSlHsIhJ4gtJbQMNi -extld=gcc $WORK/b001/_pkg_.a
/usr/local/go/pkg/tool/linux_amd64/buildid -w $WORK/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out hello

From here we can see that from a macro point of view ELF The birth experience of documents compile,link,mv These processes ;

  • compile Will go File compiled into $WORK/b001/pkg.a file ;
  • link Will $WORK/b001/pkg.a Link generation $WORK/b001/exe/a.out ;
  • mv The generated $WORK/b001/exe/a.out It is amended as follows go.mod File name specified in ; Of course , The process here is simplified Billion points details , Let's go a little deeper , Back to class , What does a complete compilation process have :
image.png

Seeing this, I believe most people may want to ctrl+w 了 , But not yet . Try to follow my train of thought and read on .

Lexical analysis

Let's use the code to simulate the process and results of lexical analysis :

var src = `
 package main
 import "fmt"
 func main() {
     fmt.Println("Hello Golang!")
 }
`

func main() {
	fset := token.NewFileSet() // positions are relative to fset
	tokenfile := fset.AddFile("hello.go", fset.Base(), len(src))
	//  Lexical analysis 
	var s scanner.Scanner
	s.Init(tokenfile, []byte(src), nil, scanner.ScanComments)
	for {
		pos, tok, lit := s.Scan()
		if tok == token.EOF {
			break
		}
		fmt.Printf("%s\t%s\t%q\n", fset.Position(pos), tok, lit)
	}
}

Generate results :

image.png

The process of lexical analysis is actually to convert the code file into a string slice The process of . namely []byte -> tokens.

token It can be roughly divided into the following categories :

  • special token. for example ,ILLEGAL( illegal TOKEN)、EOF( end of file )、COMMENT( notes )
  • Literally token. for example , identifier IDENT、 Numbers INT、 character string STRING wait .
  • The operator token.+ - * / , . ; ( ) wait .
  • keyword token.var,select,chan etc. .

Lexical analysis stage , A semicolon will be added at the end of each line of the source code ;. This is it. go The reason for not adding a semicolon at the end of each line of code .

Syntax analysis

The result of lexical analysis is token Sequence , And code location and other descriptive information . Similarly, we use code to simulate the process of parsing :

var src = `
 package main
 import "fmt"
 func main() {
     fmt.Println("Hello Golang!")
 }
`

func main() {
	fset := token.NewFileSet() // positions are relative to fset
	tokenfile := fset.AddFile("hello.go", fset.Base(), len(src))
	//  Lexical analysis 
	var s scanner.Scanner
	s.Init(tokenfile, []byte(src), nil, scanner.ScanComments)
	for {
		pos, tok, lit := s.Scan()
		if tok == token.EOF {
			break
		}
		fmt.Printf("%s\t%s\t%q\n", fset.Position(pos), tok, lit)
	}

	//  Syntax analysis 
	astfile, err := parser.ParseFile(fset, "", src, 0)
	if err != nil {
		panic(err)
	}
	// Print the AST.
	ast.Print(fset, astfile)
}

The process of grammar analysis is actually to combine the above token The sequence builds a AST( Abstract syntax tree ) As for the abstract grammar tree, I think many students can only hear its sound but not see its body , Fortunately, we are not here Print Did you come out :

     0  *ast.File {
     1  .  Package: 2:2
     2  .  Name: *ast.Ident {
     3  .  .  NamePos: 2:10
     4  .  .  Name: "main"
     5  .  }
     6  .  Decls: []ast.Decl (len = 2) { //  Statement list 
     7  .  .  0: *ast.GenDecl { // generic declaration node, Used to represent import,const,type,variable Statement statement 
     8  .  .  .  TokPos: 3:2
     9  .  .  .  Tok: import
    10  .  .  .  Lparen: -
    11  .  .  .  Specs: []ast.Spec (len = 1) {
    12  .  .  .  .  0: *ast.ImportSpec { // 
    13  .  .  .  .  .  Path: *ast.BasicLit { // A BasicLit node represents a literal of basic type.( This is used in the source code comment Information , I really didn't think how to translate this )
    14  .  .  .  .  .  .  ValuePos: 3:9
    15  .  .  .  .  .  .  Kind: STRING
    16  .  .  .  .  .  .  Value: "\"fmt\""
    17  .  .  .  .  .  }
    18  .  .  .  .  .  EndPos: -
    19  .  .  .  .  }
    20  .  .  .  }
    21  .  .  .  Rparen: -
    22  .  .  }
    23  .  .  1: *ast.FuncDecl { //  Function declaration , Function is defined by  Name Type Body form 
    24  .  .  .  Name: *ast.Ident { //  Function name Description 
    25  .  .  .  .  NamePos: 4:7
    26  .  .  .  .  Name: "main"
    27  .  .  .  .  Obj: *ast.Object {
    28  .  .  .  .  .  Kind: func
    29  .  .  .  .  .  Name: "main"
    30  .  .  .  .  .  Decl: *(obj @ 23)
    31  .  .  .  .  }
    32  .  .  .  }
    33  .  .  .  Type: *ast.FuncType { //  Function type information 
    34  .  .  .  .  Func: 4:2
    35  .  .  .  .  Params: *ast.FieldList { //  parameter list 
    36  .  .  .  .  .  Opening: 4:11 //  Start position of parameter list 
    37  .  .  .  .  .  Closing: 4:12 //  End position of parameter list 
    38  .  .  .  .  }
    39  .  .  .  }
    40  .  .  .  Body: *ast.BlockStmt { //  Function body description 
    41  .  .  .  .  Lbrace: 4:14 //  Position of the left curly bracket of the function body 
    42  .  .  .  .  List: []ast.Stmt (len = 1) {
    43  .  .  .  .  .  0: *ast.ExprStmt { //  The first element is an expression 
    44  .  .  .  .  .  .  X: *ast.CallExpr { //  Function call expression 
    45  .  .  .  .  .  .  .  Fun: *ast.SelectorExpr { //  Selector expressions 
    46  .  .  .  .  .  .  .  .  X: *ast.Ident {
    47  .  .  .  .  .  .  .  .  .  NamePos: 5:6
    48  .  .  .  .  .  .  .  .  .  Name: "fmt"
    49  .  .  .  .  .  .  .  .  }
    50  .  .  .  .  .  .  .  .  Sel: *ast.Ident { //  Selector field description 
    51  .  .  .  .  .  .  .  .  .  NamePos: 5:10
    52  .  .  .  .  .  .  .  .  .  Name: "Println"
    53  .  .  .  .  .  .  .  .  }
    54  .  .  .  .  .  .  .  }
    55  .  .  .  .  .  .  .  Lparen: 5:17 //  Left parenthesis (
    56  .  .  .  .  .  .  .  Args: []ast.Expr (len = 1) { //  parameter list 
    57  .  .  .  .  .  .  .  .  0: *ast.BasicLit {
    58  .  .  .  .  .  .  .  .  .  ValuePos: 5:18
    59  .  .  .  .  .  .  .  .  .  Kind: STRING
    60  .  .  .  .  .  .  .  .  .  Value: "\"Hello, Golang!\""
    61  .  .  .  .  .  .  .  .  }
    62  .  .  .  .  .  .  .  }
    63  .  .  .  .  .  .  .  Ellipsis: - // "..." The location of 
    64  .  .  .  .  .  .  .  Rparen: 5:34 //  Right bracket )
    65  .  .  .  .  .  .  }
    66  .  .  .  .  .  }
    67  .  .  .  .  }
    68  .  .  .  .  Rbrace: 6:2 //  Position of the right curly bracket in the function body 
    69  .  .  .  }
    70  .  .  }
    71  .  }
    72  .  Scope: *ast.Scope { //  There is only one scope information here main
    73  .  .  Objects: map[string]*ast.Object (len = 1) {
    74  .  .  .  "main": *(obj @ 27)
    75  .  .  }
    76  .  }
    77  .  Imports: []*ast.ImportSpec (len = 1) {
    78  .  .  0: *(obj @ 12)
    79  .  }
    80  .  Unresolved: []*ast.Ident (len = 1) {
    81  .  .  0: *(obj @ 46)
    82  .  }
    83  }

Above AST In the result, I added some notes according to my own understanding for your reference .

Go Medium AST Structure is defined as :

// A File node represents a Go source file.
//
// The Comments list contains all comments in the source file in order of
// appearance, including the comments that are pointed to from other nodes
// via Doc and Comment fields.
//
// For correct printing of source code containing comments (using packages
// go/format and go/printer), special care must be taken to update comments
// when a File's syntax tree is modified: For printing, comments are interspersed
// between tokens based on their position. If syntax tree nodes are
// removed or moved, relevant comments in their vicinity must also be removed
// (from the File.Comments list) or moved accordingly (by updating their
// positions). A CommentMap may be used to facilitate some of these operations.
//
// Whether and how a comment is associated with a node depends on the
// interpretation of the syntax tree by the manipulating program: Except for Doc
// and Comment comments directly associated with nodes, the remaining comments
// are "free-floating" (see also issues #18593, #20744).
//
type File struct {
	Doc        *CommentGroup   // associated documentation; or nil
	Package    token.Pos       // position of "package" keyword
	Name       *Ident          // package name
	Decls      []Decl          // top-level declarations; or nil
	Scope      *Scope          // package scope (this file only)
	Imports    []*ImportSpec   // imports in this file
	Unresolved []*Ident        // unresolved identifiers in this file
	Comments   []*CommentGroup // list of all comments in the source file
}

One go The file will be parsed into a in the parsing phase ast.File structure .

Semantic analysis

Similarly, we use code to simulate the semantic analysis process :

var src = `
 package main
 import "fmt"
 func main() {
    b = "a" + 1 //  Here is a syntax error sentence intentionally written 
     fmt.Println("Hello, Golang!")
 }
`

func main() {
	fset := token.NewFileSet() // positions are relative to fset
	tokenfile := fset.AddFile("hello.go", fset.Base(), len(src))
	//  Lexical analysis 
	var s scanner.Scanner
	s.Init(tokenfile, []byte(src), nil, scanner.ScanComments)
	for {
		pos, tok, lit := s.Scan()
		if tok == token.EOF {
			break
		}
		fmt.Printf("%s\t%s\t%q\n", fset.Position(pos), tok, lit)
	}

	//  Syntax analysis 
	astfile, err := parser.ParseFile(fset, "", src, 0)
	if err != nil {
		panic(err)
	}
	// Print the AST.
	ast.Print(fset, astfile)

	//  Semantic analysis 
	typeconfig := &types.Config{Importer: importer.Default()}
	pkg, err := typeconfig.Check("hello.go", fset, []*ast.File{astfile}, nil)
	if err != nil {
		fmt.Printf("%v\n", err)
	}
	_ = pkg
}

In the code above typeconfig.Check Will return a error, Indicates that there is a syntax error in the above program :

5:8: cannot convert "a" (untyped string constant) to untyped int

The process of semantic analysis actually includes :

  • Type checking
  • Keyword expansion
  • Escape analysis
  • Variable capture
  • Function inlining
  • Closure processing intermediate code generation needs to introduce a concept at this stage ,SSA(static single assignment) Static single assignment , The main purpose of this stage is to analyze and process the AST Convert to SSA. That is, there is only one assignment process description for each variable at compile time . for example , Here's what I want to talk about Why? Such a layer of intermediate code generation is required , Why not put AST Direct conversion to assembly code or machine code . We will encounter various problems in the computer field , When encountering problems, it is usually the ideal difference between the current situation and the target state , Often this ideal difference seems to be an insurmountable gap , But there is a classic solution to these problems , Is to add a layer of agency between our seemingly unattainable goals and the status quo , It is also called intermediate state . And then, let's take the intermediate state something To our target state . The gap between high-level language and machine language encountered here is so ,Go Co process scheduler in GMP The same is true of the model . If one intermediate state is not enough, add several more layers . Use GOSSAFUNC=main go build hello.go Commands can generate hello.go The visual generation process during the compilation of files ssa.html:
image.png

You can see from the source code and AST To the final generated genssa During the process, it has gone through dozens of rounds of code conversion , Each step is the process of converting the code to something closer to assembly code , This process is called SSA Downgrade . Finally generated genssa The code is actually very close to assembly code .

Machine code generation

The process of machine code generation is actually aimed at different hardware platforms SSA Degraded replacement by intermediate code , Replace with the instruction set expansion of the target platform .Golang The reason why cross platform or cross compilation can be achieved is that the back-end part of the compiler is continuously iteratively degraded for different platforms and different instruction sets .

The interaction between computer hardware and software is accomplished through instruction set , The most common instruction set architecture classification method is to divide instructions into complex instruction sets according to their complexity (CISC) And reduced instruction set (RISC).

According to my personal understanding ,

Complex instruction set (CISC) In fact, our idea is very similar to the encapsulation of various underlying interfaces that we are familiar with in business development , There is a new demand , If you need a new ability that you haven't had before, I'll add an instruction , Constantly add new instructions to the instruction set to complete various things , With the addition of various directives, the binding force on the directive specifications can only decline day by day , As a result, the length of each instruction is different , And it's getting more and more complicated ;

Reduced instruction set (RISC) The idea of is a bit layered , In the instruction set, I only provide atomic operations that can accomplish various things , If you need any new capabilities or functions, you can encapsulate the various instructions provided by me on the upper layer , But don't expect to add new things to the instruction set , Of course, the reduced instruction set has a good ability to regulate all instructions , It can keep the length of each instruction equal , Relatively simple .

For the current software developers do not need direct access to assembly code , Instead, instructions are generated through compilers and assemblers , Complex machine instructions also increase the complexity and scalability of instruction generation for compilers , So almost all the instructions generated by the compiler now use the reduced instruction set .

The final generated in the intermediate code generation phase genssa The file will be converted into assembly code by the assembler in this step :

package main

import "fmt"

func main() {
        fmt.Printf("hello")
}
$ go tool compile -N -l -S hello.go
"".main STEXT size=96 args=0x0 locals=0x48 funcid=0x0
        0x0000 00000 (hello.go:5)       TEXT    "".main(SB), ABIInternal, $80-0 // TEXT  Pseudo operators , Code snippet id ;"".  Represents a namespace ;SB  Pseudo register ;static base  Static base address pointer ;80 Indicates the function stack frame length ;0 Indicates the parameter length 
        0x0000 00000 (hello.go:5)       MOVD    16(g), R1
        0x0004 00004 (hello.go:5)       PCDATA  $0, $-2 //  take PC register 0 The offset address is assigned as -2  The official statement is that the directive contains gc Information has nothing to do with the main process 
        0x0004 00004 (hello.go:5)       MOVD    RSP, R2
        0x0008 00008 (hello.go:5)       CMP     R1, R2 //  Compare stack space , Determine whether capacity expansion is needed 
        0x000c 00012 (hello.go:5)       BLS     72 //  If it needs to be expanded , Jump to 72 Address instruction , That is to say NOP That line 
        0x0010 00016 (hello.go:5)       PCDATA  $0, $-1
        0x0010 00016 (hello.go:5)       MOVD.W  R30, -80(RSP)
        0x0014 00020 (hello.go:5)       MOVD    R29, -8(RSP)
        0x0018 00024 (hello.go:5)       SUB     $8, RSP, R29
        0x001c 00028 (hello.go:5)       FUNCDATA        ZR, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) //  With the above PCDATA The instructions are the same , yes gc Instructions to be used 
        0x001c 00028 (hello.go:5)       FUNCDATA        $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
        0x001c 00028 (hello.go:6)       MOVD    $go.string."hello"(SB), R0
        0x0024 00036 (hello.go:6)       MOVD    R0, 8(RSP)
        0x0028 00040 (hello.go:6)       MOVD    $5, R0
        0x002c 00044 (hello.go:6)       MOVD    R0, 16(RSP)
        0x0030 00048 (hello.go:6)       STP     (ZR, ZR), 24(RSP)
        0x0034 00052 (hello.go:6)       MOVD    ZR, 40(RSP)
        0x0038 00056 (hello.go:6)       PCDATA  $1, ZR
        0x0038 00056 (hello.go:6)       CALL    fmt.Printf(SB) //  Function call instructions 
        0x003c 00060 (hello.go:7)       MOVD    -8(RSP), R29
        0x0040 00064 (hello.go:7)       MOVD.P  80(RSP), R30
        0x0044 00068 (hello.go:7)       RET     (R30)
        0x0048 00072 (hello.go:7)       NOP
        0x0048 00072 (hello.go:5)       PCDATA  $1, $-1
        0x0048 00072 (hello.go:5)       PCDATA  $0, $-2
        0x0048 00072 (hello.go:5)       MOVD    R30, R3
        0x004c 00076 (hello.go:5)       CALL    runtime.morestack_noctxt(SB) //  Perform stack expansion 
        0x0050 00080 (hello.go:5)       PCDATA  $0, $-1
        0x0050 00080 (hello.go:5)       JMP     0 //  Jump to 0 Address re execution 
        0x0000 81 0b 40 f9 e2 03 00 91 5f 00 01 eb e9 01 00 54  [email protected]_......T
        0x0010 fe 0f 1b f8 fd 83 1f f8 fd 23 00 d1 00 00 00 90  .........#......
        0x0020 00 00 00 91 e0 07 00 f9 a0 00 80 d2 e0 0b 00 f9  ................
        0x0030 ff ff 01 a9 ff 17 00 f9 00 00 00 94 fd 83 5f f8  .............._.
        0x0040 fe 07 45 f8 c0 03 5f d6 e3 03 1e aa 00 00 00 94  ..E..._.........
        0x0050 ec ff ff 17 00 00 00 00 00 00 00 00 00 00 00 00  ................
        rel 28+8 t=3 go.string."hello"+0
        rel 56+4 t=10 fmt.Printf+0
        rel 76+4 t=10 runtime.morestack_noctxt+0
go.cuinfo.packagename. SDWARFCUINFO dupok size=0
        0x0000 6d 61 69 6e                                      main
""..inittask SNOPTRDATA size=32
        0x0000 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00  ................
        0x0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
        rel 24+8 t=1 fmt..inittask+0
go.string."hello" SRODATA dupok size=5
        0x0000 68 65 6c 6c 6f                                   hello
type..importpath.fmt. SRODATA dupok size=6
        0x0000 00 00 03 66 6d 74                                ...fmt
gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
        0x0000 01 00 00 00 00 00 00 00                          ........

Add some notes for personal understanding where I think it is not easy to understand , You may have a problem understanding , Welcome to your advice .

link

The purpose of linking process is to link the target file or static library file compiled from multiple files to generate the final executable file . With my superficial understanding of linker , The main work of the link is actually the process of address relocation .

such as , When A.go Accessed in file B.go A variable defined in the file Ver,

// A.go
Ver = 42

Compile A The assembly code generated when the file is :

movl $0x2a, Ver

But at this time, the object code doesn't know ver The address of the variable , therefore A In the compiled object file Ver The address of will be set to 0. Only when the linker Links Ver The address of , It will be revised at this time A The variable address in the target file of . This process is called address relocation .

load

When we hit... On the command line ./hello And return to what happened in the middle ? The operating system helps us put our elf The file is loaded into memory and pc Register points to program entry address ,cpu Start a series of instructions for executing this program . In this process, first shell Will help us fork A subprocess , And call execv Load the executable file from disk to memory for execution . The whole calling process is :

sys_execve/sys_execveat
|
|--> do_execve
			|-->  do_execveat_common
					|-->  _do_execve_file
								|--> exec_binprm
										|--> search_binary_handler
												|--> load_elf_binary

After entering the program entry point , because go The boot process before the program is mostly assembly code , Let's read go In fact, I don't know where to start when I start the source code of , So you need to know where the program entry point is ( This article concerns go Source code to 1.17 The version shall prevail ):

gdb ./hello // gdb load elf file 

then info file View program entry points ,

(gdb) info file
Symbols from "/data/git/workspace/src/laboratory/hello/hello".
Local exec file:
        `/data/git/workspace/src/laboratory/hello/hello', file type elf64-x86-64.
        Entry point: 0x45c200
        0x0000000000401000 - 0x000000000047f7ac is .text
        0x0000000000480000 - 0x00000000004b51c4 is .rodata
        0x00000000004b5360 - 0x00000000004b5838 is .typelink
        0x00000000004b5840 - 0x00000000004b5898 is .itablink
        0x00000000004b5898 - 0x00000000004b5898 is .gosymtab
        0x00000000004b58a0 - 0x000000000050ea38 is .gopclntab
        0x000000000050f000 - 0x000000000050f020 is .go.buildinfo
        0x000000000050f020 - 0x000000000051f5e0 is .noptrdata
        0x000000000051f5e0 - 0x0000000000526df0 is .data
        0x0000000000526e00 - 0x0000000000555d08 is .bss
        0x0000000000555d20 - 0x000000000055b080 is .noptrbss
        0x0000000000400f9c - 0x0000000000401000 is .note.go.buildid

stay Entry point Add a breakpoint at , And then start the program :

(gdb) b *0x45c200
Breakpoint 1 at 0x45c200: file /usr/local/go/src/runtime/rt0_linux_amd64.s, line 8.
(gdb) r
Starting program: /data/git/workspace/src/laboratory/hello/./hello 

Breakpoint 1, _rt0_amd64_linux () at /usr/local/go/src/runtime/rt0_linux_amd64.s:8
8               JMP     _rt0_amd64(SB)

You can see that the program is suspended /usr/local/go/src/runtime/rt0_linux_amd64.s:8 here , Here is the go The entry in the source code . If we want to see go Source words , From here, it seems most appropriate .

#include "textflag.h"

TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
	JMP	_rt0_amd64(SB)

Actually go Program boot , Scheduling ,gc And so on runtime In the bag so everyone is reading go Source code time runtime Package is the study of go Operating mechanism , A very important part of running logic package.

// _rt0_amd64 is common startup code for most amd64 systems when using
// internal linking. This is the entry point for the program from the
// kernel for an ordinary -buildmode=exe program. The stack holds the
// number of arguments and the C-style argv.
TEXT _rt0_amd64(SB),NOSPLIT,$-8
	MOVQ	0(SP), DI	// argc
	LEAQ	8(SP), SI	// argv
	JMP	runtime·rt0_go(SB)

_rt0_amd64 It has been clearly stated in the function comments. In fact, the main program parameters are stored separately here DI and SI In two registers .

rt0_go This function does more , The most important lines :

	MOVQ	$runtime·mainPC(SB), AX	// runtime.main Put the function address in AX register 
    PUSHQ	AX
    PUSHQ	$0			// arg size
	CALL	runtime·newproc(SB) //  Create a new one goroutine, The goroutine binding runtime.main, Put it in P Local queues for , Waiting for dispatch 
	POPQ	AX
	POPQ	AX

	// start this M
	//  start-up M, Start scheduling goroutine
	CALL	runtime·mstart(SB)

This is the first in the program g and m The creation process of . be-all g All pass runtime.newproc Function creation ,

// 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()
	pc := getcallerpc()
	systemstack(func() {
		newg := newproc1(fn, gp, pc)

		_p_ := getg().m.p.ptr()
		runqput(_p_, newg, true)

		if mainStarted {
			wakep()
		}
	})
}

newproc The logic of the function is not complicated , Use newproc1 Create a g Structure , And put g Put it in the waiting queue , If m0 If it has been started, it will wake up one p Get up and do this g.

go back to rt0_go This function , It turns on m0 And g0 To carry out runtime.mainPC function , The definition of this function is here :

// mainPC is a function value for runtime.main, to be passed to newproc.
// The reference to runtime.main 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

Here it is transferred to runtime.main In the method , The definition of this method is a bit long, so the code is not listed here . It's mainly about N Multiple initialization operations , Include runtime Open background task ,gc,package Various... Introduced in package Of init Functions, etc ; The most important thing is to call main In bag main Method to enter the user-defined logic .

function

runtime It is a piece of content that can be taken out to do several special projects , I understand. runtime In fact, the language starts the background process of helping users do things in the background , Because I came from c++ The language has changed, so I want to compare the previous company in c++ How did the times do such things .

Embrace in the company golang Before , stay c++ Time , We often use spp The framework provides a set of user logic scheduling mechanism called micro threads , It depends on the active surrender of user level micro threads , Compared to the current go The coordination scheduling mechanism is simple and free , Users can control whether to give up at the right place cpu For other micro threads . at present rust Of tokio The scheduling mechanism of the co process implementation library is almost the same as that of the micro thread , Because neither language has runtime Runtime ,rust The most important feature of memory security is also built at compile time trait Upper , This design also led to rust Have higher requirements for the written code , There's only one way. Just walk over , No, I'll show you . This is what I understand runtime.

sign out

The last few lines of the bootstrap are like this :

fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
fn()
if raceenabled {
	racefini()
}

// Make racy client program work: if panicking on
// another goroutine at the same time as main returns,
// let the other goroutine finish printing the panic trace.
// Once it does, it will exit. See issues 3934 and 20018.
if atomic.Load(&runningPanicDefers) != 0 {
	// Running deferred functions should not take long.
	for c := 0; c < 1000; c++ {
		if atomic.Load(&runningPanicDefers) == 0 {
			break
		}
		Gosched()
	}
}
if atomic.Load(&panicking) != 0 {
	gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1)
}

exit(0)

main.main The program return After that, if there is still no implementation defer Of panic Information will be dispatched at most 1000 Execution times defer function .

summary

thus , One Go The program ended its vigorous and unrestrained life . The phases that appear in this article , Just take it out and make a special topic , Some places were not discussed in depth , Not fully unfolded ( Here is a metaphor , But I can't think of a suitable comparison object , Think about it and delete it ). I hope this article can help you sort out some concepts , Further, I hope to give you some inspiration , Further, I hope that others can extract some concepts from them to talk about . I love you so much to see here , thank !!!

Reference material

https://draveness.me/golang/

https://www.cnblogs.com/qcrao-2018/p/11124360.html

https://segmentfault.com/a/1190000040181868

https://segmentfault.com/a/1190000020996545

https://golang.design/under-the-hood/

Self cultivation of programmers -- link 、 Loading and storage

原网站

版权声明
本文为[wish42]所创,转载请带上原文链接,感谢
https://yzsam.com/2021/10/20211019173626110F.html