什么是G栈
为什么G需要自己的栈
在参数被拷到G栈上之前, G栈是空的, 拷上去以后了现在就是非空. G栈上有一段等待执行的代码, 以及一些参数. 我们拿着这些参数, 去执行这段代码, 这就是我们要求G要做的事情对吧?
在执行你这段函数的时候, 你完全还会定义一些变量之类的东西, 然后你还会去调用一些别的函数对吧? 比如上面的函数add, 它就定义了一个局部变量c, 同时又调用了fmt.Println
这个函数, 我们又要搞一遍函数入栈, 参数入栈这一套了.
现在问题来了, 你认为c这个变量是存在哪儿的? fmt.Println
参数入栈又是入到哪儿去的? 这是一个非常简单非常直观的问题, 能直接用来回答: "为什么每个G都需要一个自己的栈了"
G0(零)是什么毛?
这个名字很鬼畜, 很中二. "零", 一种零式战机的感觉. ok, 不吐槽名字了, 我们要回答一下G0是干什么的, 以及为什么存在
回到协程创建之初, 到底是谁执行的"创建"这个动作? 某个G, 联合P与M, 形成GMP, 在运行某段代码的时候, 执行的创建这个动作, 对吧? 当时的场景是这样的对吧. Go语言中所有要执行的代码分成
用户定义的代码 -> 执行你的add(1,1)代码
管理工作所需要的代码 -> 创建一个G所需要执行的代码
G0就是被安排来做这样的管理工作的, 所有创建过程中用到的临时变量, 包括打包成一个funcval
都是在G0栈上完成的.
像上面你看到的那样, 通过调用system_stack函数, 这个函数内所有要执行的动作, 全都要切换到G0栈上去做的. 后面的你都知道了, 我们会将fn, args等等全都打包成funcval, 然后把包拷贝到G栈上去.
G栈的定义
之前已经提过了为什么会有G栈, G栈用来存放什么的, 其实人家的官方名字叫"连续栈", 后面我们会详细描述, 为什么它连续, 2K的默认内存是如何扩张的, 有了这些基础, 就非常容易理解抢占调度的发生了
G是通过一个叫做malg()
的函数创建的, 理解这种协程一样的东西本身跟系统没关系, 是你自己搞的自己定义的结构体, 我们只是为它分配了一段内存, 作为它的栈. 这就是在创建G的时候实际发生的事情, 现在我们来解释上面的内容, 首先一个栈:
就是: 一段内存, 而一段内存
就是: 一枚栈顶指针+ 一枚栈底指针, 而你的栈
就是: 两枚指针所囊括的区域
这里出现了第三枚指针stackguard,名字很中二, 栈卫士, 一种王国卫兵的感觉. 这是一枚非常重要的指针, 栈扩张/抢占调度都是以这枚指针作为起点的. 我们先看看创建出一个新栈是怎么样的.
👆如上图所示, 我们申请了一段长度为2k的内存, 作为新G的栈, 并在两头分别设置上lo/hi两枚指针,SP指针代表目前已经用了多少内存了, 被设置在hi位, 代表目前还没使用任何内存. 在离栈顶640字节的位置设置上stackguard指针, ok这就是我们的卫兵了.
G栈的扩容morestack
ok现在假设你的G一直运行运行, 栈里的内存也一直消耗着. 现在你想调用函数f, 在调用之前我们要确保栈里的内存还够f使用, 不要等f运行到一半发现内存不够, 临时再去扩容. 所以在函数调用之前搞内存检查是最合适的, 编译器会插入一段负责检查内存的代码
在上面我们提过SP起于hi点, 越用越往lo点走, 内存地址越小, 期间会经过stackguard点, 等走到lo点了就代表内存用完了. 检查逻辑如下
如果已经低于stackguard0, 可以确定已经溢出, 直接扩容
如果是高于stackguard0, 但是高的不多, 不够这个函数用怎么办? [lo,stackguard0]之间还有一点空间可以用, 适当的溢出还是可以接受的
扩容就是通过调用newstack()
函数, 重新分配一个两倍大小的内存, 然后将现在的栈拷贝过去, 重新分配地址与内存以后, 调整这个G栈的相关参数. 最后执行一下gogo
调度一下, 结束扩容.
抢占调度的原理
这部分其实是跟sysmon相结合的部分, sysmon决定应该给这家伙来个抢占调度了, 但是实际上真正发挥作用的还是stackguard0指针
我们知道在函数调用的时候会检查stackguard0与SP之间的关系, 如果sysmon决定了要抢占这个家伙, 那么会把它的stackguard0设置成stackpreempt, 到了下次函数调用做内存检查的时候一定是不通过的, 进而进入newstack()
函数
然而newstack()函数也不傻, 它发现虽然内存检测没过, 但是stackguard被设置在了stackpreempt位置, 它就发现了你其实只是想搞抢占调度而已, 是不会给你扩容的. 它会把你从M上摘下来放到全局队列里去, 最后执行一下schedule()调度, 让M重新找个任务来做, 结束抢占调度
Last updated
Was this helpful?