GMP源码剖析

1. GMP简介

G: goroutine,用户级线程或协程,在用户态调度  
M:machine,内核级线程,在内核态调度  
P:processor,存放G队列,P必须绑定M,队列中的G才可以执行  
m0:主线程对应的结构  
g0:每个M都绑定一个g0,栈的大小为8K,用来执行runtime的代码  

2. 入口函数

_rt0_【ARCH】_【OS】,例如ARCH为amd64,OS为linux的入口函数为_rt0_amd64_linux,如下图:

entry_20211203170821.png

3. TLS

TLS(Thread Local Storage),每个线程会绑定m.tls的地址,tls类型为[6]uintptr,首个元素为当前运行的g,g中保存m

4. 运行前的准备工作

a. 初始化m0所绑定的g0的栈,大小约64k,区别于其他m所绑定的栈为8K的g0,如下图:  

stack_20211203172931.png

b. 绑定m0与g0,如下图:

bind_20211203173621.png

c. 命令行参数、系统信息、调度等初始化,如下图:

init_20211203173928.png

5. 主goroutine的创建

main函数所在的goroutine的创建,如下图:  
其它goroutine由go关键字来创建,编译器会自动把go关键字转成newproc函数

main_20211203174652.png

6. goroutine调度

每个的线程的入口函数都是mstart,mstart执行schedule,schedule依次从本地P、全局P、其它P、netpoll中取g  
如果都没有取到,线程则休眠,一旦取到,则调用execute来执行,若当前goroutine执行完毕,则调用goexit0继续调度
自动执行goexit0的原因在于创建goroutine的时候,把goexit的函数地址设定到goroutine根函数返回时pop所在的栈位置  
ret指令会执行pop pc,如下图:

exit_20211203181103.png

call_20211203181816.png

7. 系统监控

goroutine会专门启动一个线程来处理netpoll、长时间运行的goroutine,如下图:
a. 如果已经有可读或可写的goroutine并且超过10ms得不到执行,则加入全局P中
b. 如果某个goroutine长时间处于运行状态超过10ms,则会被标记成抢占状态,待调用某个函数时因栈溢出触发morestack函数,进而被其他goroutine抢占
c. 如果某个goroutine处于系统调用状态,如果此时本地P还有别的goroutine,或者执行时间超过10ms,则唤醒一个M与这个P绑定,进而P中剩下的goroutine可以得到执行

sysmon_20211203183126.png

retake_20211203183227.png

标签: none

添加新评论