点击上方蓝色“架构师优雅之道”关注,每天进步一点点
一、基础知识
func main() {
a := 8
println("a address : ", &a)
time.Sleep(time.Hour)
}
// 这里加“-m”来check没有内存逃逸
[root@n227-005-021 GoTest ]$ go build -gcflags "-N -l -m" stack3.go
[root@n227-005-021 GoTest ]$ ./stack3
a address : 0xc000070f68
[root@n227-005-021 fanlv ]$ pmap 162277
162277: ./stack3
0000000000400000 400K r-x-- stack3
0000000000464000 448K r---- stack3
00000000004d4000 20K rw--- stack3
00000000004d9000 200K rw--- [ anon ]
000000c000000000 65536K rw--- [ anon ]
00007f13d92f3000 36292K rw--- [ anon ]
00007f13db664000 263680K ----- [ anon ]
00007f13eb7e4000 4K rw--- [ anon ]
00007f13eb7e5000 293564K ----- [ anon ]
00007f13fd694000 4K rw--- [ anon ]
00007f13fd695000 36692K ----- [ anon ]
00007f13ffa6a000 4K rw--- [ anon ]
00007f13ffa6b000 4580K ----- [ anon ]
00007f13ffee4000 4K rw--- [ anon ]
00007f13ffee5000 508K ----- [ anon ]
00007f13fff64000 384K rw--- [ anon ]
00007ffd8fe4d000 132K rw--- [ stack ]
00007ffd8ff82000 12K r---- [ anon ]
00007ffd8ff85000 8K r-x-- [ anon ]
total 702472K
#include <stdio.h>
#include <stdlib.h>
int add(int x, int y)
{
return x + y;
}
int main()
{
int p = add(2, 6);
printf("add:%d\n", p);
return 0;
}
add.o:
(__TEXT,__text) section
_add:
0000000100003f20 pushq %rbp
0000000100003f21 movq %rsp, %rbp
0000000100003f24 movl %edi, -0x4(%rbp)
0000000100003f27 movl %esi, -0x8(%rbp)
0000000100003f2a movl -0x4(%rbp), %eax
0000000100003f2d addl -0x8(%rbp), %eax
0000000100003f30 popq %rbp
0000000100003f31 retq
0000000100003f32 nopw %cs:(%rax,%rax)
0000000100003f3c nopl (%rax)
_main:
0000000100003f40 pushq %rbp
0000000100003f41 movq %rsp, %rbp
0000000100003f44 subq $0x10, %rsp
0000000100003f48 movl $0x0, -0x4(%rbp)
0000000100003f4f movl $0x2, %edi
0000000100003f54 movl $0x6, %esi
0000000100003f59 callq _add
0000000100003f5e movl %eax, -0x8(%rbp)
0000000100003f61 movl -0x8(%rbp), %esi
0000000100003f64 leaq 0x37(%rip), %rdi ##literal pool for: "add:%d\n"
0000000100003f6b movb $0x0, %al
0000000100003f6d callq 0x100003f80 ##symbol stub for: _printf
0000000100003f72 xorl %ecx, %ecx
0000000100003f74 movl %eax, -0xc(%rbp)
0000000100003f77 movl %ecx, %eax
0000000100003f79 addq $0x10, %rsp
0000000100003f7d popq %rbp
0000000100003f7e retq
(lldb) s
Process 43672 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x0000000100003f20 add.o`add
add.o`add:
-> 0x100003f20 <+0>: pushq %rbp // 把rbp地址压栈
0x100003f21 <+1>: movq %rsp, %rbp // rbp = rsp
0x100003f24 <+4>: movl %edi, -0x4(%rbp) // $(rbp-4) = 2
0x100003f27 <+7>: movl %esi, -0x8(%rbp) // $(rbp-8) = 6
Target 0: (add.o) stopped.
(lldb) x/8xg $rbp
0x7ffeefbff580: 0x00007ffeefbff590 0x00007fff203fbf3d
0x7ffeefbff590: 0x0000000000000000 0x0000000000000
(lldb) s
Process 43672 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x0000000100003f2a add.o`add + 10
add.o`add:
-> 0x100003f2a <+10>: movl -0x4(%rbp), %eax
0x100003f2d <+13>: addl -0x8(%rbp), %eax
0x100003f30 <+16>: popq %rbp
0x100003f31 <+17>: retq
Target 0: (add.o) stopped.
(lldb) x/8xg $rbp
0x7ffeefbff560: 0x00007ffeefbff580 0x0000000100003f5e
0x7ffeefbff570: 0x00007ffeefbff590 0x0000000000011025
(lldb) x/8xw $rbp-8
0x7ffeefbff558: 0x00000006 0x00000002 0xefbff580 0x00007ffe
0x7ffeefbff568: 0x00003f5e 0x00000001 0xefbff590 0x00007ffe
func StudentRegister(name string, age int) *Student {
s := new(Student) //局部变量s逃逸到堆
s.Name = name
s.Age = age
return s
}
func Slice() {
s := make([]int, 1000, 1000) // s 会分配在堆上
for index, _ := range s {
s[index] = index
}
}
func main() {
s := "Escape"
fmt.Println(s)
}
func Fibonacci() func() int {
a, b := 0, 1
return func() int {
a, b = b, a+b
return a
}
}
二、golang 栈
// 2013-10-03 go1.2rc2
Significant runtime reductions:
amd64 386
GoParse -14% -1%
GobDecode -12% -20%
GobEncode -64% -1%
JSONDecode -9% -4%
JSONEncode -15% -5%
Template -17% -14%
Benchmark graphs at
http://swtch.com/~rsc/gostackamd64.html
http://swtch.com/~rsc/gostack386.html
// 2014-02-27 go1.3beta1
runtime: grow stack by copying
On stack overflow, if all frames on the stack are
copyable, we copy the frames to a new stack twice
as large as the old one. During GC, if a G is using
less than 1/4 of its stack, copy the stack to a stack
half its size.
// 2014-05-20 go1.3beta2
runtime: switch default stack size back to 8kB
The move from 4kB to 8kB in Go 1.2 was to eliminate many stack split hot spots.
The move back to 4kB was predicated on copying stacks eliminating
the potential for hot spots.
Unfortunately, the fact that stacks do not copy 100% of the time means
that hot spots can still happen under the right conditions, and the slowdown
is worse now than it was in Go 1.2. There is a real program in issue 8030 that
sees about a 30x slowdown: it has a reflect call near the top of the stack
which inhibits any stack copying on that segment.
Go back to 8kB until stack copying can be used 100% of the time.
Fixes issue 8030.
// 2014-09-17 go1.4beta1
runtime: change minimum stack size to 2K.
It will be 8K on windows because it needs 4K for the OS.
Similarly, plan9 will be 4K.
On linux/amd64, reduces size of 100,000 goroutines
from ~819MB to ~245MB.
Update issue 7514
// Called from runtime·newstackcall or from runtime·morestack when a new
// stack segment is needed. Allocate a new stack big enough for
// m->moreframesize bytes, copy m->moreargsize bytes to the new frame,
// and then act as though runtime·lessstack called the function at
// m->morepc.
void
runtime·newstack(void)
{
...........
// gp->status is usually Grunning, but it could be Gsyscall if a stack split
// happens during a function call inside entersyscall.
gp = m->curg;
oldstatus = gp->status;
framesize = m->moreframesize;
argsize = m->moreargsize;
gp->status = Gwaiting;
gp->waitreason = "stack split";
newstackcall = framesize==1;
if(newstackcall)
framesize = 0;
if(newstackcall && m->morebuf.sp - sizeof(Stktop) - argsize - 32 > gp->stackguard) {
.........
} else {
// 这里计算栈的空间大小
// allocate new segment.
framesize += argsize;
framesize += StackExtra; // room for more functions, Stktop.
if(framesize < StackMin)
framesize = StackMin; // 栈最小8K
framesize += StackSystem; // StackSystem Window-64 是4K,plan9 是9,其他平台是0
gp->stacksize += framesize;
if(gp->stacksize > runtime·maxstacksize) { // maxstacksize x64是 1G,x32是250m
runtime·printf("runtime: goroutine stack exceeds %D-byte limit\n", (uint64)runtime·maxstacksize);
runtime·throw("stack overflow");
}
stk = runtime·stackalloc(framesize);
top = (Stktop*)(stk+framesize-sizeof(*top));
free = framesize;
}
}
..............
void*
runtime·stackalloc(uint32 n)
{
uint32 pos;
void *v;
if(g != m->g0)
runtime·throw("stackalloc not on scheduler stack");
if(n == FixedStack || m->mallocing || m->gcing) {
if(n != FixedStack) {
runtime·printf("stackalloc: in malloc, size=%d want %d\n", FixedStack, n);
runtime·throw("stackalloc");
}
if(m->stackcachecnt == 0)
stackcacherefill();
pos = m->stackcachepos;
pos = (pos - 1) % StackCacheSize;
v = m->stackcache[pos];
m->stackcachepos = pos;
m->stackcachecnt--;
m->stackinuse++;
return v;
}
// https://github.com/golang/go/blob/go1.2/src/pkg/runtime/malloc.goc#L34
// 这里调用 runtime·mallocgc 去申请内存,指定内存不需要GC
return runtime·mallocgc(n, 0, FlagNoProfiling|FlagNoGC|FlagNoZero|FlagNoInvokeGC);
}
// cmd/internal/obj/s390x/objz.go
if p.Mark&LEAF != 0 && autosize < objabi.StackSmall {
// A leaf function with a small stack can be marked
// NOSPLIT, avoiding a stack check.
p.From.Sym.Set(obj.AttrNoSplit, true)
}
$ GOOS=linux GOARCH=amd64 go build -gcflags="-N -l" main.go
#command-line-arguments
main.add: nosplit stack overflow
744 assumed on entry to main.add (nosplit)
-79264 after main.add (nosplit) uses 80008
// https://github.com/golang/go/blob/go1.16.6/src/runtime/proc.go#L4065
func newproc1(fn *funcval, argp unsafe.Pointer, narg int32, callergp *g, callerpc uintptr) *g {
..............
newg := gfget(_p_) // 先去P的free list拿 没有的话就调用malg new一个g
if newg == nil {
newg = malg(_StackMin)
casgstatus(newg, _Gidle, _Gdead)
allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
}
}
//https://github.com/golang/go/blob/go1.16.6/src/runtime/proc.go#L3987
// Allocate a new g, with a stack big enough for stacksize bytes.
func malg(stacksize int32) *g {
newg := new(g)
if stacksize >= 0 {
// round2 是求2的指数,比如传 6 返回 8
// _StackSystem linux是0、plan9 是512 、 Windows-x64 是4k
stacksize = round2(_StackSystem + stacksize)
systemstack(func() {//切换到 G0 为 newg 初始化栈内存
newg.stack = stackalloc(uint32(stacksize))
})
// 设置 stackguard0 ,用来判断是否要进行栈扩容
newg.stackguard0 = newg.stack.lo + _StackGuard
newg.stackguard1 = ^uintptr(0)
// Clear the bottom word of the stack. We record g
// there on gsignal stack during VDSO on ARM and ARM64.
*(*uintptr)(unsafe.Pointer(newg.stack.lo)) = 0
}
return newg
}
// Number of orders that get caching. Order 0 is FixedStack
// and each successive order is twice as large.
// We want to cache 2KB, 4KB, 8KB, and 16KB stacks. Larger stacks
// will be allocated directly.
// Since FixedStack is different on different systems, we
// must vary NumStackOrders to keep the same maximum cached size.
// OS | FixedStack | NumStackOrders
// -----------------+------------+---------------
// linux/darwin/bsd | 2KB | 4
// windows/32 | 4KB | 3
// windows/64 | 8KB | 2
// plan9 | 4KB | 3
_NumStackOrders = 4 - sys.PtrSize/4*sys.GoosWindows - 1*sys.GoosPlan9
// 全局的栈缓存,分配 32KB以下内存
var stackpool [_NumStackOrders]struct {
item stackpoolItem
_ [cpu.CacheLinePadSize - unsafe.Sizeof(stackpoolItem{})%cpu.CacheLinePadSize]byte // CacheLine对齐,防止Flase Sharding的问题
}
//go:notinheap
type stackpoolItem struct {
mu mutex
span mSpanList
}
// 全局的栈缓存,分配 32KB 以上内存
// heapAddrBits 48 , pageShift 13
var stackLarge struct {
lock mutex
free [heapAddrBits - pageShift]mSpanList // free lists by log_2(s.npages)
}
func stackinit() {
if _StackCacheSize&_PageMask != 0 {
throw("cache size must be a multiple of page size")
}
for i := range stackpool {
stackpool[i].item.span.init()
lockInit(&stackpool[i].item.mu, lockRankStackpool)
}
for i := range stackLarge.free {
stackLarge.free[i].init()
lockInit(&stackLarge.lock, lockRankStackLarge)
}
}
// https://github.com/golang/go/blob/go1.16.6/src/runtime/stack.go#L327
// Per-P, per order stack segment cache size.
_StackCacheSize = 32 * 1024
// stackalloc allocates an n byte stack.
//
// stackalloc must run on the system stack because it uses per-P
// resources and must not split the stack.
//
//go:systemstack
func stackalloc(n uint32) stack {
// Stackalloc must be called on scheduler stack, so that we
// never try to grow the stack during the code that stackalloc runs.
// Doing so would cause a deadlock (issue 1547).
thisg := getg() // 必须是g0
if thisg != thisg.m.g0 {
throw("stackalloc not on scheduler stack")
}
if n&(n-1) != 0 {
throw("stack size not a power of 2")
}
if stackDebug >= 1 {
print("stackalloc ", n, "\n")
}
if debug.efence != 0 || stackFromSystem != 0 {
n = uint32(alignUp(uintptr(n), physPageSize))
v := sysAlloc(uintptr(n), &memstats.stacks_sys)
if v == nil {
throw("out of memory (stackalloc)")
}
return stack{uintptr(v), uintptr(v) + uintptr(n)}
}
// Small stacks are allocated with a fixed-size free-list allocator.
// If we need a stack of a bigger size, we fall back on allocating
// a dedicated span.
var v unsafe.Pointer
// _FixedStack: 2K、_NumStackOrders:4 、_StackCacheSize = 32 * 1024
if n < _FixedStack<<_NumStackOrders && n < _StackCacheSize {// 小于32K
order := uint8(0)
n2 := n
// 大于 2048 ,那么 for 循环 将 n2 除 2,直到 n 小于等于 2048
for n2 > _FixedStack {
order++
n2 >>= 1
}
var x gclinkptr
//preemptoff != "", 在 GC 的时候会进行设置,表示如果在 GC 那么从 stackpool 分配
// thisg.m.p = 0 会在系统调用和 改变 P 的个数的时候调用,如果发生,那么也从 stackpool 分配
if stackNoCache != 0 || thisg.m.p == 0 || thisg.m.preemptoff != "" {
// thisg.m.p == 0 can happen in the guts of exitsyscall
// or procresize. Just get a stack from the global pool.
// Also don't touch stackcache during gc
// as it's flushed concurrently.
lock(&stackpool[order].item.mu)
x = stackpoolalloc(order)// 从 stackpool 分配
unlock(&stackpool[order].item.mu)
} else {
// 从 P 的 mcache 分配内存
c := thisg.m.p.ptr().mcache
x = c.stackcache[order].list
if x.ptr() == nil {
// 从堆上申请一片内存空间填充到stackcache中
stackcacherefill(c, order)
x = c.stackcache[order].list
}
c.stackcache[order].list = x.ptr().next // 移除链表的头节点
c.stackcache[order].size -= uintptr(n)
}
v = unsafe.Pointer(x) // 获取到分配的span内存块
} else {
// 申请的内存空间过大,从 runtime.stackLarge 中检查是否有剩余的空间
var s *mspan
// 计算需要分配多少个 span 页, 8KB 为一页
npage := uintptr(n) >> _PageShift
log2npage := stacklog2(npage)
// Try to get a stack from the large stack cache.
lock(&stackLarge.lock)
// 如果 stackLarge 对应的链表不为空
if !stackLarge.free[log2npage].isEmpty() {
//获取链表的头节点,并将其从链表中移除
s = stackLarge.free[log2npage].first
stackLarge.free[log2npage].remove(s)
}
unlock(&stackLarge.lock)
lockWithRankMayAcquire(&mheap_.lock, lockRankMheap)
//这里是stackLarge为空的情况
if s == nil {
// 从堆上申请新的内存 span
// Allocate a new stack from the heap.
s = mheap_.allocManual(npage, spanAllocStack)
if s == nil {
throw("out of memory")
}
// OpenBSD 6.4+ 系统需要做额外处理
osStackAlloc(s)
s.elemsize = uintptr(n)
}
v = unsafe.Pointer(s.base())
}
..........
return stack{uintptr(v), uintptr(v) + uintptr(n)}
}
func stackpoolalloc(order uint8) gclinkptr {
list := &stackpool[order].item.span
s := list.first
lockWithRankMayAcquire(&mheap_.lock, lockRankMheap)
if s == nil {
// no free stacks. Allocate another span worth.
// 从堆上分配 mspan
// _StackCacheSize = 32 * 1024
s = mheap_.allocManual(_StackCacheSize>>_PageShift, &memstats.stacks_inuse)
if s == nil {
throw("out of memory")
}
// 刚分配的 span 里面分配对象个数肯定为 0
if s.allocCount != 0 {
throw("bad allocCount")
}
if s.manualFreeList.ptr() != nil {
throw("bad manualFreeList")
}
//OpenBSD 6.4+ 系统需要做额外处理
osStackAlloc(s)
// Linux 中 _FixedStack = 2048
s.elemsize = _FixedStack << order
//_StackCacheSize = 32 * 1024
// 这里是将 32KB 大小的内存块分成了elemsize大小块,用单向链表进行连接
// 最后 s.manualFreeList 指向的是这块内存的尾部
for i := uintptr(0); i < _StackCacheSize; i += s.elemsize {
x := gclinkptr(s.base() + i)
x.ptr().next = s.manualFreeList
s.manualFreeList = x
}
// 插入到 list 链表头部
list.insert(s)
}
x := s.manualFreeList
// 代表被分配完毕
if x.ptr() == nil {
throw("span has no free stacks")
}
// 将 manualFreeList 往后移动一个单位
s.manualFreeList = x.ptr().next
// 统计被分配的内存块
s.allocCount++
// 因为分配的时候第一个内存块是 nil
// 所以当指针为nil 的时候代表被分配完毕
// 那么需要将该对象从 list 的头节点移除
if s.manualFreeList.ptr() == nil {
// all stacks in s are allocated.
list.remove(s)
}
return x
}
func stackcacherefill(c *mcache, order uint8) {
var list gclinkptr
var size uintptr
lock(&stackpool[order].item.mu)
//_StackCacheSize = 32 * 1024
// 将 stackpool 分配的内存组成一个单向链表 list
for size < _StackCacheSize/2 {
x := stackpoolalloc(order)
x.ptr().next = list
list = x
// _FixedStack = 2048
size += _FixedStack << order
}
unlock(&stackpool[order].item.mu)
c.stackcache[order].list = list
c.stackcache[order].size = size
}
type stack struct {
lo uintptr
hi uintptr
}
TEXT runtime·morestack(SB),NOSPLIT,$0-0
// Cannot grow scheduler stack (m->g0).
// 无法增长调度器的栈(m->g0)
get_tls(CX)
MOVQ g(CX), BX
MOVQ g_m(BX), BX
MOVQ m_g0(BX), SI
CMPQ g(CX), SI
JNE 3(PC)
CALL runtime·badmorestackg0(SB)
CALL runtime·abort(SB)
// 省略signal stack、morebuf和sched的处理
...
// Call newstack on m->g0's stack.
// 在 m->g0 栈上调用 newstack.
MOVQ m_g0(BX), BX
MOVQ BX, g(CX)
MOVQ (g_sched+gobuf_sp)(BX), SP
CALL runtime·newstack(SB)
CALL runtime·abort(SB) // 如果 newstack 返回则崩溃 crash if newstack returns
RET
func newstack() {
thisg := getg()
gp := thisg.m.curg
// 初始化寄存器相关变量
morebuf := thisg.m.morebuf
thisg.m.morebuf.pc = 0
thisg.m.morebuf.lr = 0
thisg.m.morebuf.sp = 0
thisg.m.morebuf.g = 0
...
// 校验是否被抢占
preempt := atomic.Loaduintptr(&gp.stackguard0) == stackPreempt
// 如果被抢占
if preempt {
// 校验是否可以安全的被抢占
// 如果 M 上有锁
// 如果正在进行内存分配
// 如果明确禁止抢占
// 如果 P 的状态不是 running
// 那么就不执行抢占了
if !canPreemptM(thisg.m) {
// 到这里表示不能被抢占?
// Let the goroutine keep running for now.
// gp->preempt is set, so it will be preempted next time.
gp.stackguard0 = gp.stack.lo + _StackGuard
// 触发调度器的调度
gogo(&gp.sched) // never return
}
}
if gp.stack.lo == 0 {
throw("missing stack in newstack")
}
// 寄存器 sp
sp := gp.sched.sp
if sys.ArchFamily == sys.AMD64 || sys.ArchFamily == sys.I386 || sys.ArchFamily == sys.WASM {
// The call to morestack cost a word.
sp -= sys.PtrSize
}
...
if preempt {
//需要收缩栈
if gp.preemptShrink {
gp.preemptShrink = false
shrinkstack(gp)
}
// 被 runtime.suspendG 函数挂起
if gp.preemptStop {
// 被动让出当前处理器的控制权
preemptPark(gp) // never returns
}
//主动让出当前处理器的控制权
gopreempt_m(gp) // never return
}
// 计算新的栈空间是原来的两倍
oldsize := gp.stack.hi - gp.stack.lo
newsize := oldsize * 2
...
//将 Goroutine 切换至 _Gcopystack 状态
casgstatus(gp, _Grunning, _Gcopystack)
//开始栈拷贝
copystack(gp, newsize)
casgstatus(gp, _Gcopystack, _Grunning)
gogo(&gp.sched)
}
func copystack(gp *g, newsize uintptr) {
old := gp.stack
// 当前已使用的栈空间大小
used := old.hi - gp.sched.sp
//分配新的栈空间
new := stackalloc(uint32(newsize))
...
// 计算调整的幅度
var adjinfo adjustinfo
adjinfo.old = old
// 新栈和旧栈的幅度来控制指针的移动
adjinfo.delta = new.hi - old.hi
// 调整 sudogs, 必要时与 channel 操作同步
ncopy := used
if !gp.activeStackChans {
...
adjustsudogs(gp, &adjinfo)
} else {
// 到这里代表有被阻塞的 G 在当前 G 的channel 中,所以要防止并发操作,需要获取 channel 的锁
// 在所有 sudog 中找到地址最大的指针
adjinfo.sghi = findsghi(gp, old)
// 对所有 sudog 关联的 channel 上锁,然后调整指针,并且复制 sudog 指向的部分旧栈的数据到新的栈上
ncopy -= syncadjustsudogs(gp, used, &adjinfo)
}
// 将源栈中的整片内存拷贝到新的栈中
memmove(unsafe.Pointer(new.hi-ncopy), unsafe.Pointer(old.hi-ncopy), ncopy)
// 继续调整栈中 txt、defer、panic 位置的指针
adjustctxt(gp, &adjinfo)
adjustdefers(gp, &adjinfo)
adjustpanics(gp, &adjinfo)
if adjinfo.sghi != 0 {
adjinfo.sghi += adjinfo.delta
}
// 将 G 上的栈引用切换成新栈
gp.stack = new
gp.stackguard0 = new.lo + _StackGuard // NOTE: might clobber a preempt request
gp.sched.sp = new.hi - used
gp.stktopsp += adjinfo.delta
// 在新栈重调整指针
gentraceback(^uintptr(0), ^uintptr(0), 0, gp, 0, nil, 0x7fffffff, adjustframe, noescape(unsafe.Pointer(&adjinfo)), 0)
if stackPoisonCopy != 0 {
fillstack(old, 0xfc)
}
//释放原始栈的内存空间
stackfree(old)
}
func scanstack(gp *g, gcw *gcWork) {
...
// 进行栈收缩
shrinkstack(gp)
...
}
func shrinkstack(gp *g) {
...
oldsize := gp.stack.hi - gp.stack.lo
newsize := oldsize / 2
// 当收缩后的大小小于最小的栈的大小时,不再进行收缩
if newsize < _FixedStack {
return
}
avail := gp.stack.hi - gp.stack.lo
// 计算当前正在使用的栈数量,如果 gp 使用的当前栈少于四分之一,则对栈进行收缩
// 当前使用的栈包括到 SP 的所有内容以及栈保护空间,以确保有 nosplit 功能的空间
if used := gp.stack.hi - gp.sched.sp + _StackLimit; used >= avail/4 {
return
}
// 将旧栈拷贝到新收缩后的栈上
copystack(gp, newsize)
}
func f(i int) int {
if i == 0 || i == 1 {
return i
}
return f(i - 1)
}
func main() {
println(f(100000000))
}
➜ GoTest git:(master) ✗ go run stack.go
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc0200e0390 stack=[0xc0200e0000, 0xc0400e0000]
fatal error: stack overflow
runtime stack:
runtime.throw(0x1074923, 0xe)
/Users/fanlv/.g/go/src/runtime/panic.go:1117 +0x72
runtime.newstack()
/Users/fanlv/.g/go/src/runtime/stack.go:1069 +0x7ed
runtime.morestack()
/Users/fanlv/.g/go/src/runtime/asm_amd64.s:458 +0x8f
if newsize > maxstacksize || newsize > maxstackceiling {
if maxstacksize < maxstackceiling {
print("runtime: goroutine stack exceeds ", maxstacksize, "-byte limit\n")
} else {
print("runtime: goroutine stack exceeds ", maxstackceiling, "-byte limit\n")
}
print("runtime: sp=", hex(sp), " stack=[", hex(gp.stack.lo), ", ", hex(gp.stack.hi), "]\n")
throw("stack overflow")
}
// Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.
// Using decimal instead of binary GB and MB because
// they look nicer in the stack overflow failure message.
if sys.PtrSize == 8 {
maxstacksize = 1000000000
} else {
maxstacksize = 250000000
}
func f(i int) int {
if i == 0 || i == 1 {
return i
}
return f(i - 1)
}
func main() {
println("demo3: ",demo3())
}
func demo1() {
var xDemo1 uint64
xAddr := uintptr(unsafe.Pointer(&xDemo1))
println("demo1 before stack copy xDemo1 : ", xDemo1, " xDemo1 pointer: ", &xDemo1)
f(10000000)
xPointer := (*uint64)(unsafe.Pointer(xAddr))
atomic.AddUint64(xPointer, 1)
println("demo1 after stack copy xDemo1 : ", xDemo1, " xDemo1 pointer:", &xDemo1)
}
➜ GoTest git:(master) ✗ go build -gcflags "-N -l -m" stack.go
➜ GoTest git:(master) ✗ ./stack
demo1 before stack copy xDemo1 : 0 xDemo1 pointer: 0xc000044740
demo1 after stack copy xDemo1 : 0 xDemo1 pointer: 0xc04015ff40
// f 和 main 函数代码如demo1
func demo2() {
var xDemo2 uint64
println("demo2 before stack copy xDemo2 : ", xDemo2, " xDemo2 pointer: ", &xDemo2)
f(10000000)
atomic.AddUint64(&xDemo2, 1)
println("demo2 after stack copy xDemo2 : ", xDemo2, " xDemo2 pointer:", &xDemo2)
}
➜ GoTest git:(master) ✗ go build -gcflags "-N -l -m" stack.go
#command-line-arguments
./stack.go:36:6: moved to heap: xDemo2
➜ GoTest git:(master) ✗ ./stack
demo2 before stack copy xDemo2 : 0 xDemo2 pointer: 0xc0000180b8
demo2 after stack copy xDemo2 : 1 xDemo2 pointer: 0xc0000180b8
func demo3() *uint64 {
var xDemo3 uint64 = 8
println("demo3 before stack copy xDemo3 : ", xDemo3, " xDemo3 pointer: ", &xDemo3)
f(1000)
println("demo3 after stack copy xDemo3 : ", xDemo3, " xDemo3 pointer:", &xDemo3)
return &xDemo3
}
➜ GoTest git:(master) ✗ go run stack.go
demo3 before stack copy xDemo3 : 0 xDemo3 pointer: 0xc000044770
demo3 after stack copy xDemo3 : 0 xDemo3 pointer: 0xc000117f70
➜ GoTest git:(master) ✗ go build -gcflags "-m" stack.go
#command-line-arguments
./stack.go:37:6: can inline demo3
./stack.go:14:7: inlining call to demo3
./stack.go:38:6: moved to heap: xDemo3
_main.main:000000000105c920 movq %gs:0x30, %rcx000000000105c929 cmpq 0x10(%rcx), %rsp000000000105c92d jbe 0x105ca21000000000105c933 subq $0x20, %rsp000000000105c937 movq %rbp, 0x18(%rsp)000000000105c93c leaq 0x18(%rsp), %rbp000000000105c941 movq $0x8, 0x10(%rsp)000000000105c94a callq _runtime.printlock000000000105c94f leaq 0x18e96(%rip), %rax000000000105c956 movq %rax, (%rsp)000000000105c95a movq $0x22, 0x8(%rsp)000000000105c963 callq _runtime.printstring000000000105c968 movq 0x10(%rsp), %rax000000000105c96d movq %rax, (%rsp)000000000105c971 callq _runtime.printuint000000000105c976 leaq 0x16ccd(%rip), %rax000000000105c97d movq %rax, (%rsp)000000000105c981 movq $0x13, 0x8(%rsp)000000000105c98a callq _runtime.printstring000000000105c98f leaq 0x10(%rsp), %rax000000000105c994 movq %rax, (%rsp)000000000105c998 callq _runtime.printpointer000000000105c99d nopl (%rax)000000000105c9a0 callq _runtime.printnl000000000105c9a5 callq _runtime.printunlock000000000105c9aa movq $0x3e8, (%rsp) 000000000105c9b2 callq _main.f000000000105c9b7 callq _runtime.printlock000000000105c9bc leaq 0x18bf7(%rip), %rax000000000105c9c3 movq %rax, (%rsp)000000000105c9c7 movq $0x21, 0x8(%rsp)000000000105c9d0 callq _runtime.printstring000000000105c9d5 movq 0x10(%rsp), %rax000000000105c9da movq %rax, (%rsp)000000000105c9de nop000000000105c9e0 callq _runtime.printuint000000000105c9e5 leaq 0x16b62(%rip), %rax000000000105c9ec movq %rax, (%rsp)000000000105c9f0 movq $0x12, 0x8(%rsp)000000000105c9f9 callq _runtime.printstring000000000105c9fe leaq 0x10(%rsp), %rax000000000105ca03 movq %rax, (%rsp)000000000105ca07 callq _runtime.printpointer000000000105ca0c callq _runtime.printnl000000000105ca11 callq _runtime.printunlock000000000105ca16 movq 0x18(%rsp), %rbp000000000105ca1b addq $0x20, %rsp000000000105ca1f nop000000000105ca20 retq000000000105ca21 callq _runtime.morestack_noctxt000000000105ca26 jmp _main.main
三、参考
原文链接:https://www.jianshu.com/p/6cb9eeb41eb9
如果看到这里,说明你喜欢这篇文章, 标星(置顶)本公众号可以第一时间接受到博文推送。
“分享、点赞、在看” 支持一波