本文所使用的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. 微软宣布一批新获得Microsoft Teams认证的会议硬件

    COVID-19 疾病流行期间,微软见到了 Microsoft Teams 视频会议解决方案取得的巨大增长.然而在许多情况下,生产力与音视频硬件的质量息息相关.好消息是,该公司刚刚完成了一批第三方硬件 ...

  2. Flutter仿网易云音乐:播放界面

    写在前头 本来是要做一个仿网易云音乐的flutter项目,但是因为最近事情比较多,项目周期跨度会比较长,因此分几个步骤来完成.这是仿网易云音乐项目系列文章的第一篇.没有完全照搬网易云音乐的UI,借鉴了 ...

  3. mac 关闭系统完整性保护 SIP(System Integrity Protection)的方法

    在 OS X El Capitan 中有一个跟安全相关的模式叫 SIP(System Integrity Protection ) ,它禁止让软件以 root 身份来在 mac 上运行,并且对于目录 ...

  4. 【COCOS2DX-LUA 脚本开发之四】使用tolua++编译pk创建自定义类

    此篇基本[COCOS2DX(2.X)_LUA开发之三]在LUA中使用自定义精灵(LUA脚本与自创建类之间的访问)及LUA基础讲解 在Lua第三篇中介绍了,如何在cocos2dx中使用Lua创建自定义类 ...

  5. 原生JS设计轮播图

    一.效果预览: 由于只能上传2M以下的图片,这里只截取了自动切换的效果: 二.编写语言 HTML.CSS.原生JS 三.编写思路 (一)HTML部分 1..slide意为滑槽,里面存放所有图片: 2. ...

  6. 【HBase】Java实现过滤器查询

    目录 概述 代码实现 rowKey过滤器RowFilter 列族过滤器FamilyFilter 列过滤器QualifierFilter 列值过滤器ValueFilter 专用过滤器 单列值过滤器 Si ...

  7. Excel函数有门槛,是编程

    Excel的公式体系,最简单的就是输入“=1+1”.“=2*3”.看起来没有门槛,但实质上是函数式编程,Range写Formula,Power Query写M语言,VBA里写Function.通过菜单 ...

  8. Web_php_include-攻防世界

    0x00 简介 记录这个题纯粹是为了记录以下有关strstr()函数的相关知识. 0x01 题目 <?php show_source(__FILE__); echo $_GET['hello'] ...

  9. 向大家介绍我的新书:《基于股票大数据分析的Python入门实战》

    我在公司里做了一段时间Python数据分析和机器学习的工作后,就尝试着写一本Python数据分析方面的书.正好去年有段时间股票题材比较火,就在清华出版社夏老师指导下构思了这本书.在这段特殊时期内,夏老 ...

  10. 前端面试题-WebSocket的实现和应用

    (1)什么是WebSocket? WebSocket是HTML5中的协议,支持持久连续,http协议不支持持久性连接.Http1.0和HTTP1.1都不支持持久性的链接,HTTP1.1中的keep-a ...