標籤:
首先,組裝過電腦的童鞋應該知道,電腦是由CPU,記憶體,硬碟,主板,電源組成的,當然,玩遊戲的童鞋還會念念不忘顯卡(比如GTX980戰術核顯卡)
只要有了這些東西,電腦就可以跑起來了。
然而這些東西又是如何協同工作而讓程式啟動並執行呢?
首先,我們得知道一個叫做作業系統的東西,本質上,它也是一個程式,抽象上,它可以被看作成一個環境。這個環境保證了各個程式可以正常工作,正常休息,正常學習(喂,這就不正常了吧!)
而我們程式員只要用標準的文法來告訴作業系統我們要電腦做什麼工作就可以了,比如,這裡有一段C語言代碼:
- int g(int x)
- {
- return x + 3;
- }
-
- int f(int x)
- {
- return g(x);
- }
-
- int main(void)
- {
- return f(8) + 1;
- }
這個程式在電腦上是如何工作的呢?我們對其進行反組譯碼,得到彙編代碼:
化簡之後:
g:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $3, %eax
popl %ebp
ret
f:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl 8(%ebp), %eax
movl %eax, (%esp)
call g
leave
ret
main:
pushl %ebp
movl %esp, %ebp
subl $4, %esp
movl $8, (%esp)
call f
addl $1, %eax
leave
ret
我們首先來看到Main函數進入點的兩條彙編代碼:
Pushl %ebp
Movl %esp,%ebp
Pushl指令是把ebp寄存器的值壓在棧上,把這個值給儲存起來,而movl指令則是把esp上的值放在了ebp,使ebp得到了一個新的值,這個新的值等於esp的值。
所以我們知道esp和ebp一開始都相等,也就是說以此就建立了一個程式的隔離儲存區 (Isolated Storage)堆棧(PS:我們可以把ebp看成堆棧的開始,esp看成堆棧的末尾,esp增長,則代表棧增長,反之則減少)
通俗一點來說,就是ebp和esp他們決定開一個新坑。。。
好了,開了新坑以後,就要填坑了,後面的C代碼調用了函數f(8),與之對應的是後面的三條彙編代碼:
Subl $4,%esp
Movl $8,(%esp)
Call f
首先,電腦把傳入的參數8壓到了棧上面,要完成這項操作,首先必須先擴充一下棧空間,就像前面提到的,使esp增長,怎麼增長呢,首先讓esp寄存器指向的地址減去4,再把8填到這個擴充的空間中。
之後,調用Call,也就是調用f函數,Call這句話相當於儲存現在的上下文然後改變eip指向的地址(要執行的程式碼片段的位置改變了)。
於是eip的當前地址被壓到了棧中,然後eip的地址就指向了函數f,函數f又做了和main函數開始部分相同的工作,通俗來說,它也開闢了一個獨立堆棧儲存空間(新坑),於是乎,函數f也坐起了浩浩蕩蕩的填坑工作。
在C語言中,我們看到f函數調用了g函數,與main函數調用f函數類似,首先電腦擴充了棧空間,然後把x壓入到推棧中,但是與上一個不同的是電腦並沒有直接把堆棧中的值壓到堆棧中,而是靠eax寄存器做了一次中轉:
movl 8(%ebp), %eax
movl %eax, (%esp)
雖然過程不同,但結果一樣,之後調用Call,eip再一次跳轉到g函數那裡,開新坑還有填坑:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %eax
addl $3, %eax
我們從第三條彙編代碼開始,也就是將f函數傳給g的參數x取出並放在了eax寄存器上,第四條addl語句執行了x+3
之後popl %ebp,也就是把棧頂的值彈到ebp上,我們知道ebp是棧的開始,ebp的值改變也就意味著跳轉到了一個新的棧上。
而這個新的棧正是函數f的隔離儲存區 (Isolated Storage)堆棧。
Ret語句相當於彈出棧頂的數值到eip上,而這個彈出的值正是函數f調用g函數之後的地址。
於是程式又回到了函數f中,並得到了傳回值(存在了eax寄存器中):
Leave
這條語句代表的含義是
movl %esp, %ebp
popl %ebp
返回到上一個隔離儲存區 (Isolated Storage)堆棧中。
Ret之後eip又指向main函數調用f函數的下一行代碼。也就是x+1,由於eax負責儲存返回後的函數的結果,所以有:
Addl $1,%eax
之後main函數返回,之後啊。。我編不下了。。。
電腦的工作,也可以說是作業系統的工作就是不斷地執行一個迴圈,這個迴圈儲存上一個迴圈的上下文,資料,然後為當前迴圈開闢一個上下文和儲存空間,讓當前的迴圈做事,如果當前迴圈又開始一個新的迴圈那麼就會重複這項工作,如果這項迴圈完成了,並沒有開啟新的迴圈,電腦就跳轉到上一個迴圈上去。如果用一個輪子比喻會比較貼切一些,這個輪子既可以往前轉也可以向後轉。
不過,我更喜歡電腦程式的構造與解釋所闡述的,程式員就像一個魔法師,他們寫出符文來變出各種各樣的魔法,而電腦就是一個可以實現無限夢想的魔杖。
blackerXHunter
原創作品轉載請註明出處
《Linux核心分析》MOOC課程,http://mooc.istudy.163.com/course/USTC-1000029000
Linux核心分析-1:電腦程式的一生