進程
程式 (program) 的一個實例 (instance),由 fork()
與 execve()
等系統調用所起始、執行、直到下達 exit()
系統調用為止
UNIX 支援多進程,由文本切換實現(context switch),進程本身不管文本切換,也沒有必要在程式裡撰寫撤回控制權給操作系統的處理
系統內核的調度器(scheduler)負責管理進程的執行,並參考進程的優先權決定順序
平均負載(load average)
在任何瞬間,等待執行的進程平均數,當平均負載持續地超出可用 CPU 的承載時,表示系統已經超載
由於會一直變化,uptime
指令分別回報最後一分鐘、五分鐘、十五分鐘的估值
建立進程
UNIX 最大的貢獻,就是能輕易建立進程
很多進程由 Shell 啟動——每個命令行的第一個單詞代表要執行哪個程序,且保證具備以下事項:
- 內核本文(kernel context),存在內核的數據結構,紀錄進程的資訊,方便管理與控制進程
- 一個私有的(private)、被保護的(protected)的虛擬位址空間,確保進程間不互相干擾。其可以是主機的可定址空間,可能受限於 Swap 、其他執行中工作的大小、系統調校參數的設置等
- 三個皆以開啟的文件描述代碼(標準輸入、標準輸出、標準錯誤輸出)
- 起始於交談模式 Shell 的進程,會有一個控制終端(controlling terminal),扮演三個標準文件數據流的默認來源與目的地
- Shell 展開命令行中的參數,省去程序的負擔且提供統一性
- 記憶體中的一個環境變量區域(environment space)會存在,透過函式庫調用取得
進程編號
編號為 0 的進程稱為 kernel、sched 或 swapper,可能不會顯示在 ps
列表中
進程的形式是樹狀的,除了 kernel 以外,每個進程都有父進程,及零至多個子進程
編號為 1 的進程稱為 init,對於父進程過早消失(die)的進程,其父進程會重新被指派給 init
系統在正常關機時,進程的刪除是編號由大到小依次執行的,直到剩下 init 為止,當 init 結束,系統終止
ps 命令
範例:實作簡化的 top 功能
範例:顯示用戶與相對的進程數、進程名稱
進程控制與刪除
大部分情況下,Shell 在處理下一個命令之前會等待上一個進程結束,不過只要在命令最後加上 &
字符,而非分號或換行符號,即可將進程放到後台執行(backgroud processes)
在 Shell 中,wait
命令可用以等待某個後台進程完成,不加任何參數的話則是等待全部後台進程完成
有四組鍵盤字符可以中斷前台進程(foreground processes),可透過 stty
命令設置:
- Ctrl-C: intr,刪除
- Ctrl-Y: dsusp,暫時擱置,直到輸入更新為止
- Ctrl-Z: susp,暫時擱置
- Ctrl-: quit,以核心轉儲(core-dump)方式刪除
正常行為的進程會如常完成工作,然後調用 exit()
,但有時我們會需要提早結束進程。kill
的功能就在這,不過它的名字取得不好,kill
命令其實是傳遞訊號給進程
只有進程自己本身、擁有者、root 或內核,可以傳送訊號給它,且進程無法判斷訊號從何而來
一次傳送多個信號時,傳送順序以及是否重複傳送是無法預知的
信號類型
訊號功能強調的是慣用性(convention),且應是可移植的,因此最好以特定名稱而非數字表示
對於不同程序而言,訊號所表示的意義不一定相同
ISO Standard C 只定義七種信號類型,POSIX 多增加了 20 多種信號類型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX |
暫停進程
STOP
、TSTP
通常意指暫停進程,直到 CONT
訊號出現,要求繼續執行
STOP
訊號必定傳送,無法被程序抓取或忽略
未指定抓取的訊號,通常會引起中斷,例如 FPE
、PIPE
;而 CHLD
為內核裡默認要被忽略的訊號,不會引發中斷
1 2 3 4 5 |
# 終止進程 $ kill -STOP 17787 # 一小時後恢復 $ sleep 3600 && kill -CONT 17787 & |
刪除進程
有四個相關的訊號:
TERM
:常表示「快速清除並離開」ABRT
:類似TERM
,但目標在於產生內存影像的副本,將其至於核心,即 program.core 或 core.PIDHUP
:常表示「先停止目前的工作,然後準備處理新的工作」,有重新啟動的意味KILL
:立刻終止進程
KILL
訊號必定傳送,無法被程序抓取或忽略
以慣例而言,應該先送出 HUP
,讓進程有機會優雅地終止(gracefully shutdown)
,如果進程沒有馬上離開,則應該再送出 TERM
,若還是無法離開,最後再使用 KILL
小心使用 kill
命令,當程序不成常終止時,可能會在文件系統留下殘餘數據,造成空間浪費,或導致下次程序運行發生錯誤。例如,daemon、郵件客戶端程序、文字編輯器、瀏覽器都會產生一個小型文件鎖(lock),紀錄程序正在運行。如果程序的第二個實例被啟動,而第一個實例還在執行時,第二個實例會偵測到已經存在的 lock,回報並終止程序
有些系統(GNU/Linux, Sun Solaris)提供 pgrep
與 pkill
命令,可以透過名稱而非 PID 來追蹤或刪除進程
抓取訊號
進程會向內核註冊那些它們想要處理的信號,在 Shell 中,trap
命令可以引發此註冊行為
EXIT
訊號是 Shell 額外提供給 trap
命令使用的,此訊號數值恆為 0,語句trap '...' EXIT
,代表語句在exit() 系統調用之前、在所有其他訊號之後會被引用(包含腳本的正常終止)
抓取 EXIT
完成後,腳本的離開狀態 $?
的值一樣會被保留(除非在抓取過程中重設)
須注意 DEBUG
、ERR
訊號的抓取行為在不同 Shell 版本之間可能會有不同,可移植性低
延遲進程
有四種延遲進程的情境:
- sleep / 延遲片刻:調度器忽略休眠中的進程,直到計時完成才喚醒進程;sleep 只耗用極少資源,且休眠幾乎不會干擾到其他進程
- at / 延遲至特定時間:例如
at 21:00 < command-file
代表延遲至下午九點執行,所有延遲的進程可以atq
查詢 - batch / 為資源控制而延遲:將進程加入至某個批次處理的隊列中。由於功能過於簡化,針對順序的控制很少,此命令很少用到
- crontab / 指定時間執行:按照 crontab 文件的設置定時執行特定工作
追蹤進程的系統調用
在執行程序時,顯示每個系統調用及目標程序執行時的參數,稱為系統調用追蹤器(system call tracer),常見的工具有 ktrace
、par
、strace
、trace
、truss
由於 Shell 沒有除蟲機制,使用系統調用追蹤器可以對輸出結果提供很有用的補充,追蹤的輸出可能很龐大,最好紀錄在文件中
監控文件訪問最常的追蹤方式是:在日誌尋找 access()、open()、stat()、unlink() 事件
在 GNU/Linux 上,使用 strace -e trace=file
可以減少日誌量
範例:在 GNU/Linux 追蹤 Bourne Shell 的系統調用
記得設置提示號變數 PS1
,用以區別原始與被追蹤的 Shell
1 2 3 |
$ PS1='traced$ ' strace -e trace=process /bin/sh execve("/bin/sh", ["/bin/sh"], 0x7ffc0cd210d0 /* 51 vars */) = 0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 只有預期的輸出,因為沒有建立新的進程 traced$ pwd /home/ssivart # 現在建立新的進程,包含命令輸出及追蹤輸出 traced-sh$ /bin/pwd clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f6742286850) = 24804 wait4(-1, /home/ssivart [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WSTOPPED, NULL) = 24804 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=24804, si_uid=1001, si_status=0, si_utime=0, si_stime=0} --- # 現在退出 Shell traced$ exit exit_group(0) = ? +++ exited with 0 +++ |
追蹤結果顯示 Shell 調用了 clone() 以啟動 /bin/pwd 進程,其輸出與下一個 wait4() 的追蹤輸出混合在一起。命令正常終止,且 Shell 收到 CHLD 訊號,指出子進程完成
範例:追蹤瀏覽器的系統調用
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# 將追蹤輸出到文件 $ strace -e trace=file -o browser.log firefox # 正常關閉瀏覽器後查詢瀏覽器的鎖定文件 $ grep -i /lock browser.log lstat("/tmp/firefox/lock", 0x7ffc01eecaa0) = -1 ENOENT (沒有此一檔案或目錄) symlink("127.0.1.1:+27168", "/tmp/firefox/lock") = 0 symlink("127.0.1.1:+27168", "/home/ssivart/.mozilla/firefox/pj3lhq4z.default-release/lock") = -1 EEXIST (檔案已存在) readlink("/home/ssivart/.mozilla/firefox/pj3lhq4z.default-release/lock", "127.0.1.1:+26662", 1023) = 16 unlink("/home/ssivart/.mozilla/firefox/pj3lhq4z.default-release/lock") = 0 symlink("127.0.1.1:+27168", "/home/ssivart/.mozilla/firefox/pj3lhq4z.default-release/lock") = 0 unlink("/tmp/firefox/lock") = 0 |
須注意的是鎖定文件不見得總是以 lock 來命名,以及當瀏覽器提前死亡時,刪除鎖定文件的 unlock() 不會被執行
進程帳(process accounting)
每當一個進程完成時,內核會寫入一個簡潔的二進制紀錄到帳目文件裡,例如 /var/adm/pacct 或 /var/account/pacct
帳目轉為文字數據流之前,必須先做處理,或用特定工具讀取:acct
、acctcom
、lastcom
、sa
範例:找出系統有的進程帳工具
1 2 3 4 |
$ apropos accounting acct (2) - switch process accounting on or off acct (5) - process accounting file |
/proc 文件系統
要訪問內核裡的進程數據,與其透過無數次系統調用不斷更新,在特定的 UNIX 版本(尤其是 GNU/Linux),可以直接透過一個特殊文件來訪問——也就是 /proc 目錄
透過文件取得進程數據是很方便的,就算缺乏系統調用的界面,也可以透過程式語言來取得
/proc 中的文件,大小皆為 0,其數據並非真的存在於儲存設備中(只是作為界面)
範例:列出及讀取特定進程的 proc 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ ls /proc/3396 arch_status cgroup coredump_filter environ gid_map map_files mounts numa_maps pagemap root setgroups stat task uid_map attr clear_refs cpu_resctrl_groups exe io maps mountstats oom_adj patch_state sched smaps statm timens_offsets wchan autogroup cmdline cpuset fd limits mem net oom_score personality schedstat smaps_rollup status timers auxv comm cwd fdinfo loginuid mountinfo ns oom_score_adj projid_map sessionid stack syscall timerslack_ns ~$ cat -v /proc/3396/io rchar: 2352021493 wchar: 1275872543 syscr: 3109830 syscw: 3384371 read_bytes: 640823296 write_bytes: 2239549440 cancelled_write_bytes: 10268672 |