惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
B
Blog RSS Feed
宝玉的分享
宝玉的分享
腾讯CDC
博客园_首页
T
Tailwind CSS Blog
月光博客
月光博客
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
M
MIT News - Artificial intelligence
A
About on SuperTechFans
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
MongoDB | Blog
MongoDB | Blog
博客园 - 聂微东
V
Visual Studio Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
SecWiki News
SecWiki News
美团技术团队
P
Privacy International News Feed
H
Help Net Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Microsoft Security Blog
Microsoft Security Blog
Know Your Adversary
Know Your Adversary
Y
Y Combinator Blog
D
DataBreaches.Net
Project Zero
Project Zero
T
The Blog of Author Tim Ferriss
Cyberwarzone
Cyberwarzone
C
Cybersecurity and Infrastructure Security Agency CISA
C
Cisco Blogs
S
Schneier on Security
G
GRAHAM CLULEY
博客园 - 三生石上(FineUI控件)
Cisco Talos Blog
Cisco Talos Blog
小众软件
小众软件
Forbes - Security
Forbes - Security
D
Docker
T
Tenable Blog
S
Secure Thoughts
雷峰网
雷峰网
S
Security @ Cisco Blogs
T
The Exploit Database - CXSecurity.com
The Cloudflare Blog
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
阮一峰的网络日志
阮一峰的网络日志

Jiajun的技术笔记

你好,2026! TiDB 源码阅读(六):TiDB Coprocessor 源码解析 性能优化的核心思想 TiDB 源码阅读(五):索引 TiDB 源码阅读(四):AST、逻辑计划、物理计划 CockroachDB Serverless Architecture podman 无故退出 Cursor Control-L (CTRL-L) Keyboard Shortcuts in Terminal Replace docker with podman Using xmonad with xfce4 A RC script for freebsd frpc 自己动手写一个k8s controller AI 会取代你的(编程)岗位吗? 自建DERP服务器提升Tailscale连接速度(使用Nginx转发) 自动升级Docker容器 再读《程序员修炼之道-从小工到专家》 让浏览器下载文件 再读《软件随想录》/《黑客与画家》/《软技能》 HTTP 压力测试中的 Coordinated Omission 2的补码 编程语言中的 context 是什么? flutter macOS 构建出错 Flatpak 使用小记 Golang CAS 操作是怎么实现的 PostgreSQL 当MQ来使用 Clash 结合 工作VPN 的网络设计 使用 PostgreSQL 搭建 JuiceFS PostgreSQL 配置优化和日志分析 有GitHub Copilot?那就可以搭建你的ChatGPT4服务 窗口函数的使用(以PG为例) 读《为什么学生不喜欢上学》 OpenAI Prompt Engineering 摘录和总结 读《打造真正的新产品》 VueJS 总结 Linux 自动挂载 alist 提供的webdav FreeBSD 使用 vm-bhyve 安装Debian虚拟机 FreeBSD 和 Linux 网卡聚合实现提速 GPT 帮我搞定了时区转换问题 长任务系统如何处理? macOS/Linux 编译 InputLeap 使用开源软KVM - synergy-core 解决 macOS 终端hostname一直变化问题 KVM 共享 Intel 集成显卡 PromQL 备忘 读《格鲁夫给经理人的第一课》 读《打开心智》 为什么要把复杂的联表操作拆成多个单表查询? 红包系统的设计 MySQL Index Condition Pushdown Optimization Go mod 简明教程 OpenWRT 使用 Android/iOS USB 网络 搭建旁路由 Golang gRPC 错误处理 编写可维护的单元测试代码 OAuth 2 详解(六):Authorization Code Flow with PKCE OAuth 2 详解(五):Device Authorization Flow OAuth 2 详解(三):Resource Owner Password Credentials Grant OAuth 2 详解(四):Client Credentials Flow OAuth 2 详解(二):Implict Grant Flow OAuth 2 详解(一):简介及 Authorization Code 模式 ElasticSearch 学习笔记 三种git流程以及发版模型 错误处理实践 权限模型(RBAC/ABAC) OIDC(OpenID Connect) 简介 任务队列简介 PostgreSQL 操作笔记 使用Drone CI构建CI/CD系统 Golang migrate 做数据库变更管理 使用PostgreSQL做搜索引擎 Nginx 源码阅读(三): 连接池、内存池 Nginx 源码阅读(二): 请求处理 Nginx 源码阅读(一): 启动流程 Go 泛型简明教程 KVM 显卡穿透给 Windows 使用 HTTP Router 处理 Telegram Bot 按钮回调 使用反射(reflect)对结构体赋值 GIN 是如何绑定参数的 你好 2022(2021 年终总结) 用Go导入大型CSV到PostgreSQL 使用 OpenWRT 搭建软路由 使用软KVM切换器 barrier 共享键鼠 SQL 防注入及原理 使用 gomock 测试 Go 代码 gevent不是黑魔法(二): gevent 实现 gevent不是黑魔法(一): greenlet 实现 用 entgo 替代 gorm 应用内使用crontab不是那么方便 单测时要不要 mock 数据库? Sentry 自建指南 用selenium完成自动化任务 用闲置的安卓手机做垃圾电话短信过滤 推荐三个时间管理工具 一次事故反思 当JS遇到uint64:JS整数溢出问题 SQLite3 存储以及ACID原理 Redis源码阅读:pub/sub实现 Redis源码阅读:zset实现 Redis源码阅读:bitmap 位图的运算 Redis源码阅读:set是怎么做交并集运算的?
Golang runtime 源码阅读与分析
Jiajun Huang · 2018-02-02 · via Jiajun的技术笔记

只想简单地写写,就不写的太复杂了。注释都在: https://github.com/jiajunhuang/go

  • Go的编译方式是静态编译,把runtime直接编译到最终的可执行文件里。首先我们把代码考过来,然后编译出 go 这个可执行文件 出来。

  • 编写以下代码,然后用我们自己编译出来的go来编译出一个二进制文件。注意要带调试信息并且禁止优化的,要不然不方便看。

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	fmt.Println("hello world!")
    }
    
    $ ../bin/go build -gcflags "-N -l" -o test_demo1 demo1.go
    $ gdb test_demo1
    (gdb) source /home/jiajun/Code/go/src/runtime/runtime-gdb.py
    Loading Go Runtime support.
    (gdb) info files
    Symbols from "/home/jiajun/Code/go/analysis/test_demo1".
    Local exec file:
    	`/home/jiajun/Code/go/analysis/test_demo1', file type elf64-x86-64.
    	Entry point: 0x44fa90
    	0x0000000000401000 - 0x0000000000482608 is .text
    	0x0000000000483000 - 0x00000000004c4e3f is .rodata
    	0x00000000004c4f60 - 0x00000000004c5ac0 is .typelink
    	0x00000000004c5ac0 - 0x00000000004c5b00 is .itablink
    	0x00000000004c5b00 - 0x00000000004c5b00 is .gosymtab
    	0x00000000004c5b00 - 0x0000000000514042 is .gopclntab
    	0x0000000000515000 - 0x0000000000521bdc is .noptrdata
    	0x0000000000521be0 - 0x00000000005286f0 is .data
    	0x0000000000528700 - 0x0000000000544d88 is .bss
    	0x0000000000544da0 - 0x00000000005474b8 is .noptrbss
    	0x0000000000400f9c - 0x0000000000401000 is .note.go.buildid
    (gdb) b *0x44fa90
    Breakpoint 1 at 0x44fa90: file /home/jiajun/Code/go/src/runtime/rt0_linux_amd64.s, line 8.
    

然后就跳到 rt0_linux_amd64.s 看。虽然没有系统的学汇编,但是边看边猜还是可以继续看下去的。

TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
	JMP	_rt0_amd64(SB)

然后发现 _rt0_amd64 继续不下去了。于是:

(gdb) b _rt0_amd64
Breakpoint 2 at 0x44c2b0: file /home/jiajun/Code/go/src/runtime/asm_amd64.s, line 15.

发现跳到了 rt0_go,不过gdb直接打断点发现打不出来,于是就在同一个文件里尝试搜索。 发现在: https://github.com/jiajunhuang/go/blob/67a58c5a2401e89fd4f688e8f70fd3be9506cea5/src/runtime/asm_amd64.s#L87

  • 继续跟踪,发现有标签,最后到了 ok 这个标签。

    (gdb) b runtime.g0
    Function "runtime.g0" not defined.
    Make breakpoint pending on future shared library load? (y or [n]) n
    (gdb) b runtime.m0
    Function "runtime.m0" not defined.
    Make breakpoint pending on future shared library load? (y or [n]) n
    (gdb) b runtime.check
    Breakpoint 3 at 0x434890: file /home/jiajun/Code/go/src/runtime/runtime1.go, line 141.
    (gdb) b runtime.args
    Breakpoint 4 at 0x434340: file /home/jiajun/Code/go/src/runtime/runtime1.go, line 65.
    (gdb) b runtime.osinit
    Breakpoint 5 at 0x424750: file /home/jiajun/Code/go/src/runtime/os_linux.go, line 274.
    (gdb) b runtime.schedinit
    Breakpoint 6 at 0x428b30: file /home/jiajun/Code/go/src/runtime/proc.go, line 508.
    (gdb) b runtime.mainPC
    Function "runtime.mainPC" not defined.
    Make breakpoint pending on future shared library load? (y or [n]) n
    (gdb) b runtime.main
    Breakpoint 7 at 0x427980: file /home/jiajun/Code/go/src/runtime/proc.go, line 131.
    (gdb) b runtime.newproc
    Breakpoint 8 at 0x42f540: file /home/jiajun/Code/go/src/runtime/proc.go, line 3321.
    (gdb) b runtime.mstart
    Breakpoint 9 at 0x42a920: file /home/jiajun/Code/go/src/runtime/proc.go, line 1208.
    

因此函数调用链是:

runtime.check -> runtime.args -> runtime.osinit -> runtime.schedinit -> runtime.newproc

最后一步里的 runtime.newproc 之前有把 runtime.mainPC 压栈。

MOVQ    $runtime·mainPC(SB), AX     // entry

// 然后再下面就有:
DATA    runtime·mainPC+0(SB)/8,$runtime·main(SB)

所以应该是 runtime.mainPC 作为入口点,由 runtime.newproc 来执行进入。

  • 需要提前把我读到的知识剧透,方便读者理解。

    • Go的runtime中,M是 Machine,代表操作系统的线程
    • P是 Processor,意思是逻辑处理器,在最初的版本里是没有P的
    • G是 Goroutine。是Go中执行任务的单元,也是coroutine中的最小个体。
    • 在最初的版本里没有P,所以MG是 M:N。历史原因不是特别了解,不过我猜测引入P的原因是,当M执行系统调用或者cgo代码 而阻塞时,如果没有P的存在,那么该M上的所有G就无法执行。而引入P之后,可以把该M上的P摘掉,放到别的M上执行。
  • coroutine又称微线程,协程,纤程等。原因是,线程是操作系统调度的最小单位,而coroutine则是用户态的 “线程”。线程的 创建,销毁,切换代价非常的高。通过线程池无法解决c10k问题,而 I/O多路复用+回调的方式写起来又比较反人类,所以有协程 这么一个东西。在用户态,以同步的方式写异步。通过某些关键字主动让出执行权限,而后等到 I/O 事件准备好时,再切换回来。

例如Python中的 Tornado 就通过yield+I/O多路复用回调实现了协程。gevnet则更黑一点,直接利用Python导入的机制把标准库的 代码patch。AsyncIO 其实差不多。不过 TornadoAsyncIO 的异步代码都具有传染性,说的是例如 @gen.coroutine 这种 玩意儿。

所以有了Go这种,在语言层面实现异步的方式(gevent其实与此十分类似)。

  • newproc 执行 systemstack 函数。这个函数的作用是在系统栈中调用给定的函数 fn。看他的注释:

    // systemstack runs fn on a system stack.
    // If systemstack is called from the per-OS-thread (g0) stack, or
    // if systemstack is called from the signal handling (gsignal) stack,
    // systemstack calls fn directly and returns.
    // Otherwise, systemstack is being called from the limited stack
    // of an ordinary goroutine. In this case, systemstack switches
    // to the per-OS-thread stack, calls fn, and switches back.
    // It is common to use a func literal as the argument, in order
    // to share inputs and outputs with the code around the call
    // to system stack:
    //
    //	... set up y ...
    //	systemstack(func() {
    //		x = bigcall(y)
    //	})
    //	... use x ...
    //
    // systemstack如果是由g0调用,或者收到信号而调用,就会调用fn然后返回。
    // 否则,systemstack切换到 per-OS-thread栈执行完fn之后,又切回去
    //go:noescape
    func systemstack(fn func())
    

newprocsystemstack 传的参数便是 newproc1。而 newproc1 做的事情就是新建一个Goroutine丢到队列里。而执行的fn 就是 runtime.mainPC,就是 runtime.main

  • 接下来读到 runtime.main,没多远就执行了一个

    151     systemstack(func() {                                                                         
    152         newm(sysmon, nil)                                                                        
    153     })
    

这个sysmon是 system monitor,负责抢占,检查网络事件等。

然后执行

- `runtime_init`。这个是动态生成的。
- `gcenable` 启动gc
- `main_init` 动态生成的。
- `main_main` 就是我们 `main` 包里的main函数了。
- 通过for循环确保 `&runningPanicDefers` 为0才退出。
- `exit` 退出。

_init 的函数都是动态生成的,顺序与 import 顺序不一定一致,但是被依赖的包的init完了才会init当前文件。


似乎到这里,整个启动流程就看完了。接下来我们跳到其中的细节去看。


https://github.com/jiajunhuang/go/blob/67a58c5a2401e89fd4f688e8f70fd3be9506cea5/src/runtime/proc.go#L508

  • findrunnable 中实现了work stealing:

    • 检查是否处于GC态
    • 检查本地有没有可执行的G
    • 检查全局队列有没有可执行的G
    • 检查网络I/O有没有可以恢复执行的G
    • 去别的队列里偷
    • 还是没有,就把自己挂起
  • runtime里最重要的几个文件:

    • runtime1.go 初始化时的检测
    • runtime2.go 初始化等
    • proc.go 调度,work stealing等
    • mheap.gomalloc.go 内存分配相关实现
  • 如何实现协程?可以看看我的这个项目:

https://github.com/jiajunhuang/storm

虽然看起来是Cython写的,其实是Python,当时为了练习一下Cython就全部加上了类型改了后缀名,没差。Python中实现协程 毕竟简单多了,方便理解。


相关文章