多工環境


何謂程序? (特別與 「程式」 對照)

程式 program 是一個靜態的東西, 通常指放在硬碟 (或光碟, 或其他儲存裝置) 上面的檔案。 有時候會以 程式原始碼 program source code可執行檔 executable 來更明確地區分程式的兩種狀態 -- 前者是給人讀/寫的版本; 後者是經過 編譯, 給機器執行的版本。 當然如果一個程式以某種 直譯語言 interpreted language 寫成, 就沒有這個分別了, 同樣一個檔案, 既可以給人讀寫, 也可以給機器執行。 現今的 命令稿語言 scripting language 如 shell script, perl, python, php, ... 等等, 均屬此類。

程序 process 則是一個動態的東西, 可以說是 "執行中的程式 (program in execution), 它的生命從載入記憶體, CPU 開始執行起, 到最後一個指令被 CPU 執行結束為止。

通常程序的壽命相形較短, 只存在於電腦開機的時刻; 而程式則不然。 又, 一個程式可能產生好幾個程序 -- 例如上網頁製作課時, 老師一聲令下, 許多同學先後打開同一個程式 nvu。 一個程序也可能先後執行好幾個不同的程式, 例如本頁中使用 exec 的實驗。

操作範例

[process control]
[job control]
  1. ps 命令並注意 "命令欄" (command), 可以看到有兩個 processes (程序) 正在執行: 一個是 bash (或 tcsh); 另一個是 ps.
  2. ps -f 命令並注意 "狀態欄" (status): 先前一直在接受你輸入命令列的 shell 程序正在睡覺 (sleep); 而剛剛叫出來執行的 ps 程序則正在跑 (running).
  3. 注意上面的 pid 與 ppid (parent process id). 再下一次 ps -f 命令, 再看看它們的 pid 與 ppid: 你的 shell 還是同一個 process; 而 ps 則是另一個新的 process. 但不論是先前的 ps 或是現在的 ps, 都是從你的 shell 給 fork 出來的 (它們共同的 parent process 都是你的 shell).
  4. 接下來不要 logout, 另外再開啟一個 terminal:
    1. 如果你在 X-Window 下, 就開啟一個新的 rxvt 或 xterm;
    2. 如果你在 Linux 的文字模式下, 就用 <Alt>-F2 或 <Alt>-F3 或 <Alt>-F6 切換到另一個 virtual terminal (虛擬終端機) 去.
    3. 如果你在 MS Windows 下透過網路連到 UNIX 主機, 就再開一個 putty 或 telnet.
  5. 在這個新的 terminal 下, 下 ps 命令, 結果還是看到兩個 processes. 結論: 平時 ps 只顯示在同一個 terminal 內執行的所有 processes. 如何一次看到自己所有的 processes, 不論這些 processes 在那個 terminal 下執行, 甚或不在任何一個 terminal 下執行? 用 ps x. Q: 為什麼只看到 3 個, 而不是 4 個? 猜猜看: 如果你開啟第三個 terminal, 再在其中任何一個 terminal 內下 ps x, 會看到那幾個 processes? (你應該可以猜得到其中幾個的 pid.)
  6. Q: 在一個 terminal 下用 nano 編輯一個檔案, 在第二個 terminal 下用 less 看另一個檔案, 在第三個 terminal 下用 ps x 看看有那些 processes 在執行. 請指出每個 pid 所代表的 process 各在做什麼事情.
  7. 如何查看系統內所有的 processes? 下 ps -A 命令. 可是那些是我的, 那些是別人的呢? 下 ps -Af 命令. (我最常用這個組合)
  8. 查看系統內所有 processes 的父子關係: ps -Af --forest | less
  9. 準備動作: 把 sysinfo 存檔, 並把執行的權限開放給自己. 執行 ./sysinfo 這是一個文字模式下的電子鐘; 同時還會印出系統內目前總共有多少個程序在執行.
  10. 如何打斷正在執行的 process? 按 ^C.
  11. 把一個命令丟到背景去執行: sysinfo & 雖然很明顯地, sysinfo 開始執行了; 但是請注意 command prompt 又跑出來了. 這表示你可以繼續下命令, 例如 ls 或是 last | less 等等. (題外話: sysinfo 使用 ANSI Escape Sequence 來移動遊標, 所以它的輸出可以固定在右上角, 不過這和 process 的觀念無關) 你在 shell 底下繼續執行的叫做 前景 job (foreground job); sysinfo 叫做 背景 job (background job)
  12. 再丟一個背景 job 吧: sysinfo 3 & 這次新的 sysinfo 顯示在第三列. (而原來的 sysinfo 還是在第一列繼續執行.)
  13. 看一下現在有那些背景 jobs 在執行: jobs
  14. 也用 ps l 看一下有那些 processes 正在執行. 目前可以把 job 和 process 想成是同一個東西 -- 就是一個正在執行的程式. jobs 命令只顯示在背景執行, 或處於 "凍結" 狀態的 processes; 而 ps 則連正在前景執行的也會顯示.
  15. 一次把兩個背景 jobs 都終結掉: kill %1 %2 然後用 jobs 再檢查一下.
  16. 這一次在前景執行: sysinfo 然後按 ^Z 鍵把正在執行的前景 job "凍結" (suspend) 起來, 再用 jobs 看看所有背景 job 的狀態, 最後用 bg %1 指令把 1 號 job 丟到背景去執行, 並用 jobs 檢查結果.
  17. 如果有好幾個 processes 同時在跑, 那麼鍵盤輸入的資料究竟會被那個 process 讀進去呢? 當然就是那個正在前景執行 (用 jobs 看不到, 用 ps 才看得到) 的 process 嘍. 如果它搶得鍵盤輸入, 卻又不用, 那麼就會像剛才最開始直接在前景執行 sysinfo 一樣, 對一般鍵沒有反應.
  18. 接下來用 fg %1 指令把 1 號背景 job 帶到前景執行. (所以沒有 command prompt 出來) 如果沒有問題, 就再用 ^Z 把它凍結起來.
  19. 我們可以用 kill 命令達到相同的效果:
  20. 再來先用 kill -CONT %1 叫 (目前處於 suspended 狀態的) 1 號 job 在背景繼續執行 (效果和 bg %1 一樣).
  21. kill -STOP %1 凍結 (正在背景執行的) 1 號 job.
  22. kill -TERM %1 打斷 1 號 job. (其實用 kill %1 就可以了.)
  23. 有些程式非常頑強, 無法用 ^C 中斷, 怎麼辦? 可以開啟另外一個 terminal, 用 ps x 看, 再用 kill 命令把它打斷. 如果 kill -TERM 還沒有辦法, 可以用 kill -KILL.
  24. [exec 的效果] 先前的終端機全部關掉. 重開一個終端機, 執行 echo $SHLVL 看一下目前的 shell 是第幾層, 並執行 ps l 看看有那些 processes 在執行, 然後下 bash 以進入另外一個 shell, 再執行 echo $SHLVLps l. 可以看到原來的 shell 為了等現在的 shell 執行完畢所以在睡覺; 現在的 shell 為了等 ps 所以在睡覺. 按一次 ctrl-d 結束目前的 shell, 再看看目前的 shell 層次, 及有那些 processes 在執行. 最後再按一次 ctrl-d 脫離最開始的 shell.
  25. 同樣重開一個終端機, 執行 echo $SHLVLps l 接著下 exec bash 再看看目前的 shell 層次與正在執行的 processes. 注意到現在只有一個 shell, 它的 pid 和原來的 shell 一樣, 而原來的 shell 不見了! 原來的 shell 變身為 bash 了. 現在只要按一次 ctrl-d 就會離開 linux.

名詞解釋

程序的各種狀態變換

  1. process (程序): 一個正在執行的程式. 一個可執行檔可以產生好幾個 processes. 例如一個使用者可以同時開啟好幾個終端機視窗, 每個視窗都是一個獨立執行的 process, 但是它們共用 /bin/bash 這一個可執行檔.
  2. 每個 process 都有一個識別代號 pid (process identification).
  3. 每個 process 都有一個 parent, 當初這個 process 之所以會產生, 就是由它的 parent process 分出來的. 從一個 process 分出另一個 process 的步驟叫做 fork, 可以想成是無性生殖.
  4. 但是生出一個與原 process 一模一樣的東西有什麼用呢? 通常 child process 馬上會變身 (exec) 成為另外一個應用程式.
  5. terminal 或稱 tty (終端機): 大約可以這樣理解: 每個文字視窗就是一個 tty
  6. ps l 指令結果各重要欄位意義:
    1. STAT: 狀態, S (休眠), R (正在跑), I (閑置), T(凍結)
    2. UID: 這個 process 以那個使用者的名義在執行?
    3. PID: Process IDentification #, 程序的編號.
    4. PPID: Parent Process IDentification #, 父程序的編號.
    5. TTY: 使用的控制終端機.
    6. TIME: 使用了幾秒鐘的 CPU 時間
    7. COMMAND: 先前是甚麼樣的命令產生這個程序的?
  7. ps 指令常用的參數:
    1. a: 不只使用者自己的, 連別人的程序也顯示出來.
    2. u: 對每個程序多顯示 "USER" 這個欄位.
    3. x: 不只有控制台的, 連沒有控制台機的程序也顯示出來.
    4. w: 不要把後面切掉啦!
    5. v, m: 多一些記憶體使用狀況報告
  8. job (工作?):
  9. 控制程序執行的一些方法:
    1. 用 & 把程序丟到背景去執行
    2. 用 ^C 把執行到一半的前景程序打斷 (無法恢復)
    3. 用 ^Z 把執行到一半的前景程序暫停
    4. jobs: 看看這個控制台有那些暫停或在背景執行的程序
    5. fg %數字: 把某個暫停的程序放到前景來執行
    6. bg %數字: 把某個暫停的程序放到背景去執行 (和當初直接用 & 的效果相同)
  10. 送信號給正在執行的程式: kill -TERM pid1 pid2 ...
    常用的信號:
    1. TERM (kill 命令的內定值): 警告性地通知程序該中斷了. 寫得好的程式會自己 "清場"
    2. STOP: 凍結 (正在背景執行的) 程序.
    3. CONT: 把原本被凍結的程序解凍, 丟到背景繼續執行.
    4. KILL: 強迫中斷程式, 大部分頑強的程序都會被中斷.
    5. HUP: 通常用來強迫 demon 類程序重新讀它的設定檔 (組態檔)
    其他詳見 signal(7) 手冊.