歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網

UNIX的進程

日期:2017/3/3 15:26:02   编辑:Unix基礎知識

在UNIX系統中,每個系統和最終用戶任務都包含在某個進程中。系統總是不斷地創建新的進程,當任務結束或意外發生時,進程會終止。在本文中,您將了解如何控制進程和使用一些命令來查看您的系統。

在最近的街頭游樂會上,有一個單人樂隊讓我很是著迷。的確,這讓我很開心,還給我留下了深刻印象。這個單人樂隊的唯一成員利用嘴、大腿、膝蓋和腳分別控制口琴、五弦琴、钹和腳鼓,生動地演奏了齊柏林飛船樂隊的《天堂的階梯》,他演奏的貝多芬《第五交響曲》也頗為動人。和他相比,我能一邊拍腦袋一邊摸肚子就覺得很不錯了。(或者是一邊拍肚子一邊摸腦袋。)

對您來說,幸運的是,UNIX® 操作系統更像是那個單人樂隊,而不是像我這個笨手笨腳的專欄作家。UNIX特別擅長同時處理多個任務,並安排它們訪問系統中的有限資源(內存、設備和 CPU)。打個比方,UNIX 可以一邊散步,一邊嚼口香糖。

這個月我們研究的內容要比平常更深入一些,我們會看看 UNIX 是如何同時做這麼多事的。這次我們還會探索 shell 的內部,了解工作控制命令,如 Ctrl+C(終止)和 Ctrl+Z(掛起)是怎樣實現的。

一個真正的多任務系統

在 UNIX(以及大多數現代操作系統,包括 Microsoft® Windows®、Mac OS X、FreeBSD 和 Linux®)中,每個計算任務都是由一個進程表示的。UNIX 似乎能同時運行很多任務,這是因為每個進程都會輪流(從概念上來講)分到一小片 CPU 時間。

一個進程就像一個容器,它與某個正在運行的應用程序、環境變量、應用程序的輸入和輸出,以及進程的狀態(包括其優先級和累計資源使用情況)捆綁在一起。圖 1 顯示了一個進程。

圖 1. UNIX進程的概念化模型

為了便於理解,您可以把一個進程想像成一個獨立的國家,有邊界、資源,還有國民生產總值。

每個進程還有一個所有者。一般來說,您啟動的任務(如您的 shell 和命令)的所有者就是您。系統服務的所有者可能是特殊用戶或超級用戶 root。例如,為了增強安全性,Apache HTTP Server 的所有者一般是一個名為 www 的專用用戶,該用戶能提供 Web 服務器所需的的文件訪問權限,但不包含其他權限。

進程的所有權可能會改變,但必須嚴格保持其獨占性。一個進程在任何時候都只能有一個所有者。

最後,每個進程都具有權限。一般來說,進程的權限與其所有者的權限是相稱的。(例如,如果您無法在命令行 Shell 中訪問某個特定文件,則您從 Shell 中啟動的程序也會繼承同樣的限制。)這一繼承規則有一個例外情況,即應用程序啟用了特殊的 setuid 或 setgid 位,如 ls 顯示的那樣,在此情況下,某個進程可能會獲得比其所有者更高的權限。

setuid 位可以使用 chmod u+s 進行設置。setuid 的權限如下所示:

$ ls -l /usr/bin/top
-rwsr-xr-x   1 root wheel   83088 Mar 20 2005 top

setgid 位可以使用 chmod g+s 設置:

$ ls -l /usr/bin/top
-r-xr-sr-x  1 root tty 19388 Mar 20 2005 /usr/bin/wall

一個 setuid 進程(如啟動 top)是用擁有該文件的用戶權限運行的。因此,當您運行 top 時,您的權限會被提升,與 root 的權限等同。類似地,一個 setgid 進程是用與文件的組所有者相關聯的權限運行的。

例如,在 Mac OS X 中,wall 工具(“write all”的縮寫,因其會將某個消息寫入所有物理或虛擬終端設備而得名)的 setgid 被設為tty(如上所示)。當您登錄並分配到一個用來鍵入的終端設備(該終端成為 Shell 的標准輸入)時,您將被指定為該設備的所有者,而 tty 成為組所有者。因為 wall 是以組 tty 的權限運行的,所以它可以打開和寫入所有終端。

獲取列表

就像所有其他系統資源一樣,您的UNIX有一個有限但十分龐大的進程池(實際上,系統中的進程幾乎用之不盡)。每個新任務(如啟動 vi 或運行 xclock)都會立即從池中分配到一個進程。在 UNIX 系統中,您可以使用 ps 命令,查看一個或多個進程。

例如,如果您想查看您擁有的所有進程,鍵入 ps -w --user username : $ ps -w --user mstreicher

您可以使用 ps -a -w -x 查看完整的進程列表。(ps 命令的格式和特定的標志隨各個 UNIX 版本而有所差異。請參閱系統的聯機文檔,以查找具體的說明。) -a 是選擇 tty 設備上運行的所有進程;-x 則可進一步選擇與 tty 無關的所有進程,通常包括所有的永久系統服務,如 Apache HTTP server、cron 工作調度程序等等;-w 則以加寬的格式顯示內容,在查看命令行或與每個進程相關的應用程序完整路徑名時很有用。

ps 具有豐富的功能,某些版本的 ps 甚至允許您自定義輸出。例如,下面就是一個有用的自定義進程列表:

$ ps --user mstreicher -o pid,uname,command,state,stime,time
PID USER   COMMAND     S STIME   TIME
14138 mstreic sshd: mstreicher S 09:57 00:00:00
14139 mstreic -bash      S 09:57 00:00:00
14937 mstreic ps --user mstrei R 10:23 00:00:00

-o 根據各列名稱的順序對輸出進行格式化。pid、uname 和 command 分別指進程 ID、用戶名和命令。state 代表進程的狀態,如正在睡眠 (S) 或運行 (R)。(稍後將對進程狀態進行更詳細的說明。)stime 顯示命令的開始時間,time 則顯示該進程占用了多少 CPU 時間。

進程從哪裡來?

在 UNIX 中,某些進程會從系統啟動到關機的時間裡一直運行,但大多數進程都會隨任務的開始和完成而迅速地出現和消失。有時,某個進程可能會“早夭“,甚至會“暴死”(比如在系統崩潰時)。新的進程是從哪裡來的呢?

每個新的 UNIX 進程都是某個現有進程的產物。另外,每個新進程(不妨將其稱為“子”進程)是對“父”進程的克隆體(至少有一瞬間是如此),直到“子”進程繼續獨立執行為止。(如果每個進程都是某個現有進程的後代,那麼不免會有一個疑問:“第一個進程是從哪裡來的?”請參閱下面的側欄以尋找答案。)

雞和蛋

某些爭論是經久不息的:生存還是毀滅?可口可樂還是百事可樂?PC 還是 Mac?當然,還有一個古老的悖論,“雞生蛋,還是蛋生雞?”

如果每個新的 UNIX 進程都是某個現有的、正在運行的進程的後代,那麼第一個進程是從哪裡來的?答案是:UNIX 內核在系統啟動序列中產生了第一個進程。

第一個進程被恰如其分地稱為 init,所有其他系統進程的親緣關系最終都可以追溯到init。實際上,init 的進程編號是1。如果您要查看 init 的狀態,可鍵入 ps -l 1:

F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S  0  1  0 0 68  0 -  373 select ?  0:02 init [2]

正如您所看到的,init 的所有者 (UID) 是 0 (root)。和系統中所有其他進程不同的是,init 沒有父進程,它的父進程 ID (PPID) 為 0。

圖 1-4 詳細說明了進程的產生過程:

在圖 2 和圖 3 中,進程 A (Process A),正在運行一個由藍色方框表示的程序。它運行編號為 10,11,12…的指令。進程 A 有屬於自己的數據、程序的副本、打開的文件集,以及自己的環境變量集,當進程 A 剛出現時,會對它們進行初次捕捉。
圖 2. 進程 A 運行代碼

在 UNIX 中,fork() 系統調用(之所以有這個名稱,是因為它是一個調用或請求,要求操作系統進行協助)被用來產生新的進程。當程序 A (Program A) 執行指令 13 (Instruction 13) fork() 時,系統會立即創建進程 A 的一個精確克隆版本,並將其命名為進程 Z (Process Z)。Z 具有和 A 相同的環境變量、相同的內存內容、相同的程序狀態,打開的文件也一樣。圖 3 顯示的是進程A 生成進程Z後,進程A 和Z 的狀態。
圖 3. 進程 A 生成自身的克隆體


起初,進程 Z 是從進程 A 停止的地方開始執行的。也就是說,此後進程 Z 從指令 14 (Instruction 14) 處開始執行。進程 A 會在同一指令位置繼續執行。

一般來說,指令 14 處的編程邏輯將測試當前的進程是子進程還是父進程,也就是說,進程 Z 和進程 A 中的指令 14 分別判定這兩個進程是否為其他進程的後代或祖先。為了以示區別,fork() 系統調用在子進程中返回 0,但返回給父進程的卻是進程 Z 的進程 ID。

在上次測試之後,進程A 和進程Z 會出現差異,每個進程會采用單獨的代碼路徑,就像路上出現岔道,每一個都會走上不同的分枝。生成一個新進程的流程更多地被稱為分叉,這就像兩位旅行者走到了路上的岔道。因此,系統調用被命名為 fork()。

在分叉之後,進程A 可能會繼續運行同一個應用程序。而進程 Z 則可能立即發生變化,轉到另一個應用程序。後一種操作會改變程序通過進程運行的內容,它被稱為執行,但您可以把它看成是一次再生過程:雖然進程 ID 不變,但進程內部的指令會被新程序的指令完全取代。圖 4 顯示的是稍後進程 Z 的狀態。


圖 4. 進程 Z 現在獨立於它的祖先,即進程 A

分叉

您可以在自己的命令行,很方便地體驗分叉操作。首先,打開一個新的 xterm。(您現在可能會認識到,xterm 就是它本身的進程,在 xterm 中,shell 是由 xterm 產生的一個獨立進程)。接下來,輸入: ps -o pid,ppid,uname,command,state,stime,time
您應該會看到類似這樣的內容:  PID PPID USER   COMMAND     S STIME   TIME
16351 16350 mstreic -bash      S 11:23 00:00:00
16364 16351 mstreic ps -o pid,ppid,u R 11:24 00:00:00

從該列表的 PPID 字段中,我們知道 ps 命令是 bash shell 的子進程。(-bash 中的連字符說明 shell 實例是一個登錄 shell。)為了運行 ps,bash 會分叉,創建一個新進程;新進程通過使用執行,使其本身得以重生,轉化為 ps 的一個新的實例。

這裡是另一個可供嘗試的實驗。鍵入: sleep 10 & sleep 10 & sleep 10 & ps -o pid,ppid,uname,command,state,stime,time
您應該會看到類似這樣的內容: $ sleep 10 & sleep 10 & sleep 10 & ps -o pid,ppid,uname,command,state,stime,time
PID PPID USER   COMMAND     S STIME   TIME
16351 16350 mstreic -bash      S 11:23 00:00:00
16843 16351 mstreic sleep 10     S 11:42 00:00:00
16844 16351 mstreic sleep 10     S 11:42 00:00:00
16845 16351 mstreic sleep 10     S 11:42 00:00:00
16846 16351 mstreic ps -o pid,ppid,u R 11:42 00:00:00

命令行生成四個新進程。在每個 sleep 命令後鍵入 &,在後台運行每一個命令,或與 Shell 並行。 ps 是生成的另一個進程,但它是在前台運行的,可以防止 shell 在該進程終止之前運行其他命令。而且,如PPID的值所示,所有四個進程都是 Shell 的後代。三個sleep命令都被標為S,因為沒有哪個進程會在它們睡眠時使用資源。

為了方便起見,shell 會持續跟蹤它生成的所有後台進程。鍵入jobs,可以看到一個列表:

$ sleep 10 & sleep 10 & sleep 10 &
[1] 16843
[2] 16844
[3] 16845
$ jobs
[1]  Running         sleep 10 &
[2]  Running         sleep 10 &
[3]  Running         sleep 10 &

此處,為了方便起見,三個工作分別用標簽標為 1,2 和 3。數字 16843、16844 和 16845 分別是每個進程的進程 ID。因此,後台任務 1 即為進程 ID 16843。

您可以利用這些標簽,從命令行操作您的後台工作。例如,如要終止某個命令,鍵入 kill %N ,其中 N 是該命令的標簽。如要將某個命令由後台移到前台,請鍵入 fg %N :

$ sleep 10 & sleep 10 & sleep 10 &
[7] 17741
[8] 17742
[9] 17743
$ kill %7
$ jobs
[7]  Terminated       sleep 10
[8]- Running         sleep 10 &
[9]+ Running         sleep 10 &
$ fg %8
sleep 10

從命令行中同時異步運行多個命令,是處理您自己的任務集的好方法。一個長時間運行的工作(例如,系統管理的數值計算或大型程序的編譯)最適合放在後台。為了捕獲每個後台命令的輸出,請考慮使用重定向操作符 >、>&、>> 和 >>&,將輸入重定向到某個文件。當後台命令結束後,shell 會在下一個提示符之前顯示一條警告消息:

$ whoami
mstreicher
[8]- Done          sleep 10
[9]+ Done          sleep 10
$

向遙遠的進程池前進

某些進程會一直存活(如 init),而某些進程會以新的形式重生(如您的 shell)。最終大多進程都會因自然原因(即程序運行結束)而消亡。

此外,您還可以將某個進程放在一個掛起的動作序列中,等待被再次激活。正如先前的示例所示,您可以用 kill 提前終止某個進程。

當某個命令在前台運行時,如果您希望將它掛起,請按 Ctrl + Z:

$ sleep 10
(Press Control-Z)
[1]+ Stopped         sleep 10
$ ps
PID PPID USER   COMMAND     S STIME   TIME
18195 16351 mstreic sleep 10     T 12:44 00:00:00

Shell 已將命令掛起,為了方便起見,還為它分配了一個標簽。您可以像先前那樣使用這個標簽,以終止工作或讓工作返回前台。您還可以使用 bg 命令在後台恢復這個進程:

bg %1
[1]+ sleep 10 &

當某個命令在前台運行時,如果您想終止它,請按 Ctrl + C:

$ sleep 10
(Press Control-C
$ jobs
$


您的 Shell 能使進程的掛起和終止變得更容易,但在 Shell 單純的外表下,卻隱藏著復雜的一面。在內部,Shell 使用 UNIX 信號來影響進程的狀態。信號是一個事件,它被用來向某個進程發出警報。操作系統生成許多信號,但您可以將信號從一個進程發送到另一個進程,甚至能讓某個進程給自己發送信號。

UNIX 包括多種信號,它們大多都有特殊目的。例如,如果您將信號 SIGSTOP 發送到某個進程,該進程將掛起。(要獲取信號的完整列表,請鍵入 man 7 signal 或鍵入 kill -L)。您可以用 kill 命令發送信號。

$ sleep 20 &
[1] 19988
$ kill -SIGSTOP 19988
$ jobs
[1]+ Stopped         sleep 20

起初,sleep 命令在後台啟動,其進程 ID 為 19988。在發送 SIGSTOP 之後,該進程會改變狀態,變為掛起或停止。發送另一個信號 SIGCONT,重新激活進程,該進程將從上次停止的地方繼續執行。

也就是說,每次您按 Ctrl + Z 時,您的 shell 將向前台發送 SIGSTOP 信號。bg 命令發送 SIGCONT。而 Ctrl + C 則會發送 SIGTERM,要求立即終止進程。

一些信號可以被某個進程阻塞,應用程序可以通過設計,顯式地“捕捉 (catch)”信號,並以一種特殊的方式對每個事件作出反應。例如,系統服務 xinetd 會按需要啟動其他網絡服務,它在收到 SIGHUP 時會重新讀取它的配置文件。在 Linux 中,向 init 發送信號,可能會改變系統的運行級別,甚至會導致系統關閉。.(這裡有一個問題:kill %1 和 kill 1 有什麼區別?

進程甚至可以給自己發送信號。想像一下,您正在編寫一個游戲,想留給用戶五秒鐘時間作出反應。您的代碼可以設置一個五秒鐘的定時器,接下來繼續進行重繪屏幕等操作。當定時器的時間耗盡後,將有一個 SIGALRM 信號被送回您的進程。呯!時間到!

(這裡提供了問題的答案:kill %1 會終止標簽為 1 的後台工作。kill 1 會終止 init,當必須關閉計算機時,將向操作系統發送這個信號。)

在特殊情況下,操作系統還可以將一些其他信號傳送給進程。內存違例會引發 SIGSEGV 信號,立即終止進程,並留下一個內核轉儲。有一個特殊的信號 SIGKILL 是無法被阻塞或捕捉的,它會立即終止某個進程。

和 UNIX 中許多其他資源一樣,您只能向您擁有的進程發送信號。這可以防止您終止重要的系統服務和其他用戶的進程。超級用戶 root 可以向任何進程發送信號。

更多魔法揭密

UNIX 有許多可活動的部分。它有系統服務、設備、內存管理器等等。好在這些復雜的花樣大都被隱藏起來,不會被看到,或可以通過用戶界面(如 shell 或窗口工具)很方便地使用。更妙的是,如果您想深入探究,隨時都可以使用 top, ps 和 kill 等專用工具。

現在您已經知道了進程的工作原理,可以組成自己的單人樂隊了。只有一個要求:成為一只自由自在的飛鳥!

Copyright © Linux教程網 All Rights Reserved