一.進程的基本概念
程序是為了完成某種任務而設計的軟件,比如vi是程序。什麼是進程呢? 進程就是運行中的程序。一個運行著程序,可能有多個進程。比如Web服務器是Apache服務器,當管理員啟動服務後,可能會有好多人來訪問,也就是說許多用戶同時請求httpd,Apache服務器將會創建多個httpd進程來對其進行服務。
首先我們看看進程的定義。進程是一個具有獨立功能的程序關於某個數據集合的一次可以並發執行的運行活動,是處於活動狀態的計算機程序。進程作為構成系統的基本細胞,不僅是系統內部獨立運行的實體,而且是獨立競爭資源的基本實體。了解進程的本質,對於理解、描述和設計操作系統有著極為重要的意義。了解進程的活動、狀態,也有利於編制復雜程序。
二.進程的屬性
進程的定義:一個進程是一個程序的一次執行的過程;程序是靜態的,它是一些保存在磁盤上的可執行的代碼和數據集合;進程是一個動態的概念,它是Linux系統的基本的調度單位。
一個進程由如下元素組成:
程序讀取的上下文,它表示程序讀取執行的狀態。
程序當前執行的目錄。
程序服務的文件和目錄。
程序訪問的權限。
內存和其他分配給進程的系統資源。
Linux進程中最知名的屬性就是它的進程號(Process Idenity Number,PID)和它的父進程號(Parent Process ID,PPID)。PID、PPID都是非零正整數。一個PID唯一地標識一個進程。一個進程創建新進程稱為創建了子進程(Child Process)。相反地,創建子進程的進程稱為父進程。所有進程追溯其祖先最終都會落到進號為1的進程身上,這個進程叫做init進程,是內核自舉後第一個啟動的進程。init進程扮演終結父進程的角色。因為init進程永遠不會終止,所以系統總是可以確信它的存在,並在必要的時候以它為參照。如果某個進程它在衍生出來的全部子進程結束之前被終止,就會出現必須以init為參照的情況。此時那些失去了父進程的子進程就都會以init作為它們的父進程。如果執行一下ps-af命令,可以列出許多父進程ID為1的進程來。Linux提供了一條pstree命令,允許用戶查看系統內正在運行的各個進程之間的繼承關系。直接在命令行中輸入pstree即可,程序會以樹狀結構方式列出系統中正在運行的各進程之間的繼承關系。
三.理解Linux下進程的結構
Linux中一個進程在內存裡有三部分數據,就是“數據段”、“堆棧段”、“代碼段”。基於I386兼容的中央處理器,都有上述三種段寄存器,以方便操作系統的運行,如下圖所示。
代碼段
數據段
堆棧段
代碼段是存放了程序代碼的數據,假如機器中有數個進程運行相同的一個程序,那麼它們就可以使用同一個代碼段。而數據段則存放程序的全局變量、常數及動態數據分配的數據空間。堆棧段存放的就是子進程的返回地址、子程序的參數及程序的局部變量。堆棧段包含在進程控制塊PCB(Process Control Block)中。PCB處於進程核心堆棧的底部,不需要額外分配空間。
四.進程狀態
現在我們來看看,進程在生存周期中的各種狀態及狀態的轉換。下面是Linux系統的進程狀態模型的各種狀態。
用戶狀態:進程在用戶狀態下運行的狀態。
內核狀態:進程在內核狀態下運行的狀態。
內存中就緒:進程沒有執行,但處於就緒狀態,只要內核調度它,就可以執行。
內存中睡眠:進程正在睡眠並且進程存儲在內存中,沒有被交換到SWAP設備。
就緒且換出:進程處於就緒狀態,但是必須把它換入內存,內核才能再次調度它運行。
睡眠且換出:進程正在睡眠,且被換出內存。
被搶先:進程從內核狀態返回用戶狀態時,內核搶先於它做了上下文切換,調度了另一個進程。原先這個進程就處於被搶先狀態。
僵死狀態(zombie):進程調用exit結束,進程不再存在,但在進程表項中仍有記錄,該記錄可由父進程收集。
現在我們從進程的創建到退出來看看進程的狀態轉化。需要說明的是,進程在它的生命周期裡並不一定要經歷所有狀態。
五.Linux進程的創建
fork函數在Linux下產生新的進程的系統調用,這個函數名是英文中“分叉”的意思。為什麼取這個名字呢? 因為一個進程在運行中,如果使用了fork,就產生了另一個進程,於是進程就“分叉”了,所以這個名字取得很形象。fork的語法如下所示:
代碼如下:
#include
#include
pid_t fork();
在Linux網絡編程中經常用到fork()系統調用。例如在一個客戶機/Web服務器構建的網絡環境中,Web服務器往往可以滿足許多客戶端的請求。如果一個客戶機要訪問Web服務器,需要發送一個請求,此時由服務器生成一個父進程,然後父進程通過fork()系統調用產生一個子進程,此時客戶機的請求由子進程完成。父進程可以再度回到等待狀態不斷服務其他客戶端。原理如下圖所示。
有一個更簡單的執行其他程序的函數system,參數string傳遞給一個命令解釋器(一般為sh)執行,即string被解釋為一條命令,由sh執行該命令。若參數string為一個空指針,則檢查命令解釋器是否存在。該命令可以和同命令行下的命令形式相同,但由於命令作為一個參數放在系統調用中,應注意編譯時對特殊意義字符的處理。命令的查找是按PATH環境變量的定義執行的。命令所生成的後果一般不會對父進程編程造成影響。返回值:當參數為空指針時,只有當命令解釋器有效時返回值為非零。若參數不為空指針,返回值為該命令的返回狀態(同waitpid())的返回值。命令無效或語法錯誤則返回非零值,所執行的命令被終止。其他情況則返回-1.它是一個較高層的函數,實際上相當於在shell下執行一條命令,除了system之外,系統調用exec來執行一個可執行文件,來代替當前進程的執行映像。系統調用exit的功能是終止發出調用的進程。sleep函數調用用來指定進程掛起的秒數。wait函數族用來等待和控制進程。poppen函數和system函數類似,區別是它用管道方式處理輸出。
父進程和子進程的關系是管理和被管理的關系,當父進程終止時,子進程也隨之而終止。但子進程終止時,父進程並不一定終止。比如httpd服務器運行時,我們可以殺掉其子進程,父進程並不會因為子進程的終止而終止。
六.進程的管理
1.啟動進程
輸入需要運行的程序的程序名,執行一個程序,其實也就是啟動了一個進程。在Linux系統中,每個進程都具有一個進程號(PID),用於系統識別和調度進程。啟動一個進程有兩個主要途徑:手工啟動和調度啟動,後者是事先進行設置,根據用戶要求自動啟動。由用戶輸入命令,直接啟動一個進程便是手工啟動進程。但手工啟動進程又可以分為很多種,根據啟動的進程類型不同;性質不同,實際結果也不一樣。
(1)前台啟動
前台啟動是手工啟動一個進程的最常用的方式。用戶鍵入一個命令“df”,就已經啟動了一個進程,而且是一個前台的進程。這時候系統其實已經處於多進程狀態。有許多運行在後台的、系統啟動時就已經自動啟動的進程正在悄悄運行著。有的用戶在鍵入“df”命令以後趕緊使用“ps -x”查看,卻沒有看到df進程,會覺得很奇怪。其實這裡因為df這個進程結束太快,使用ps查看時該進程已經執行結束了。如果啟動一個比較耗時的進程,例如在根命令下運行:find,然後使用ps aux查看,就會看到在裡面有一個find進程。
(2)後台啟動
直接從後台手工啟動一個進程用得比較小一些,除非是該進程甚為耗時,且用戶也不急著需要結果。假設用戶要啟動一個需要長時間運行的格式化文本文件的進程,為了不使整個shell在格式化過程中都處於“癱瘓”狀態,從後台啟動這個進程是明智的選擇。
2.進程調度
當需要中斷一個前台進程的時候,通常使用Ctrl+C組合鍵。但是對於一個後台進程,就不是一個組合鍵所能解決的了,這時就必須使用kill命令。該命令可以終止後台進程。至於終止後台進程的原因有很多,或許是該進程占用的CPU時間過多;或許是該進程已經掛死。這種情況是經常發生的。kill命令的工作原理是:向Linux系統的內核發送一個系統操作信號和某個程序的進程標識號,然後系統內核就可以對進程標識號指定的進程進行操作。
七.Linux的第一個進程:init
init是Linux系統執行的第一個進程,進程ID為1,是系統所有進程的起點,主要用來執行一些開機初始化腳本和監視進程。Linux系統在完成核內引導以後就開始運行init程序,init程序需要讀取配置文件/etc/inittab。Inittab是一個不可執行的文本文件,它由若干行命令所組成。
在RHEL 4系統中,inittab配置文件的內容如下所示:
代碼如下:
#
#inittab
#
#
#author
#
#Default runlevel.the runlevels used by rhs are:
#0 - halt (do not set initdefault to this)
#1 - single user mode
#2 - multiuser