本文所使用的Golang为1.14,dlv为1.4.0。

源代码

package main

import "fmt"

func main() {
fmt.Println("Hello")
}

开始调试

root@xiamin:~/study# dlv debug test.go
Type 'help' for list of commands.
(dlv) l
> _rt0_amd64_linux() /root/go/src/runtime/rt0_linux_amd64.s:8 (PC: 0x465800)
Warning: debugging optimized function
3: // license that can be found in the LICENSE file.
4:
5: #include "textflag.h"
6:
7: TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
=> 8: JMP _rt0_amd64(SB)
9:
10: TEXT _rt0_amd64_linux_lib(SB),NOSPLIT,$0
11: JMP _rt0_amd64_lib(SB)

可以看到最开始是从_rt0_amd64_linux执行,然后直接跳转到_rt0_amd64。执行si进入_rt0_amd64。

(dlv) si
> _rt0_amd64() /root/go/src/runtime/asm_amd64.s:15 (PC: 0x461c20)
Warning: debugging optimized function
10: // _rt0_amd64 is common startup code for most amd64 systems when using
11: // internal linking. This is the entry point for the program from the
12: // kernel for an ordinary -buildmode=exe program. The stack holds the
13: // number of arguments and the C-style argv.
14: TEXT _rt0_amd64(SB),NOSPLIT,$-8
=> 15: MOVQ 0(SP), DI // argc,将参数个数存入DI
16: LEAQ 8(SP), SI // argv,参数数组的地址存入SI
17: JMP runtime·rt0_go(SB)

继续执行,runtime.rt0_go() /root/go/src/runtime/asm_amd64.s:89 (PC: 0x461c30)

runtime.rt0_go

runtime.rt0_go中代码较多,但我们只关注与调度相关的。

TEXT runtime·rt0_go(SB),NOSPLIT,$0
// 忽略处理命令行参数相关 // 为全局变量g0设置一些栈相关的属性
MOVQ $runtime·g0(SB), DI // 将全局变量g0的存入DI
LEAQ (-64*1024+104)(SP), BX // bx = SP-(64*1024+104),g0的栈帧大小
MOVQ BX, g_stackguard0(DI) // g0.stackguard0 = bx
MOVQ BX, g_stackguard1(DI) // g0.stackguard1 = bx
MOVQ BX, (g_stack+stack_lo)(DI) // g0.stack.lo = bx 栈的低地址
MOVQ SP, (g_stack+stack_hi)(DI) // g0.stack.hi = sp 栈的高地址 // 忽略获取cpu型号等相关与cgo初始化 // 线程本地存储(tls)相关设置
LEAQ runtime·m0+m_tls(SB), DI // di = &m0.tls
CALL runtime·settls(SB) // 设置tls,下面有详细分析 // 验证tls是否生效:通过tls设置一个数值,然后m0.tls[0]获取,与设置的值对比。
get_tls(BX) // 获取fs地址到bx
MOVQ $0x123, g(BX) // 反编译后 mov qword ptr fs:[0xfffffff8], 0x123,表示设置fs-8地址中的内容为0x123,其实就是m0.tls[0]的地址。
MOVQ runtime·m0+m_tls(SB), AX // ax = m0.tls[0]
CMPQ AX, $0x123 // 比较
JEQ 2(PC)
CALL runtime·abort(SB) // m0.tls[0] = &g0; g0与m0相互绑定
get_tls(BX) // 获取fs地址到bx
LEAQ runtime·g0(SB), CX // cx = &g0
MOVQ CX, g(BX) // m0.tls[0] = &g0
LEAQ runtime·m0(SB), AX // ax = &m0
MOVQ CX, m_g0(AX) // m0.g0 = &g0
MOVQ AX, g_m(CX) // g0.m = &m0 // 忽略copy argc和argv的代码
CALL runtime·args(SB) // 命令行参数相关,暂不关心
CALL runtime·osinit(SB) // 设置全局变量ncpu(cpu个数),全局变量physHugePageSize
CALL runtime·schedinit(SB) // 调度器初始化 // 调用runtime·newproc创建goroutine,指向函数为runtime·main
MOVQ $runtime·mainPC(SB), AX // runtime·mainPC就是runtime·main
PUSHQ AX // newproc的第二个参数,也就是goroutine要执行的函数。
PUSHQ $0 // newproc的第一个参数,表示要传入runtime·main中参数的大小,此处为0。
// 创建 main goroutine。非main goroutine也是此方法创建。
// go编译会将语句 go foo() 编译为 runtime·newproc(SB) 并传入参数。
CALL runtime·newproc(SB)
POPQ AX
POPQ AX CALL runtime·mstart(SB) // 进入调度循环
CALL runtime·abort(SB) // mstart应该永不返回,如果返回,则是程序出现错误了。
RET
MOVQ $runtime·debugCallV1(SB), AX
RET
DATA runtime·mainPC+0(SB)/8,$runtime·main(SB)
GLOBL runtime·mainPC(SB),RODATA,$8

runtime·settls 设置线程本地存储

runtime·settls中通过调用arch_prctl系统调用设置FS来实现线程本地存储。

通过syscall指令调用系统调用

  • rax存放系统调用号,调用返回值也会放在rax中
  • 当系统调用参数小于等于6个时,参数则须按顺序放到寄存器 rdi,rsi,rdx,r10,r8,r9中。
  • 如果系统调用的参数数量大于6个,需将参数保存在一块连续的内存中,并将地址存入rbx中。

新建非m0的m时也会通过runtime·clone调用此函数。

TEXT runtime·settls(SB),NOSPLIT,$32
// 此时di = &m.tls[0]
ADDQ $8, DI // ELF 需要使用 -8(FS),di+=8,执行完此指令后 di = &m.tls[1]
MOVQ DI, SI // 将地址移动到si中,作为系统调用的第二个参数
MOVQ $0x1002, DI // ARCH_SET_FS表示设置FS,作为系统调用的第一个参数
MOVQ $SYS_arch_prctl, AX // rax存储系统调用号
SYSCALL
CMPQ AX, $0xfffffffffffff001 // 比较返回结果
JLS 2(PC)
MOVL $0xf1, 0xf1 // crash
RET

runtime.schedinit 调度初始化

runtime.schedinit中包含了很多功能的初始化,本文暂且分析与调度相关的

func schedinit() {

	_g_ := getg()		// 未找到getg()的源代码,通过注释得知getg()返回当前g,此处 _g_为&g0
..........
sched.maxmcount = 10000 // m的最大数量为10000
..........
mcommoninit(_g_.m) // 此处_g_.m即为m0,对m0的一些初始化工作,下面详细分析
..........
// 获取要初始化的p的数量,默认与cpu个数相同,如果指定了GOMAXPROCS,则为GOMAXPROCS
procs := ncpu
if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
procs = n
}
// 初始化allp并为allp中的元素初始化、赋值等,详见下方
if procresize(procs) != nil {
throw("unknown runnable goroutine during bootstrap")
}
..........
}

schedinit->mcommoninit

func mcommoninit(mp *m) {
_g_ := getg() // 获取当前g,也就是g0 // g0 stack won't make sense for user (and is not necessary unwindable).
if _g_ != _g_.m.g0 {
callers(1, mp.createstack[:]) // 调用栈相关
} lock(&sched.lock)
if sched.mnext+1 < sched.mnext {
throw("runtime: thread ID overflow")
}
mp.id = sched.mnext // 设置m的id
sched.mnext++ // 加1,以后分配给下一个m
checkmcount() // 检查非空闲数量的m是否超过了10000 // rand相关
mp.fastrand[0] = uint32(int64Hash(uint64(mp.id), fastrandseed))
mp.fastrand[1] = uint32(int64Hash(uint64(cputicks()), ^fastrandseed))
if mp.fastrand[0]|mp.fastrand[1] == 0 {
mp.fastrand[1] = 1
} // 新建一个32k栈大小的g,赋值给m0.gsignal。并使 m0.gsignal.m = *m0
mpreinit(mp)
if mp.gsignal != nil {
mp.gsignal.stackguard1 = mp.gsignal.stack.lo + _StackGuard
} // 下面两步将mp放入全局变量allm中,allm是个链表
mp.alllink = allm
atomicstorep(unsafe.Pointer(&allm), unsafe.Pointer(mp))
unlock(&sched.lock) // Allocate memory to hold a cgo traceback if the cgo call crashes.
if iscgo || GOOS == "solaris" || GOOS == "illumos" || GOOS == "windows" {
mp.cgoCallers = new(cgoCallers)
}
}

mcommoninit基本上就是做一些m0的初始化。

schedinit->procresize

// 传入参数nprocs为期望的所有p的个数
func procresize(nprocs int32) *p {
old := gomaxprocs // gomaxprocs在本方法的末尾会被更改
if old < 0 || nprocs <= 0 {
throw("procresize: invalid arg")
}
if trace.enabled {
traceGomaxprocs(nprocs)
} // 更新统计信息
now := nanotime()
if sched.procresizetime != 0 {
sched.totaltime += int64(old) * (now - sched.procresizetime)
}
sched.procresizetime = now // 初始化allp
if nprocs > int32(len(allp)) {
// Synchronize with retake, which could be running
// concurrently since it doesn't run on a P.
lock(&allpLock)
if nprocs <= int32(cap(allp)) {
allp = allp[:nprocs]
} else {
// 初始化一个临时变量nallp,与现存的allp合并,然后将nallp赋值给全局变量allp
nallp := make([]*p, nprocs)
copy(nallp, allp[:cap(allp)])
allp = nallp
}
unlock(&allpLock)
} // 初始化新添加到allp中的元素
for i := old; i < nprocs; i++ {
pp := allp[i]
if pp == nil {
pp = new(p)
}
pp.init(i) // 会初始化p结构的属性:id,status,mcache等
atomicstorep(unsafe.Pointer(&allp[i]), unsafe.Pointer(pp)) // 赋值
} _g_ := getg()
// 初始化的时候 _g_.m.p = 0 所以走else
if _g_.m.p != 0 && _g_.m.p.ptr().id < nprocs {
// continue to use the current P
_g_.m.p.ptr().status = _Prunning
_g_.m.p.ptr().mcache.prepareForSweep()
} else {
// 此处省略一些初始化时不会进入的代码 _g_.m.p = 0
_g_.m.mcache = nil
p := allp[0]
p.m = 0
p.status = _Pidle
acquirep(p) // m.mcache = p.mcache;p和m相互绑定;p.status = _Prunning。下面有分析。
if trace.enabled {
traceGoStart()
}
} // 释放未使用的p的资源,比如调用runtime.GOMAXPROCS(num),会调用procresize。
// num小于当前p的数量时,会执行此处
for i := nprocs; i < old; i++ {
p := allp[i]
p.destroy()
// can't free P itself because it can be referenced by an M in syscall
} // Trim allp.
if int32(len(allp)) != nprocs {
lock(&allpLock)
allp = allp[:nprocs]
unlock(&allpLock)
} // 将除了当前m绑定p的其余allp中的都以链表形式存入sched.pidle中
var runnablePs *p
for i := nprocs - 1; i >= 0; i-- {
p := allp[i]
if _g_.m.p.ptr() == p { // 是否是当前g.m的p
continue
}
p.status = _Pidle
if runqempty(p) {
pidleput(p) // 将p放入到空闲列表中
} else {
p.m.set(mget())
p.link.set(runnablePs)
runnablePs = p
}
}
// 这里会更改gomaxprocs的值
stealOrder.reset(uint32(nprocs))
var int32p *int32 = &gomaxprocs // make compiler check that gomaxprocs is an int32
atomic.Store((*uint32)(unsafe.Pointer(int32p)), uint32(nprocs))
return runnablePs
}

总结一下procresize的工作:

  • allp切片中p的数量小于期望p数量时,对allp进行扩容
  • 使用new创建p并调用p.init初始化刚扩容出的,init中为p分配id和mcache
  • 初始化时,调用acquirep使allp[0]与m0相互绑定,并且m.mcache = p.mcache,p.status = _Prunning
  • allp切片中p的数量大于期望p数量时,调用p.destroy释放未使用的p的资源
  • 将除了allp[0]之外的p状态设置为_Pidle并加入到全局空闲列表sched.pidle中
  • 更改gomaxprocs值为nprocs

acquirep(p)->wirep(_p_) :acquirep中的主要逻辑就是调用了wirep

func wirep(_p_ *p) {
_g_ := getg() if _g_.m.p != 0 || _g_.m.mcache != nil {
throw("wirep: already in go")
}
if _p_.m != 0 || _p_.status != _Pidle {
id := int64(0)
if _p_.m != 0 {
id = _p_.m.ptr().id
}
print("wirep: p->m=", _p_.m, "(", id, ") p->status=", _p_.status, "\n")
throw("wirep: invalid p state")
}
_g_.m.mcache = _p_.mcache // p的mcache赋值给m.mcache
_g_.m.p.set(_p_) // 与下面的一行为 p和m相互绑定
_p_.m.set(_g_.m)
_p_.status = _Prunning // 更改p的状态
}

Golang源码学习:调度逻辑(一)初始化的更多相关文章

  1. Golang源码学习:调度逻辑(二)main goroutine的创建

    接上一篇继续分析一下runtime.newproc方法. 函数签名 newproc函数的签名为 newproc(siz int32, fn *funcval) siz是传入的参数大小(不是个数):fn ...

  2. quagga源码学习--BGP协议的初始化

    quagga支持BGP-4,BGP-4+协议,支持多协议(mpls,isis,ospf等等)以及单播,组播路由的导入和分发. 具体的协议,这里就不附录了,网络上有很多资料,或者RFC. 协议源码的学习 ...

  3. Golang源码学习:使用gdb调试探究Golang函数调用栈结构

    本文所使用的golang为1.14,gdb为8.1. 一直以来对于函数调用都仅限于函数调用栈这个概念上,但对于其中的详细结构却了解不多.所以用gdb调试一个简单的例子,一探究竟. 函数调用栈的结构(以 ...

  4. Golang源码学习:调度逻辑(四)系统调用

    Linux系统调用 概念:系统调用为用户态进程提供了硬件的抽象接口.并且是用户空间访问内核的唯一手段,除异常和陷入外,它们是内核唯一的合法入口.保证系统的安全和稳定. 调用号:在Linux中,每个系统 ...

  5. Golang源码学习:调度逻辑(三)工作线程的执行流程与调度循环

    本文内容主要分为三部分: main goroutine 的调度运行 非 main goroutine 的退出流程 工作线程的执行流程与调度循环. main goroutine 的调度运行 runtim ...

  6. Golang源码学习:监控线程

    监控线程是在runtime.main执行的时候在系统栈中创建的,监控线程与普通的工作线程区别在于,监控线程不需要绑定p来运行. 监控线程的创建与启动 简单的调用图 先给出个简单的调用图,好心里有数,逐 ...

  7. 【js】vue 2.5.1 源码学习 (七) 初始化之 initState 响应式系统基本思路

    大体思路(六) 本节内容: 一.生命周期的钩子函数的实现 ==> callHook(vm , 'beforeCreate') beforeCreate 实例创建之后 事件数据还未创建 二.初始化 ...

  8. Spring源码学习

    Spring源码学习--ClassPathXmlApplicationContext(一) spring源码学习--FileSystemXmlApplicationContext(二) spring源 ...

  9. Spring 源码学习 04:初始化容器与 DefaultListableBeanFactory

    前言 在前一篇文章:创建 IoC 容器的几种方式中,介绍了四种方式,这里以 AnnotationConfigApplicationContext 为例,跟进代码,看看 IoC 的启动流程. 入口 从 ...

随机推荐

  1. MaxCompute Studio提升UDF和MapReduce开发体验

    原文链接:http://click.aliyun.com/m/13990/ UDF全称User Defined Function,即用户自定义函数.MaxCompute提供了很多内建函数来满足用户的计 ...

  2. vue elementui table 双击单元格实现编辑,聚焦,失去焦点,显示隐藏input和span

    <el-table :data="tableData" class="tb-edit" style="width: 100%" ref ...

  3. CF思维联系– CodeForces - 991C Candies(二分)

    ACM思维题训练集合 After passing a test, Vasya got himself a box of n candies. He decided to eat an equal am ...

  4. Codeforce 1311A Add Odd or Subtract Even

    Add Odd or Subtract Even time limit per test2 seconds memory limit per test256 megabytes inputstanda ...

  5. DP背包(一)

    01背包 for(int i=0;i<n;i++) //遍历每一件物品 for(int j=v;j>=wei[i];j--)//遍历背包容量,表示在上一层的基础上,容量为J时,第i件物品装 ...

  6. 数据结构--顺序栈--C++实现

    #include <iostream> #define MaxSize 5000 using namespace std; template <typename T> clas ...

  7. muduo网络库源码学习————线程本地单例类封装

    muduo库中线程本地单例类封装代码是ThreadLocalSingleton.h 如下所示: //线程本地单例类封装 // Use of this source code is governed b ...

  8. pycharm 新建文件后选错文件格式怎么改

    经常在新建文件的时候,忘记填写文件后缀,导致文件无默认格式,而且同名字的文件怎么改都改不成想要的格式,所以随手记录一下怎么修正: 原因:肯定是pycharm已经默认指定了一个格式,所以再重复新建同样名 ...

  9. ssrf爆破mysql

    php ssrf 代码<?php $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $_GET['url']); #curl_setopt($ch ...

  10. 一键运行CIS安全扫描,集群安全无忧!

    CIS安全扫描是Rancher 2.4推出的其中一个重磅功能,旨在帮助用户快速.有效地加强集群的安全性.本文将详细介绍CIS安全扫描这一功能,包含详细的操作demo. 本文来自Rancher Labs ...