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
人生还有意义。那一定是还在找存在的理由
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?