標籤:
朱宇軻 + 原創作品轉載請註明出處 + 《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
大家都知道,現在的電腦主要遵循的是所謂的“馮諾依曼架構”。那什麼是馮諾依曼架構呢,其實就是電腦通過匯流排從記憶體中讀取一條條的程式和資料,將它們儲存在自己的寄存器中一條條地執行,如所示。
而今天,我們將通過彙編一個具體的C程式來探討電腦工作的流程。
首先寫下這麼一段C程式:
1 //linux.c 2 int g(x) 3 { 4 return x+3; 5 } 6 int f(x) 7 { 8 return g(x); 9 }10 int main()11 {12 return f(10)+1;13 }
在Linux的環境中輸入如下指令:
gcc –S –o linux.s linux.c -m32
然後開啟linux.s,就可以看到我們彙編後的代碼(直接上了)
將裡面以“.”開頭的行去掉(這是為連結用的),得到彙編後的代碼:
1 g: 2 pushl %ebp 3 movl %esp, %ebp 4 movl 8(%ebp), %eax 5 addl $3, %eax 6 popl %ebp 7 ret 8 f: 9 pushl %ebp10 movl %esp, %ebp11 subl $4, %esp12 movl 8(%ebp), %eax13 movl %eax, (%esp)14 call g15 leave16 ret17 main:18 pushl %ebp19 movl %esp, %ebp20 subl $4, %esp21 movl $10, (%esp)22 call f23 addl $1, %eax24 leave25 ret
接下來我們來分析一下改程式具體的流程。
程式一開始,CPU的IP寄存器指向彙編代碼的第18行,假設堆棧在記憶體中的地址分別為0,1,2,3……堆棧基指標寄存器(EBP)和堆棧頂指標寄存器(ESP)均指向堆棧段0處。
第18~21行首先為main函數開闢新的記憶體地區,之後將傳的參數10入棧,此時堆棧段如下所示:
然後程式調用call 函數,將IP入棧,IP指向代碼第9行f處。
在f函數的代碼處,首先為f函數開闢新的記憶體地區,接著將傳入的參數10賦值給EAX,並將EAX入棧,此時堆棧段記憶體如:
程式在此調用call進入g函數。在g函數中,同樣先是開闢記憶體空間,然後將參數傳給EAX,並將EAX的值加上3。
之後將EBP出棧,並調用ret命令。此時IP重新指向f函數call之後的命令,堆棧記憶體的情況如下:
之後就是不斷的調用leave與ret命令,跳出當前的記憶體地區,回到上一級函數的記憶體地區中,並將EAX的值加3,直到跳出main函數,至此程式結束。
從上面的分析中,我覺得可以歸納出以下幾點:
1.電腦的運行流程確是遵循馮諾依曼架構,CPU將記憶體中的代碼和資料讀取到自己的寄存器中,再根據一條條命令調用寄存器進行進一步的操作。
2.在進入每一個程式之前,CPU都會將上一級的EIP和EBP壓棧,相當於為新的函數重新開闢了一段新的記憶體空間,直到退出函數的時候才將它們出棧。
3.CPU的各個寄存器都有不同的分工,如EIP指向要執行的代碼,EAX儲存傳回值等。它們貫穿於整個程式執行流程,自己寫程式時一般不要輕易改動。
從一段代碼的彙編看電腦的工作原理