點我查看祕籍連載函數
進程其實都是在執行任務,而任務其實就是函數定義的(函數也稱爲方法、子程序等,本質都同樣),因此進程的做用就是不斷的執行函數。程序啓動時,第一個要執行的函數是main()函數(有些語言隱藏了這個函數,但任何程序必定會有一個程序入口函數),而後在main()函數中調用其它函數,每當調用其它函數時,都會先進行函數跳轉,轉而讓進程去執行被調用的函數,當被調函數執行完成後又回到調用函數的位置繼續向下運行。操作系統
程序執行的基本流程以下圖所示。右邊是程序的僞代碼,左邊是程序運行過程。首先進程跳轉到main函數處開始執行,而後執行一個賦值語句a=1,繼續往下發現是調用一個函數func1(),因而跳轉到func1(),同時還會保存好main中是從這個位置(假設稱爲位置1)處跳轉的,以便執行完func1()後能夠跳回到main()。而後開始執行func1()中的代碼,在CPU執行func1()執行的時候,main()函數就沒法繼續向下執行了,它必須等待func1()執行完成後的返回,當func1()執行完後根據跳回到位置1,因而main函數繼續向下執行,也就是賦值語句x=2,而後又以一樣的流程調用func2()函數並返回,最終main()函數執行完成,進程終止,程序退出。3d
每當進程調用一次函數,都會在用戶棧中爲該函數分配一個棧幀(stack frame),也稱爲調用棧(call stack),當該函數返回時又會釋放該棧幀。釋放的棧幀不會從虛擬內存中移除,它能夠被以後調用的函數從新使用,因此棧空間的大小是不會減少的。指針
根據這個特性並結合上圖所描述的程序執行過程,能夠推斷出一個重要的結論。因爲函數內部調用函數時,外部函數的棧幀不會釋放,只有內部函數所有退出了纔會繼續執行外部函數並在執行完成的時候釋放外部函數的棧幀,因此,遞歸函數(即函數內部調用函數自身)若是遞歸調用的層次太多(好比無限遞歸),會分配大量的棧幀,而且不會釋放,直到棧空間不足,沒法再分配新的棧幀,這時會報棧溢出(stack overflows)錯誤。因此,必需要合理編寫遞歸函數,使得遞歸函數可以在達到某些條件時返回,從而釋放棧幀,避免無限遞歸。blog
棧幀中保存了傳遞給該函數的參數、該函數中定義的局部變量、函數的返回值、調用該函數的程序計數器副本,以及一些其它重要信息。這裏有必要解釋下棧幀中的程序計數器副本。遞歸
什麼是程序計數器(Program Counter,PC)?這是CPU中的一個寄存器,在這個寄存器中保存了下一個要執行指令的指針。因此,CPU每執行一個指令的時候,就會設置這個寄存器使它指向下一個指令。進程
前面描述程序執行流程的時候說過,當main()函數調用func1()函數的時候,須要保存main()函數中調用func1()的位置,以便func1()返回時能夠跳轉回main()函數繼續向下執行。其實,main()函數在開始調用func1()函數的時候,PC寄存器就已經指向了這個指令,CPU能夠將這個指令的指針的值(也就是PC的副本)保存在func1()函數的棧幀中,這樣func1()執行完成後就能將這個指針從新放回到CPU的PC寄存器中,使得CPU從新回到main()函數調用func1()的位置處,從而調用者main能夠取得函數func1()棧幀中的返回值(這時候func1()的棧幀被釋放),並繼續執行下面的代碼。內存
操做系統還爲每一個進程維護另外一個棧:內核棧。這個棧的位置在內核的內存區域中,只有內核可以訪問,用戶進程沒法訪問。get
內核棧的做用是存放上下文切換時的進程信息。變量
當進程A要切換到進程B時,首先要陷入內核,而後內核將CPU中關於進程A的進程信息(即某些寄存器中的值)保存在進程A的內核棧中,而後從進程B的內核棧中恢復進程B的信息到CPU的某些寄存器中,再退出內核模式回到進程B,這樣CPU就開始執行進程B了。