Linux從程式到進程

來源:互聯網
上載者:User

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

 

電腦如何執行進程呢?這可以說是電腦啟動並執行核心問題。即使我們已經編寫好程式,但程式是死的文本,只有成為活的進程才能帶來產出。我們已經從Linux進程基礎中瞭解了進程的一些概況。現在我們看一下從程式到進程的漫漫征程。

 

1. 一段程式

我們下面展示一個簡單的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變數。

我們並不在意這個程式的具體功能和結果。我們關心的是這個程式的運行過程。為該程式的運行過程,以及各個變數的作用範圍:

運行流程

2. 進程空間

為了進一步瞭解上面的程式的運行,我們還需要知道進程是如何使用記憶體的。當程式檔案運行為進程的時候,進程在記憶體中得到空間(進程自己的小房間)。每個進程空間按照如下方式分為不同地區:

記憶體空間

Text地區用來儲存指令(instruction),來告訴程式每一步的操作。Global Data用於存放全域變數,stack用於存放局部變數,heap用於存放動態變數 (dynamic variable. 程式利用malloc系統調用,直接從記憶體中為dynamic variable開闢空間)。Text和Global data在進程一開始的時候就確定了,並在整個進程中保持固定大小。

 

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

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

stack變化

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

 

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

由於stack和heap的大小則會隨著進程的運行增大或者變小,當stack和heap增長到兩者相遇時候,也就是記憶體空間圖中的藍色地區(unused area)完全消失的時候,進程會出現棧溢出(stack overflow)的錯誤,導致進程終止。在現代電腦中,核心一般都會為進程分配足夠多的藍色地區,如果我們即時清理的話,stack overflow是可以避免的。但是,在進行一些矩陣運算的時候,由於所需的記憶體很大,依然可能出現stack overflow的情況。一種解決方式是增大核心分配給每個進程的記憶體空間。如果依然不能解決問題的話,我們就需要增加實體記憶體。

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

 

在進階語言中,這些記憶體管理的細節對於使用者來說不透明。在編程的時候,我們只需要記住上一節中的變數範圍就可以了。但在想要寫出複雜的程式或者debug的時候,我們就需要相關的知識了。

 

3. 進程附加資訊

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

 

4. fork & exec

現在,我們可以更加深入地瞭解fork和exec(參考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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.