計算機是如何工作的?

    此篇文章出於完成作業的目的,同時也總結一下自己的學習的體會,鞏固一下學習成果。是完全真實的作業過程。如需轉載請保留以下信息:

    陳鐵 + 原創作品轉載請註明出處 + 《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000。


    今天計算機已經成爲我們生活中重要不可分離的重要組成部分,從隨身攜帶的手機到超級計算機,大部分都遵循馮諾伊曼體系結構:存儲程序、順序執行。程序編制好後,通過輸入設備提供給計算機順序執行。只要人可以將需要解決的問題描述爲計算機可以順序執行的指令序列,計算機就可以給出相應的結果。所以人們編制了計算機語言用來描述問題,現代計算機語言分爲低級語言和高級語言,低級語言更接近機器,高級語言更接近人類。爲了描述計算機的工作過程,我們採用接近機器的彙編語言(組合語言)描述計算機的執行過程。

    實驗環境的主機操作系統是Windows7 64位,運行VirtualBox 4.3.20 Edition,虛擬機安裝CentOS7.0 64bit,Linux kernel 3.10.0。gcc版本4.8.2,gdb版本GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-51.el7。

    以下是作業過程說明:

    1.C語言源代碼如下:

        #include <stdio.h>

        int g(int x) {
            return x+2;
        }
        int f(int x) {
            return g(x);
        }
        int main() {
            return f(7)+5;
        }

    2.執行命令進行gcc -S main.s main.c生成彙編代碼方便我們步進分析代碼的執行情況。執行gcc -g main.c -o main生成可用gdb調試的執行代碼。在linux終端下執行代碼情況如下:

    wKioL1T6j-nBaT5KAAEImRuKXiQ934.jpg

    3.我們通過分析代碼流程也可以得到正確答案:

    程序從main開始執行,調用了f函數,把參數7傳過去賦給x。f函數又調用了g函數,把x也就是7傳過去,g函數得到參數x的值爲7,返回7+2=9給f函數,f函數把9返回給main函數,main函數返回9+5=14作爲程序的執行結果。在Linux終端下,14保存在系統變量$?中。

    4.計算機系統我們可以抽象簡化爲CPU、內存、輸入輸出幾部分。下面我們看一下這個程序在我的環境下,計算機是如何機械的的出這個14的。存在函數的程序會大量進行內存的堆棧操作,簡單的加法運算在此不展開介紹,重點對於堆棧的操作進行跟蹤。以下是cat main.s 所列出的彙編代碼,僅保留可執行的部分。

g:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    %edi, -4(%rbp)
        movl    -4(%rbp), %eax
        addl    $2, %eax
        popq    %rbp
        ret
f:
        pushq   %rbp
        movq    %rsp, %rbp
        subq    $8, %rsp
        movl    %edi, -4(%rbp)
        movl    -4(%rbp), %eax
        movl    %eax, %edi
        call    g
        leave
        ret
main:
        pushq   %rbp
        movq    %rsp, %rbp
        movl    $7, %edi
        call    f
        addl    $5, %eax
        popq    %rbp
        ret


    (1)執行gdb main進入調試,l命令可以顯示出C語言代碼。break main設置斷點,使程序直行到程序開始處停下來,然後單步執行,看一下計算機到底是如何工作的。run命令使程序開始執行。pushq %rbp;movq %rsp, %rbp後,此時rip指向rip=0x40051a。

    wKiom1T6nhXiPrWIAAD7JKsRU-c586.jpg

     (2)執行 info registers命令查看一下寄存器的情況。堆棧指針rbp和rsp指向相同的地址0x7fffffffe550,表明當前程序堆棧爲空。    wKioL1T6n8HzxPK9AAKPTqpeevM709.jpg

這時彙編代碼保存了堆棧原來的指針,操作系統開始調用main函數。可以看到rip指向下一條要執行指令的地址。

    (3)有函數調用,我們在gdb中執行stepi命令。執行movl    $7, %edi。

(gdb) stepi
0x000000000040051f      10              return f(7)+5;

(gdb) print $rip

$1 = (void (*)()) 0x40051f <main+9>

(gdb) print $edi
$1 = 7

    (4)這時把程序中傳給函數f的7保存進了寄存器edi中,繼續執行,調用f函數。call f,執行的操作是當前rip=0x00400524值壓棧(在gdb中可以執行x %rsp命令查看),rsp-8,f函數所在地址放入rip中。計算機會執行f函數中的pushq $rbp;movq %rsp,%rbp;subq    $8, %rsp實際是保存調用f函數前main函數的指針。此時rbp=0x7fffffffe540,rsp=0x7fffffffe538;而(rbp)保存着調用前堆棧棧頂地址,當然棧頂移動8個字節用來接受傳人的參數。

wKioL1T6sJKBVs0QAABm80xKEpw225.jpgwKioL1T6w8Hxb6qSAABQ4dhCHlI438.jpg                              

    (5)執行3次stepi命令,movl %edi, -4(%rbp);movl -4(%rbp), %eax;movl %eax, %edi 這三行指令很明確,從main傳過來的參數存入堆棧空間,然後通過eax寄存器保存一下,在此放到edi中,準備傳給g函數。

wKioL1T6xMSRqXxEAAE0ROZPtco453.jpg

    (6)調用g函數時call g:rip值壓棧;rsp-8;g地址賦給rip。

    wKiom1T6xM_Af9tcAAGql9M-r-A704.jpg

    接下來執行兩條初始化指令pushq %rbp;movq %rsp, %rbp,保存後rbp、rsp變成了0x7fffffffe528。movl %edi, -4(%rbp);movl -4(%rbp), %eax;addl $2, %eax,接受傳入的參數,通過eax執行加法,此時結果保存在eax中。然後g函數執行恢復處理,popq  %rbp;ret,rsp指向0x7fffffffe538。

wKioL1T6t2ygiag8AAEwFi2z_00052.jpg

    (7)g函數返回時結果保存在eax中。回到f函數代碼繼續執行。其中leave指令相當於

        movq %rbp, %rsp

        popq %rbp

    程序執行後寄存器情況如下:

wKioL1T6yeCR8m37AABhvAF94sQ270.jpg

    ret指令執行後rsp=0x7fffffffe548

    (8)完成f函數調用後,回到main函數執行addl $5, %eax語句。結果保存在eax中。

wKioL1T6uLPwsTMwAAEXKM8g8Cw505.jpg

    (9)後面代碼是完成main函數返回,原理和一般函數調用相同,在此不在分析。

    

    總結:在我最初接觸計算機的時候,上機的機時還是很奢侈的東西,那時就給自己制定了學習方法:首先根據教材所教的知識,在頭腦中模擬計算機會如何執行,假定課本上的例子都是正確的,推導出機器應當給出什麼樣的結果,當有機會在計算機上操作時再進行驗證。現在看來,那時的思路是對的,但由於沒能堅持,今天的計算機水平還是一般。就本質而言,今天的計算機的確就是模擬人的操作過程,程序員如何設計的程序,計算機就會不折不扣的執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章