歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Linux進程的理解

通俗的講,進程就是正在執行的程序或代碼。我們知道,程序本身就是一堆代碼,開始的時候存儲在磁盤上,這時它是靜態的、無生命的;只有當程序的代碼被加載到內存中,代碼才有了生命,才能被CPU動態的執行。

問題是,現在的操作系統可以並行的執行多個程序,也就是內存中同時存放著多個程序的代碼,為了方便管理,必須要合理的組織它們。方式就是由操作系統給每段代碼添加一些元數據,這些元數據就是PCB,即任務控制塊。

不難理解的是,每個程序的代碼實際上可以分為兩部分:指令的數據。指令就是程序代碼規定的各種操作;數據就是這些操作的對象。一個程序可以多次被加載到內存成為多個進程,比如同時打開兩個vim分別編輯不同的文件。那麼問題是:這時候有必要在內存中同時存放多個該程序的指令copy嗎?答案是不必的。指令部分被設置為只讀且允許系統中正在運行的兩個或多個進程之間能夠共享這一代碼;而數據部分則被各個進程私有,不可以共享,比如每個vim都只能編輯自己的文件。

那麼PCB裡面都有些什麼呢?

進程id。系統中每個進程有唯一的id,在C語言中用 pid_t 類型表示,其實就是一個非負整數。 進程的狀態,有運行、掛起、停止、僵屍等狀態。 進程切換時需要保存和恢復的一些CPU寄存器。 描述虛擬地址空間的信息。 描述控制終端的信息。 當前工作目錄(Current Working Directory)。 umask 掩碼。 文件描述符表,包含很多指向 file 結構體的指針。 和信號相關的信息。 用戶id和組id。 控制終端、Session和進程組。 進程可以使用的資源上限(Resource Limit)。

可見,操作系統為了控制進程,PCB的結構還是挺復雜的。 進程的創建

經常聽說“創建一個進程”,這到底是怎麼回事呢?首先能想到的是,進程不是孫猴子,不可能自己蹦出來,肯定是別人“生”的。Linux中,進程是由父進程創建的,准確的說,是父進程中的代碼的指令部分主動使用了創建進程的函數fork(),然後一個子進程就被“生”了出來。fork函數如何工作的呢?由於每個進程都有一個PCB,所以它首先要跟操作系統申請一個PCB(PCB是有限的),然後分配新進程內存,接著copy父進程的代碼。實際上,fork就是很懶的復制了一下父進程,也就是說,fork函數調用過程中,內存中會會出現兩個幾乎一模一樣的進程,當然除了進程號(它是唯一的)。進程的復制完成後,兩個進程都有一個fork函數等待返回(注意,是返回,因為fork函數本身也是一條一條的代碼,前面的一部分完成復制功能,子進程出現後,就到了返回的那部分代碼了),而它們的返回結果是不同的(操作系統來控制返回結果):父進程中的fork返回子進程的pid;而子進程中的fork返回0;如果fork失敗,返回的是-1。

fork只是創造出了兩個幾乎一樣的進程,它們運行的是同樣的代碼,這和一開始說的可不一樣,因為我們創造一個新進程大多是用來執行新程序代碼的。這時我們需要exec類函數,當進程調用一種exec函數時,該進程的程序代碼完全被新程序替換,從新程序的啟動例程開始執行。調用exec並不創建新進程,所以調用exec前後該進程的id並未改變。如果調用成功則加載新的程序從啟動代碼開始執行,不再返回,如果調用出錯則返回-1,所以exec函數只有出錯的返回值而沒有成功的返回值。exec 系統調用執行新程序時會把命令行參數和環境變量表傳遞給 main 函數。環境變量表是進程所處系統環境的一個描述,一段代碼要正常執行必然要使用各種系統資源,環境變量表就是對它的一個抽象。但是,exec類函數是需要顯式調用的,子進程不會主動加載新的程序代碼!所以,一般是在父進程的代碼中,根據fork的返回值寫一個分支,子進程的分支中顯式調用exec。 進程的終止

一個進程在終止時會關閉所有文件描述符,釋放在用戶空間分配的內存,但它的PCB還保留著,操作系統在其中保存了一些信息:如果是正常終止則保存著退出狀態,如果是異常終止則保存著導致該進程終止的信號是哪個。這個進程的父進程可以調用 wait 或 waitpid 獲取這些信息,然後徹底清除掉這個進程。我們知道一個進程的退出狀態可以在Shell中用特殊變量 $? 查看,因為Shell是它的父進程,當它終止時Shell調用 wait 或 waitpid 得到它的退出狀態同時徹底清除掉這個進程。

如果一個進程已經終止,但是它的父進程尚未調用 wait 或 waitpid 對它進行清理,這時的進程狀態稱為僵屍(Zombie)進程。任何進程在剛終止時都是僵屍進程,正常情況下,僵屍進程都立刻被父進程清理了。

如果一個父進程終止,而它的子進程還存在(這些子進程或者仍在運行,或者已經是僵屍進程了),則這些子進程的父進程改為 init 進程。 init 是系統中的一個特殊進程,通常程序文件是 /sbin/init ,進程id是1,在系統啟動時負責啟動各種系統服務,之後就負責清理子進程,只要有子進程終止, init 就會調用 wait 函數清理它。

僵屍進程是不能用 kill 命令清除掉的,因為 kill 命令只是用來終止進程的,而僵屍進程已經終止了。所以一顆可行的辦法是,kill其父進程。

wait 和 waitpid 函數的原型是:
#include 
#include 
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

若調用成功則返回清理掉的子進程id,若調用出錯則返回-1。父進程調用 wait 或 waitpid 時可能會:
+ 阻塞(如果它的所有子進程都還在運行)。
+ 帶子進程的終止信息立即返回(如果一個子進程已終止,正等待父進程讀取其終止信息)。
+ 出錯立即返回(如果它沒有任何子進程)。

Copyright © Linux教程網 All Rights Reserved