go创建m绑定系统线程

 


1、创建新的M


    1.1 从 sched.freem 中获取一个空闲的m,或者new 一个m

复制代码
// Create a new m. It will start off with a call to fn, or else the scheduler.
// fn needs to be static and not a heap allocated closure.
// May run with m.p==nil, so write barriers are not allowed.
//
// id is optional pre-allocated m ID. Omit by passing -1.
//
//go:nowritebarrierrec
func newm(fn func(), pp *p, id int64) {
    acquirem()

    mp := allocm(pp, fn, id)
    mp.nextp.set(pp)
    mp.sigmask = initSigmask
    if gp := getg(); gp != nil && gp.m != nil && (gp.m.lockedExt != 0 || gp.m.incgo) && GOOS != "plan9" {
        lock(&newmHandoff.lock)
        if newmHandoff.haveTemplateThread == 0 {
            throw("on a locked thread with no template thread")
        }
        mp.schedlink = newmHandoff.newm
        newmHandoff.newm.set(mp)
        if newmHandoff.waiting {
            newmHandoff.waiting = false
            notewakeup(&newmHandoff.wake)
        }
        unlock(&newmHandoff.lock)
        releasem(getg().m)
        return
    }
    newm1(mp)
    releasem(getg().m)
}

func newm1(mp *m) {
    if iscgo {
       ...省略cgo...
    }
    execLock.rlock() // Prevent process clone.
    newosproc(mp)
    execLock.runlock()
}
复制代码

2、创建系统线程


    2.1 创建轻量级进程 LWP

    将当前的m 结构体 g0的栈以及g0还有线程运行的函数mstart,也就是调度器运行函数传递个 clone 汇编代码,创建一个运行调度器的子线程

复制代码
func newosproc(mp *m) {
    stk := unsafe.Pointer(mp.g0.stack.hi)
    /*
     * note: strace gets confused if we use CLONE_PTRACE here.
     */
    if false {
        print("newosproc stk=", stk, " m=", mp, " g=", mp.g0, " clone=", abi.FuncPCABI0(clone), " id=", mp.id, " ostk=", &mp, "\n")
    }

    // Disable signals during clone, so that the new thread starts
    // with signals disabled. It will enable them in minit.
    var oset sigset
    sigprocmask(_SIG_SETMASK, &sigset_all, &oset)
    ret := retryOnEAGAIN(func() int32 {
        r := clone(cloneFlags, stk, unsafe.Pointer(mp), unsafe.Pointer(mp.g0), unsafe.Pointer(abi.FuncPCABI0(mstart)))
        // clone returns positive TID, negative errno.
        // We don't care about the TID.
        if r >= 0 {
            return 0
        }
        return -r
    })
    sigprocmask(_SIG_SETMASK, &oset, nil)

    if ret != 0 {
        print("runtime: failed to create new OS thread (have ", mcount(), " already; errno=", ret, ")\n")
        if ret == _EAGAIN {
            println("runtime: may need to increase max user processes (ulimit -u)")
        }
        throw("newosproc")
    }
}
复制代码

3、Clone系统调用


3.1 参数的保存

复制代码
// int32 clone(int32 flags, void *stk, M *mp, G *gp, void (*fn)(void));
TEXT runtime·clone(SB), NOSPLIT|NOFRAME, $0
    MOVL    flags+0(FP), DI  //将传入的`flags`参数放入`DI`寄存器
    MOVQ    stk+8(FP), SI    //将传入的`stk`参数放入`SI`寄存器
    // 将`DX`、`R10`、`R8`寄存器清零。这是为调用`clone`系统调用做准备
    MOVQ    $0, DX
    MOVQ    $0, R10
    MOVQ    $0, R8
    
    // Copy mp, gp, fn off parent stack for use by child.
    // Careful: Linux system call clobbers CX and R11. 注意的是,Linux系统调用会修改`CX`和`R11`寄存器
    // 从函数参数中获取`mp`、`gp`、`fn`的值,分别存入`R13`、`R9`和`R12`寄存器。
    MOVQ    mp+16(FP), R13
    MOVQ    gp+24(FP), R9
    MOVQ    fn+32(FP), R12  // 将fn(FP+32 位置,也就是第5个参数:unsafe.Pointer(abi.FuncPCABI0(mstart))) 放入R12

复制代码

 3.2 m和g0非空检查

    // 检查`mp`和`gp`是否为零,如果其中任何一个为零,跳转到`nog1`。
    CMPQ    R13, $0    // m
    JEQ     nog1
    CMPQ    R9, $0    // g
    JEQ     nog1

3.3 TLS的初始化

复制代码
    // 计算TLS(线程局部存储)的地址并存入`R8`寄存器。
LEAQ m_tls(R13), R8

#ifdef GOOS_android
    // Android stores the TLS offset in runtime·tls_g.
    SUBQ    runtime·tls_g(SB), R8
#else

    // 根据elf格式调整TLS的地址。
    ADDQ    $8, R8    // ELF wants to use -8(FS)
#endif
    //将`CLONE_SETTLS`标志添加到`DI`寄存器中。
    ORQ     $0x00080000, DI // add flag CLONE_SETTLS(0x00080000) to call clone
复制代码

3.4 创建轻量级进程(LWP)

    执行clone 系统调用,创建子进程(线程),并执行子进程的初始化(sp/m/g/tls)

复制代码
nog1:
    // 执行`clone`系统调用。系统调用的编号放在`AX`寄存器中,并使用`SYSCALL`指令执行。
    MOVL    $SYS_clone, AX 
    SYSCALL
    
    // In parent, return.
    CMPQ    AX, $0          // 检查系统调用返回值,区分父子进程。
    JEQ     3(PC)           //  返回值为0:子进程,跳到第三条指令。不等于0父进程往下执行。
    MOVL    AX, ret+40(FP)  //不等于0父进程,将返回值存储在返回值位置
    RET                     // 并返回。

    // In child, on new stack.
    MOVQ    SI, SP    // 在子进程中,设置新的栈指针。

    // If g or m are nil, skip Go-related setup. 再次检查`mp`和`gp`是否为零,如果是,跳到`nog2`。
    CMPQ    R13, $0    // m
    JEQ     nog2
    CMPQ    R9, $0    // g
    JEQ     nog2

    // Initialize m->procid to Linux tid 在子进程中,获取Linux线程ID,并将其存储到`mp->procid`。
    MOVL    $SYS_gettid, AX
    SYSCALL
    MOVQ    AX, m_procid(R13)

    // In child, set up new stack
    // 设置TLS,初始化g0 m0等数据结构。调用`runtime·stackcheck`函数进行栈检查。
    get_tls(CX)
    MOVQ    R13, g_m(R9)  // g(R9).m = R13 = mp(新的m)
    MOVQ    R9, g(CX)     // g(CX) = g0
    MOVQ    R9, R14 // set g register   R14 = g0
    CALL    runtime·stackcheck(SB)
复制代码

3.5 启动调度器

     从上述调用我们知道,传递过来要线程执行的函数为mstart 从引导顺序中知道mstart启动了调度器,

那此处就是在子线程中运行调度器。

复制代码
nog2:
    // Call fn. This is the PC of an ABI0 function.
    CALL    R12   // 调用线程将要执行的函数也就是 abi.FuncPCABI0(mstart) 从新拉起一个调度器
    // 以上函数将不会再结束返回,除非退出线程
    // It shouldn't return. If it does, exit that thread.
    MOVL    $111, DI
    MOVL    $SYS_exit, AX
    SYSCALL
    JMP     -3(PC)    // keep exiting
复制代码

 

posted @   G1733  阅读(19)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示