Linux從程序到進程


作者:Vamei 出處:http://www.cnblogs.com/vamei 歡迎轉載,也請保留這段聲明。謝謝!

 

計算機如何執行進程呢?這是計算機運行的核心問題。即使已經編寫好程序,但程序是死的。只有活的進程才能產出。我們已經從Linux進程基礎中了解了進程。現在我們看一下從程序到進程的漫漫征程。

 

一段程序

下面是一個簡單的C程序,假設該程序已經編譯好,生成可執行文件vamei.exe。

#include <stdio.h>

int glob=0;                                             /*global variable*/

void main(void) { int main1=5;                                          /*local variable of main()*/
  int main2;                                            /*local variable of main()*/ main2 = inner(main1);                                 /* call inner() function */ printf("From Main: glob: %d \n", glob); printf("From Main: main2: %d \n", main2); } int inner(int inner1) {                                 /*inner1 is an argument, also local to inner()*/
  int inner2=10;                                        /*local variable of inner()*/ printf("From inner: glob: %d \n", glob); return(inner1+inner2); }

(選取哪一個語言或者具體的語法並不是關鍵,大部分語言都可以寫出類似上面的程序。在看Python教程的讀者也可以利用Python的函數結構和print寫一個類似的python程序。當然,還可以是C++,Java,Objective-C等等。選用C語言的原因是:它是為UNIX而生的語言。)

 

main()函數中調用了inner()函數。inner()中調用一次printf()以輸出。最后,在main()中進行了兩次printf()。

注意變量的作用范圍。簡單地說,變量可以分為全局變量局部變量。在所有函數之外聲明的變量為全局變量,比如glob,在任何時候都可以使用。在函數內定義的變量為局部變量,只能在該函數的作用域(range)內使用,比如說我們在inner()工作的時候不能使用main()函數中聲明的main1變量,而在main()中我們無法使用inner()函數中聲明的inner2變量。

 

不用太過在意這個程序的具體功能。要點是這個程序的運行過程。下圖為該程序的運行過程,以及各個變量的作用范圍:

運行流程

進程空間

為了進一步了解上面程序的運行,我們還需要知道,進程如何使用內存。當程序文件運行為進程時,進程在內存中獲得空間。這個空間是進程自己的小屋子。

每個進程空間按照如下方式分為不同區域:

內存空間

Text區域用來儲存指令(instruction),說明每一步的操作。Global Data用於存放全局變量,棧(Stack)用於存放局部變量,堆(heap)用於存放動態變量 (dynamic variable. 程序利用malloc系統調用,直接從內存中為dynamic variable開辟空間)。TextGlobal data在進程一開始的時候就確定了,並在整個進程中保持固定大小

 

棧(Stack)(stack frame)為單位。當程序調用函數的時候,比如main()函數中調用inner()函數,stack會向下增長一幀。幀中存儲該函數的參數局部變量,以及該函數的返回地址(return address)。此時,計算機將控制權從main()轉移到inner(),inner()函數處於激活(active)狀態。位於棧最下方的幀,和全局變量一起,構成了當前的環境(context)。激活函數可以從環境中調用需要的變量。典型的編程語言都只允許你使用位於stack最下方的幀 ,而不允許你調用其它的幀 (這也符合stack結構“先進后出”的特征。但也有一些語言允許你調用棧的其它部分,相當於允許你在運行inner()函數的時候調用main()中聲明的局部變量,比如Pascal)。當函數又進一步調用另一個函數的時候,一個新的幀會繼續增加到棧的下方,控制權轉移到新的函數中。當激活函數返回的時候,會從棧中彈出(pop,讀取並從棧中刪除)該幀,並根據幀中記錄的返回地址,將控制權交給返回地址所指向的指令(比如從inner()函數中返回,繼續執行main()中賦值給main2的操作)。

下圖是棧在運行過程中的變化。箭頭表示棧的增長方向。每個方塊代表一幀。開始的時候我們有一個為main()服務的幀,隨着調用inner(),我們為inner()增加一個幀。在inner()返回時,我們再次只有main()的幀,直到最后main()返回,其返回地址為空,所以進程結束。

stack變化

在進程運行的過程中,通過調用和返回函數,控制權不斷在函數間轉移。進程可以在調用函數的時候,原函數的幀中保存有在我們離開時的狀態,並為新的函數開辟所需的幀空間。在調用函數返回時,該函數的幀所占據的空間隨着幀的彈出而清空。進程再次回到原函數的幀中保存的狀態,並根據返回地址所指向的指令繼續執行。上面過程不斷繼續,棧不斷增長或減小,直到main()返回的時候,棧完全清空,進程結束。

 

當程序中使用malloc的時候,堆(heap)向上增長,其增長的部分就成為malloc從內存中分配的空間。malloc開辟的空間會一直存在,直到我們用free系統調用來釋放,或者進程結束。一個經典的錯誤是內存泄漏(memory leakage), 就是指我們沒有釋放不再使用的堆空間,導致堆不斷增長,而內存可用空間不斷減少。

棧和堆的大小則會隨着進程的運行增大或者變小。當棧和堆增長到兩者相遇時候,也就是內存空間圖中的藍色區域(unused area)完全消失的時候,再無可用內存。進程會出現棧溢出(stack overflow)的錯誤,導致進程終止。在現代計算機中,內核一般會為進程分配足夠多的藍色區域,如果清理及時,棧溢出很容易避免。即便如此,內存負荷過大,依然可能出現棧溢出的情況。我們就需要增加物理內存了。

Stack overflow可以說是最出名的計算機錯誤了,所以才有IT網站(stackoverflow.com)以此為名。

 

在高級語言中,這些內存管理的細節對於用戶來說不透明。在編程的時候,我們只需要記住上一節中的變量作用域就可以了。但在想要寫出復雜的程序或者debug的時候,我們就需要相關的知識了。

 

進程附加信息

除了上面的信息之外,每個進程還要包括一些進程附加信息,包括PID,PPID,PGID(參考Linux進程基礎以及Linux進程關系)等,用來說明進程的身份、進程關系以及其它統計信息。這些信息並不保存在進程的內存空間中。內核會為每個進程在內核自己的空間中分配一個變量(task_struct結構體)以保存上述信息。內核可以通過查看自己空間中的各個進程的附加信息就能知道進程的概況,而不用進入到進程自身的空間 (就好像我們可以通過門牌就可以知道房間的主人是誰一樣,而不用打開房門)。每個進程的附加信息中有位置專門用於保存接收到的信號(正如我們在Linux信號基礎中所說的“信箱”)。

 

fork & exec

現在,我們可以更加深入地了解forkexec(參考Linux進程基礎)的機制了。當一個程序調用fork的時候,實際上就是將上面的內存空間,包括text, global data, heap和stack,又復制出來一個,構成一個新的進程,並在內核中為改進程創建新的附加信息 (比如新的PID,而PPID為原進程的PID)。此后,兩個進程分別地繼續運行下去。新的進程和原有進程有相同的運行狀態(相同的變量值,相同的instructions...)。我們只能通過進程的附加信息來區分兩者。

程序調用exec的時候,進程清空自身內存空間的text, global data, heap和stack,並根據新的程序文件重建text, global data, heap和stack (此時heap和stack大小都為0),並開始運行。

(現代操作系統為了更有效率,改進了管理fork和exec的具體機制,但從邏輯上來說並沒有差別。具體機制請參看Linux內核相關書籍)

 

這一篇寫了整合了許多東西,所以有些長。這篇文章主要是概念性的,許多細節會根據語言和平台乃至於編譯器的不同而有所變化,但大體上,以上的概念適用於所有的計算機進程(無論是Windows還是UNIX)。更加深入的內容,包括線程(thread)、進程間通信(IPC)等,都依賴於這里介紹的內容。

 

總結

函數,變量的作用范圍,global/local/dynamic variables

global data, text,

stack, stack frame, return address, stack overflow

heap, malloc, free, memory leakage

進程附加信息, task_struct

fork & exec

 

歡迎閱讀“騎着企鵝采樹莓”系列文章


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM