這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
- 對這個話題已經有深入理解的童鞋請繞道;
- 對這個話題感興趣,且有極強學習能力的同學請閱讀這裡,並且不用回來了。
- 其他和我一樣愚笨的IT民工們,繼續向前沖吧……
- 【2013年3月21日】以下關於 stackless 的描述有致命的腦殘錯誤,請忽略。感謝 @minux 指出。
首先,來看一段神奇的 golang 代碼:
package mainvar ( 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 申請新的棧空間。