Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

What is the startup process of the Go program?

2025-01-15 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article mainly explains "what is the startup process of the Go program". The explanation in the article is simple and clear, and it is easy to learn and understand. Please follow the editor's train of thought to study and learn "what is the startup process of the Go program".

Go boot phase

Find the entrance

First compile the sample program mentioned above:

$GOFLAGS= "- ldflags=-compressdwarf=false" go build

The GOFLAGS parameter is specified in the command because debug information is compressed in order to reduce the binary size from Go1.11 onwards. Makes it impossible to understand what compressed DWARF means when using gdb on MacOS (and that's exactly what I'm using MacOS).

Therefore, you need to turn it off in this debugging, and then use gdb to debug, in order to achieve the purpose of observation:

$gdb awesomeProject (gdb) info files Symbols from "/ Users/eddycjy/go-application/awesomeProject/awesomeProject". Local exec file: `/ Users/eddycjy/go-application/awesomeProject/awesomeProject', file type mach-o-x86-64. Entry point: 0x1063c80 0x0000000001001000-0x00000000010a6aca is .text... (gdb) b * 0x1063c80 Breakpoint 1 at 0x1063c80: file / usr/local/Cellar/go/1.15/libexec/src/runtime/rt0_darwin_amd64.s, line 8.

Through the debugging of Entry point, we can see that the real program entry is in the runtime package, and different computer architectures point to different points. For example:

MacOS is in src/runtime/rt0_darwin_amd64.s.

Linux is in src/runtime/rt0_linux_amd64.s.

It eventually points to the rt0_darwin_amd64.s file, which has a very intuitive name:

Breakpoint 1 at 0x1063c80: file / usr/local/Cellar/go/1.15/libexec/src/runtime/rt0_darwin_amd64.s, line 8.

Rt0 stands for the abbreviation of runtime0, referring to the creation of the runtime, super daddy:

Darwin represents the target operating system (GOOS).

Amd64 represents the target operating system architecture (GOHOSTARCH).

At the same time, Go language also supports more target system architectures, such as AMD64, AMR, MIPS, WASM and so on:

Source code directory

If you are interested, you can check it further under the src/runtime directory, which will not be introduced here.

Entrance method

In the rt0_linux_amd64.s file, you can see that _ rt0_amd64_darwin JMP jumps to the _ rt0_amd64 method:

TEXT _ rt0_amd64_darwin (SB), NOSPLIT,$-8 JMP _ rt0_amd64 (SB)...

Then jump to the runtime ·rt0 _ go method:

TEXT _ rt0_amd64 (SB), NOSPLIT,$-8 MOVQ 0 (SP), DI / / argc LEAQ 8 (SP), SI / / argv JMP runtime ·rt0 _ go (SB)

This method moves the argc and argv inputted by the program from memory to register.

The first two values of the stack pointer (SP) are argc and argv, respectively, the number of corresponding parameters and the values of specific parameters.

Open the main line

After the program parameters are ready, the formal initialization method falls in the runtime ·rt0 _ go method:

TEXT runtime ·rt0 _ go (SB), NOSPLIT,$0... CALL runtime ·check (SB) MOVL 16 (SP), AX / / copy argc MOVL AX, 0 (SP) MOVQ 24 (SP), AX / / copy argv MOVQ AX, 8 (SP) CALL runtime ·args (SB) CALL runtime ·osinit (SB) CALL runtime ·schedinit (SB) / / create a new goroutine to start program MOVQ $runtime ·mainPC (SB) AX / / entry PUSHQ AX PUSHQ $0 / / arg size CALL runtime ·newproc (SB) POPQ AX POPQ AX / / start this M CALL runtime ·mstart (SB).

Runtime.check: run-time type checking, mainly to verify whether the compiler's translation is correct and whether there are "pits". The basic code is to check whether int8 is equal to 1 under the unsafe.Sizeof method.

Runtime.args: system parameter transfer, mainly passing the system parameter conversion to the program.

Runtime.osinit: basic system parameter settings, mainly to obtain the number of CPU cores and memory physical page size.

Runtime.schedinit: initializes various runtime components, including scheduler, memory allocator, heap, stack, GC, and so on. The p is initialized and m0 is bound to a p.

Runtime.main: the main job is to run main goroutine, and although it points to $runtime ·mainPC in runtime ·rt0 _ go, it essentially points to runtime.main.

Runtime.newproc: create a new goroutine and bind the runtime.main method (that is, the entry main method in the application). And put it into the local queue of p bound by m0 for subsequent scheduling.

Runtime.mstart: start m, and the scheduler starts round robin.

In the runtime ·rt0 _ go method, it mainly completes all kinds of runtime checks, sets and acquires system parameters, and initializes a large number of Go basic components.

After initialization, the main cooperative program (main goroutine) is run and put into the waiting queue (GMP model). Finally, the scheduler begins to schedule round robin.

Summary

According to the above source code analysis, the following flow chart of Go application boot can be obtained:

Go program boot process

In the Go language, the actual running entrance is not the main func written by the user every day, let alone the runtime.main method, but starts from rt0_*_amd64.s, and finally JMP all the way to runtime ·rt0 _ go, and then completes a series of initialization actions that Go itself needs to complete in this method.

As a whole, it includes:

Runtime type checking, system parameter passing, CPU core number acquisition and setting, runtime component initialization (scheduler, memory allocator, stack, stack, GC, etc.).

Run main goroutine.

Run a large number of default behaviors such as the corresponding GMP.

A great deal of knowledge related to the scheduler is involved.

The follow-up analysis will further analyze the love and hate in runtime ·rt0 _ go, especially the scheduling methods such as runtime.main and runtime.schedinit, which are of great learning value, and interested partners can continue to pay attention.

Go Scheduler initialization

Now that we know how the Go program boots, we need to understand how the scheduler flows in Go Runtime.

Runtime.mstart

The main focus here is on the runtime.mstart method:

Func mstart () {/ / get g0 _ g _: = getg () / / determine the stack boundary osStack: = _ g_.stack.lo = = 0 if osStack {size: = _ g_.stack.hi if size = = 0 {size = 8192 * sys.StackGuardMultiplier} _ g_.stack.hi = uintptr (noescape (unsafe.Pointer (& size) _ g_.stack.lo = _ g_.stack.hi-size + 1024} _ g_.stackguard0 = _ g_.stack.lo + _ StackGuard _ g_.stackguard1 = _ g_.stackguard0 / / launch m Perform scheduler round robin mstart1 () / / exit thread if mStackIsSystemAllocated () {osStack = true} mexit (osStack)}

Call the getg method to get g in the GMP model, which in this case is G0.

Determine whether it is a system stack by checking the boundary of g's execution stack _ g_.stack (the boundary of the stack happens to be lo, hi). If so, the boundary of the execution stack is initialized according to the system stack.

Call the mstart1 method to start the system thread m for round robin scheduling of the scheduler.

Call the mexit method to exit the system thread m.

Runtime.mstart1

So the real logic lies in the mstart1 method, which we continue to analyze:

Func mstart1 () {/ / get g And determine whether it is g0 _ g _: = getg () if _ g _! = _ g_.m.g0 {throw ("bad runtime ·mstart")} / / initialize m and record the caller pc, sp save (getcallerpc (), getcallersp () asminit () minit () / / set signal handler if _ gus.m = & M0 {mstartm0 ()} / / run startup function if fn: = _ g_.m.mstartfn Fn! = nil {fn ()} if _ glos.m! = & m0 {acquirep (_ g_.m.nextp.ptr ()) _ g_.m.nextp = 0} schedule ()}

Call the getg method to get g. And determine whether the acquired g is G0 by the previously bound _ g_.m.g0. If not, a fatal error is thrown directly. Because the scheduler only runs on G0.

Call the minit method to initialize m, and record the caller's PC and SP, which is convenient for reuse in subsequent schedule phases.

If it is determined that the m bound by the current g is M0, the mstartm0 method is called to set the signal handler. This action must come after the minit method so that the minit method can prepare the thread in advance so that it can process the signal.

If the m bound by the current g has a startup function, it runs. Or skip it.

If the m bound by the current g is not M0, you need to call the acquirep method to get and bind p, that is, m is bound to p.

Call the schedule method for formal scheduling.

After a long circle of work, we finally entered the main course of the topic. The original latent schedule method is the real scheduling method, and the rest are pre-processing and data preparation.

Due to the space problem, the schedule method will be further analyzed in the next part. Let's first focus on some of the details of this article.

Deep analysis of the problem

However, the space here has been relatively long, and a lot of problems have been accumulated. We analyze the two elements with the highest exposure rates in Runtime:

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

What is M0 and what is the function?

What is G0 and what is its function?

M0

M0 is the first system thread created by Go Runtime. A Go process has only one M0, also known as the main thread.

In many ways:

Data structure: M0 is no different from other created m.

Creation process: M0 is the process that should be compiled and copied directly to M0 at startup, while other subsequent m is created by itself in Go Runtime.

Variable declaration: M0 is the same as regular m, and M0 is defined as var M0 m, which is nothing special.

G0

G is generally divided into three types, which are:

The person who performs user tasks is called g.

Execute the main goroutine of the runtime.main.

The person who performs the scheduling task is called G0.

G0 is special, each m has only one G0 (only one G0), and each m is bound to only one G0. G0 is also assigned through assembly, and the rest of the subsequent creation is regular g.

In many ways:

Data structure: G0 and other created g are the same in data structure, but there are stack differences. The stack on G0 is allocated to the system stack, and the stack size on Linux is fixed by default 8MB, which cannot be expanded or reduced. The conventional g starts with only 2KB, which can be expanded.

Running status: G0 is different from the regular g, it does not have so many running states, and it will not be preempted by the scheduler. The scheduling itself runs on G0.

Variable declaration: the definition of var G0 and the regular gjournal G0 is G0 g, nothing special.

Thank you for your reading, the above is the content of "what is the startup process of Go program". After the study of this article, I believe you have a deeper understanding of what the startup process of Go program is, and the specific use needs to be verified in practice. Here is, the editor will push for you more related knowledge points of the article, welcome to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report