当前位置:网站首页>Assembly function MCALL systemstack asmcgocal system call

Assembly function MCALL systemstack asmcgocal system call

2020-11-07 20:56:12 XXX small m

Turn to the article : https://studygolang.com/artic...

Statement

The following analysis is based on Golang1.14 edition .
Different hardware platforms use different assembly files , The functions analyzed in this paper mcall, systemstack, asmcgocall Is based on asm_arm64.s Assembly files .
No operating system platforms use different system calls , The functions analyzed in this paper syscall Is based on asm_linux_arm64.s Assembly files .

CPU The context of

The essence of these functions is to switch goroutine,goroutine When switching, you need to switch CPU Execution context , There are mainly 2 Value of registers SP( The top address of the stack used by the current thread ),PC( The address of the next instruction to be executed ).

mcall function

mcall The function is defined as follows ,mcall The function pointer is passed in , The types of incoming functions are as follows , There is only one parameter goroutine The pointer to , No return value .

func mcall(fn func(*g) 

mcall The function executes the scheduling code in the system stack , And the scheduling code doesn't return , Will be executed again during the run mcall.mcall The process is to save the current g The context of , Switch to g0 The context of , Pass in function parameters , Jump to function code execution .

// void 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|NOFRAME, $0-8
    // Save caller state in g->sched
    // At this point, the thread's current sp pc bp The context is stored in the register   You need to write the value of the register back to g  Here's how to write back g The process of 
    MOVD    RSP, R0  // R0 = RSP
    MOVD    R0, (g_sched+gobuf_sp)(g)  // g_sp = RO  preservation sp Register value 
    MOVD    R29, (g_sched+gobuf_bp)(g) // g_bp = R29 (R29 preservation bp value )
    MOVD    LR, (g_sched+gobuf_pc)(g)  // g_pc = LR (LR preservation pc value )
    MOVD    $0, (g_sched+gobuf_lr)(g)  // g_lr = 0
    MOVD    g, (g_sched+gobuf_g)(g)    // ???

    // Switch to m->g0 & its stack, call fn.
    //  Change the current g Cut it to g0
    MOVD    g, R3  // R3 = g (g Represents the current call mcall At the time of the goutine)
    MOVD    g_m(g), R8 // R8 = g.m (R8 Express g The binding of m  That is, the present m)
    MOVD    m_g0(R8), g // g = m.g0 ( Will the current g Switch to a g0)
    BL  runtime·save_g(SB) // ???
    CMP g, R3 // g == g0  R3 ==  call mcall Of g  It must not be equal 
    BNE 2(PC) //  If you don't want to wait, do it normally 
    B   runtime·badmcall(SB) //  Equality means that there is bug  call badmcall
    // fn Is the function to call   Write to register 
    MOVD    fn+0(FP), R26           // context R26 Deposit is fn Of pc
    MOVD    0(R26), R4          // code pointer R4 It's also fn Of pc value 
    MOVD    (g_sched+gobuf_sp)(g), R0  // g0 Of  sp Values are assigned to registers 
    MOVD    R0, RSP // sp = m->g0->sched.sp
    MOVD    (g_sched+gobuf_bp)(g), R29 // g0 Of bp The value is assigned to the corresponding register 
    MOVD    R3, -8(RSP) // R3 Was assigned to call before mcall Of g  Now write g0 In the stack of   As fn The function parameter of 
    MOVD    $0, -16(RSP) //  The null value here is not very understandable   Only one parameter and no return value   Why reserve in the stack 8 byte 
    SUB $16, RSP //  Offset stack 16byte( above g $0  Each account 8byte)
    BL  (R4) // R4 Now it's fn Of pc value   Jump to it  PC perform fn
    B   runtime·badmcall2(SB) //  This function never returns   So this step can never be carried out in theory  

Common calls mcall The functions executed are :

mcall(gosched_m)
mcall(park_m)
mcall(goexit0)
mcall(exitsyscall0)
mcall(preemptPark)
mcall(gopreempt_m) 

systemstack function

systemstack The function is defined as follows , The function passed in has no parameters , No return value .

func systemstack(fn func()) 

systemstack The function is executed in the system stack and can only be executed by g0( or gsignal?) Scheduling code executed , and mcall The difference is , After executing the scheduling code, it will switch back to the code being executed now .
This part of the source code annotation has only a general understanding of the process , Many details can't be worked out . The main process is to judge the current running g Is it g0 perhaps gsignal, If so, run it directly , If not, switch to g0, After executing the function, switch to g Return to the use of transfer .

// systemstack_switch is a dummy routine that systemstack leaves at the bottom
// of the G stack. We need to distinguish the routine that
// lives at the bottom of the G stack from the one that lives
// at the top of the system stack because the one at the top of
// the system stack terminates the stack walk (see topofstack()).
TEXT runtime·systemstack_switch(SB), NOSPLIT, $0-0
    UNDEF
    BL  (LR)    // make sure this function is not leaf
    RET

// func systemstack(fn func())
TEXT runtime·systemstack(SB), NOSPLIT, $0-8
    MOVD    fn+0(FP), R3    // R3 = fn
    MOVD    R3, R26     // context R26 = R3 = fn
    MOVD    g_m(g), R4  // R4 = m

    MOVD    m_gsignal(R4), R5   // R5 = m.gsignal
    CMP g, R5  // m.gsignal You have the authority to execute fn Of g
    BEQ noswitch //  If it is equal, it is already m.gsignale 了   There is no need to switch 

    MOVD    m_g0(R4), R5    // R5 = g0
    CMP g, R5  //  If the current g It's already g0  It means that there is no need to switch 
    BEQ noswitch

    MOVD    m_curg(R4), R6 // R6 = m.curg
    CMP g, R6 // m.curg == g
    BEQ switch

    // Bad: g is not gsignal, not g0, not curg. What is it?
    // Hide call from linker nosplit analysis.
    MOVD    $runtime·badsystemstack(SB), R3
    BL  (R3)
    B   runtime·abort(SB)

switch:
    // save our state in g->sched. Pretend to
    // be systemstack_switch if the G stack is scanned.
    MOVD    $runtime·systemstack_switch(SB), R6
    ADD $8, R6  // get past prologue
    //  The following is the general preservation of the current g The context of 
    MOVD    R6, (g_sched+gobuf_pc)(g)
    MOVD    RSP, R0
    MOVD    R0, (g_sched+gobuf_sp)(g)
    MOVD    R29, (g_sched+gobuf_bp)(g)
    MOVD    $0, (g_sched+gobuf_lr)(g)
    MOVD    g, (g_sched+gobuf_g)(g)

    // switch to g0
    MOVD    R5, g  // g = R5 = g0
    BL  runtime·save_g(SB)
    MOVD    (g_sched+gobuf_sp)(g), R3 // R3 = sp
    // make it look like mstart called systemstack on g0, to stop traceback
    SUB $16, R3  // sp Address   Memory alignment 
    AND $~15, R3
    MOVD    $runtime·mstart(SB), R4
    MOVD    R4, 0(R3)
    MOVD    R3, RSP
    MOVD    (g_sched+gobuf_bp)(g), R29 // R29 = g0.gobuf.bp

    // call target function
    MOVD    0(R26), R3  // code pointer
    BL  (R3)

    // switch back to g
    MOVD    g_m(g), R3
    MOVD    m_curg(R3), g
    BL  runtime·save_g(SB)
    MOVD    (g_sched+gobuf_sp)(g), R0
    MOVD    R0, RSP
    MOVD    (g_sched+gobuf_bp)(g), R29
    MOVD    $0, (g_sched+gobuf_sp)(g)
    MOVD    $0, (g_sched+gobuf_bp)(g)
    RET

noswitch:
    // already on m stack, just call directly
    // Using a tail call here cleans up tracebacks since we won't stop
    // at an intermediate systemstack.
    MOVD    0(R26), R3  // code pointer  R3 = R26 = fn
    MOVD.P  16(RSP), R30    // restore LR  R30 = RSP + 16(systemstack After the call is completed, the next instruction PC value ?)
    SUB $8, RSP, R29    // restore FP  R29 = RSP - 8  It means stack 
    B   (R3) 

asmcgocall function

asmcgocall The function is defined as follows , The parameters passed in are 2 Function pointer and parameter pointer , The return parameter is int32.

func asmcgocall(fn, arg unsafe.Pointer) int32 

asmcgocall The function is used to execute cgo Code , This part of the code can only be found in g0( or gsignal, osthread) Stack execution of , Therefore, the process is to determine whether the current stack should be switched first , If there is no need to switch, execute directly nosave Then return , Otherwise, save the current g The context of , And then switch to g0, After execution cgo Code back to g, Then return .

// func asmcgocall(fn, arg unsafe.Pointer) int32
// Call fn(arg) on the scheduler stack,
// aligned appropriately for the gcc ABI.
// See cgocall.go for more details.
TEXT ·asmcgocall(SB),NOSPLIT,$0-20
    MOVD    fn+0(FP), R1  // R1 = fn
    MOVD    arg+8(FP), R0  // R2 = arg

    MOVD    RSP, R2     // save original stack pointer
    CBZ g, nosave  //  If g by nil  The jump to  nosave. g == nil Whether it indicates that the current is osthread?
    MOVD    g, R4  // R4 = g

    // Figure out if we need to switch to m->g0 stack.
    // We get called to create new OS threads too, and those
    // come in on the m->g0 stack already.
    MOVD    g_m(g), R8 // R8 = g.m
    MOVD    m_gsignal(R8), R3 // R3 = g.m.gsignal
    CMP R3, g  //  If g == g.m.signal jump nosave
    BEQ nosave
    MOVD    m_g0(R8), R3 //  If g== m.g0 jump nosave
    CMP R3, g
    BEQ nosave

    // Switch to system stack.
    // save g The context of 
    MOVD    R0, R9  // gosave<> and save_g might clobber R0
    BL  gosave<>(SB)
    MOVD    R3, g
    BL  runtime·save_g(SB)
    MOVD    (g_sched+gobuf_sp)(g), R0
    MOVD    R0, RSP
    MOVD    (g_sched+gobuf_bp)(g), R29
    MOVD    R9, R0

    // Now on a scheduling stack (a pthread-created stack).
    // Save room for two of our pointers /*, plus 32 bytes of callee
    // save area that lives on the caller stack. */
    MOVD    RSP, R13
    SUB $16, R13
    MOVD    R13, RSP  // RSP = RSP - 16
    MOVD    R4, 0(RSP)  // save old g on stack  RSP.0 = R4 = oldg
    MOVD    (g_stack+stack_hi)(R4), R4 // R4 = old.g.stack.hi
    SUB R2, R4  // R4 = oldg.stack.hi - old_RSP
    MOVD    R4, 8(RSP)  // save depth in old g stack (can't just save SP, as stack might be copied during a callback)
    BL  (R1) // R1 = fn
    MOVD    R0, R9 // R9 = R0 = errno?

    // Restore g, stack pointer. R0 is errno, so don't touch it
    MOVD    0(RSP), g  // g = RSP.0 = oldg
    BL  runtime·save_g(SB)
    MOVD    (g_stack+stack_hi)(g), R5 // R5 = g.stack.hi
    MOVD    8(RSP), R6 // R6 = RSP + 8 = oldg.stack.hi - old_RSP
    SUB R6, R5 // R5 = R5 - R6 = old_RSP
    MOVD    R9, R0 // R0 = R9 = errno
    MOVD    R5, RSP // RSP = R5 = old_RSP

    MOVW    R0, ret+16(FP) // ret = R0 = errno
    RET

nosave:
    // Running on a system stack, perhaps even without a g.
    // Having no g can happen during thread creation or thread teardown
    // (see needm/dropm on Solaris, for example).
    // This code is like the above sequence but without saving/restoring g
    // and without worrying about the stack moving out from under us
    // (because we're on a system stack, not a goroutine stack).
    // The above code could be used directly if already on a system stack,
    // but then the only path through this code would be a rare case on Solaris.
    // Using this code for all "already on system stack" calls exercises it more,
    // which should help keep it correct.
    MOVD    RSP, R13 
    SUB $16, R13  
    MOVD    R13, RSP // RSP = RSP - 16
    MOVD    $0, R4 // R4 = 0
    MOVD    R4, 0(RSP)  // Where above code stores g, in case someone looks during debugging.
    MOVD    R2, 8(RSP)  // Save original stack pointer.  RSP + 8 = old_R2
    BL  (R1)
    // Restore stack pointer.
    MOVD    8(RSP), R2  // R2 = RSP + 8 = old_R2
    MOVD    R2, RSP // RSP = old_R2 = old_RSP
    MOVD    R0, ret+16(FP) // ret = R0 = errno
    RET 

syscall function

Syscall The function is defined as follows , Pass in 4 Parameters , return 3 Parameters .

func syscall(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) 

syscall The function is used to pass in the address and parameters of the system call , Return... After execution . The process is mainly executed before system call entersyscall, Set up g p The state of , And then we'll go into the reference , After execution , Write the return value and execute exitsyscall Set up g p The state of .
entersyscall and exitsyscall stay g Call in detail .

// func Syscall(trap int64, a1, a2, a3 uintptr) (r1, r2, err uintptr);
// Trap # in AX, args in DI SI DX R10 R8 R9, return in AX DX
// Note that this differs from "standard" ABI convention, which
// would pass 4th arg in CX, not R10.

// 4 Input parameters :PC param1 param2 param3
TEXT ·Syscall(SB),NOSPLIT,$0-56
    //  call entersyscall  Judgment is whether the execution conditions are satisfied   Record scheduling information   Switch g p The state of 
    CALL    runtime·entersyscall(SB)
    //  Put the parameters in the register 
    MOVQ    a1+8(FP), DI
    MOVQ    a2+16(FP), SI
    MOVQ    a3+24(FP), DX
    MOVQ    trap+0(FP), AX  // syscall entry
    SYSCALL
    CMPQ    AX, $0xfffffffffffff001
    JLS ok
    //  When execution fails   Write the return value 
    MOVQ    $-1, r1+32(FP)
    MOVQ    $0, r2+40(FP)
    NEGQ    AX
    MOVQ    AX, err+48(FP)
    //  call exitsyscall  Record scheduling information 
    CALL    runtime·exitsyscall(SB)
    RET
ok:
    //  When the execution is successful   Write the return value 
    MOVQ    AX, r1+32(FP)
    MOVQ    DX, r2+40(FP)
    MOVQ    $0, err+48(FP)
    CALL    runtime·exitsyscall(SB)
    RET 

except Syscal also Syscall6( except fn also 6 Parameters ) The due 6 System call with parameters . Achieve the same thing with little difference , There is no analysis here .

Summary and reflection

1. The function of assembly function . Why? golang Assembly functions must be introduced ? because CPU The context of execution is register , Only assembly language can operate registers .
2.CPU The context and g.sched(gobuf) The fields in the structure correspond one by one , Only 10 Less than fields , Therefore, the efficiency of context switching is very high .
3. except golang, Whether other languages in use need similar assembly to realize the interaction between language and operating system ?

Last

except mcall function , Other functions do not understand the details of the specific implementation , After strengthening the knowledge related to the compilation, fill in the hole .

版权声明
本文为[XXX small m]所创,转载请带上原文链接,感谢