日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

Linux从程序到进程

發布時間:2023/11/27 生活经验 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux从程序到进程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者: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開辟空間)。TextGlobal 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

現在,我們可以更加深入地了解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

總結

以上是生活随笔為你收集整理的Linux从程序到进程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。