- 对这个话题已经有深入理解的童鞋请绕道;
- 对这个话题感兴趣,且有极强学习能力的同学请阅读这里,并且不用回来了。
- 其他和我一样愚笨的IT民工们,继续向前冲吧……
- 【2013年3月21日】以下关于 stackless 的描述有致命的脑残错误,请忽略。感谢 @minux 指出。
首先,来看一段神奇的 golang 代码:
package main var ( i = 1 ) func main() { i = i + 1 print(i, "\n") main() }
熟悉 c 语言的人都知道,如果在 c 语言中编译执行类似的代码,程序最终会发生栈溢出(stack overflow),从而导致段错误(segmentation fault)。在 32 位环境下(我只有 32 位的实验环境)编译链接并执行这段代码,预期的“段错误”并没有出现。
程序长时间稳定的执行。同时,利用 top 命令,会看到程序所使用的内存不断上升,丝毫没有减少的痕迹。
祭出神器 gdb,来看一下这个 go 程序到底做了什么。
gdb stackless ... (gdb) b main.main ... (gdb) r ... (gdb) disassemble
当输入 disassemble 命令后,gdb 帮我们反编译了程序代码:
0x08048c00 <+0>: mov %gs:0x0,%ecx 0x08048c07 <+7>: mov -0x8(%ecx),%ecx 0x08048c0a <+10>: cmp (%ecx),%esp 0x08048c0c <+12>: ja 0x8048c17 <main.main+23> 0x08048c0e <+14>: xor %edx,%edx 0x08048c10 <+16>: xor %eax,%eax 0x08048c12 <+18>: call 0x8049515 <runtime.morestack> 0x08048c17 <+23>: sub $0x1c,%esp 0x08048c1a <+26>: mov 0x806c00c,%eax 0x08048c20 <+32>: inc %eax 0x08048c21 <+33>: mov %eax,0x806c00c 0x08048c27 <+39>: cltd 0x08048c28 <+40>: mov %eax,(%esp) 0x08048c2b <+43>: mov %edx,0x4(%esp) 0x08048c2f <+47>: call 0x804ef1b <runtime.printint> 0x08048c34 <+52>: lea 0x80557b0,%esi 0x08048c3a <+58>: lea (%esp),%edi 0x08048c3d <+61>: cld 0x08048c3e <+62>: movsl %ds:(%esi),%es:(%edi) 0x08048c3f <+63>: movsl %ds:(%esi),%es:(%edi) 0x08048c40 <+64>: call 0x804f099 <runtime.printstring> 0x08048c45 <+69>: call 0x8048c00 <main.main> 0x08048c4a <+74>: add $0x1c,%esp 0x08048c4d <+77>: ret
main.main+0到main.main+12处,我们看到取了两个值进行比较,当 (%ecx)大于%esp时,跳转到main.main+23。
main.main+23到main.main+64处,显然是我们程序的逻辑处理:变量i自加一、打印i、打印”\n”。
main.main+69再次调用地址 0x8048c00 的 main.main。
除了main.main+0到main.main+12的比较,main.main+14到main.main+18对 runtime.morestack 的调用外,这和一个普通的程序并无二至。
那么程序一开始比较两个数据和调用 runtime.morestack 是什么呢?
原来在程序一开始先比较了栈限制和已经使用的栈的大小(%esp)。当栈限制大于已经使用的栈的时候,说明空间充裕,则直接跳转到实际代码处执行(+23)。当栈限制小于等于时已经使用的栈时,执行 runtime.morestack(+18)申请更多的栈空间。
在 $(GOROOT)/src/pkg/runtime/stack.h 中,有略微详细的说明。
由此看来 golang 实现的并不是 stackless,而是在每次函数调用前判断栈空间是否足够,如果发现栈空间不够用,就立刻申请新的栈空间使用。当函数退出时,才释放这些栈空间。
还是在 gdb 中,执行:
(gdb) b runtime.morestack ... (gdb) r
在函数调用 100 多次后(猜测 32 位跟 64 位应当不同,没试),触发了 runtime.morestack 申请新的栈空间。
Leave a Reply