Classic Shell Scripting 讀書筆記 (七)

  • Devops

進程

程式 (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 多種信號類型

暫停進程

STOPTSTP 通常意指暫停進程,直到 CONT 訊號出現,要求繼續執行

STOP 訊號必定傳送,無法被程序抓取或忽略

未指定抓取的訊號,通常會引起中斷,例如 FPEPIPE;而 CHLD 為內核裡默認要被忽略的訊號,不會引發中斷

刪除進程

有四個相關的訊號:

  • TERM:常表示「快速清除並離開」
  • ABRT:類似 TERM,但目標在於產生內存影像的副本,將其至於核心,即 program.core 或 core.PID
  • HUP:常表示「先停止目前的工作,然後準備處理新的工作」,有重新啟動的意味
  • KILL:立刻終止進程

KILL 訊號必定傳送,無法被程序抓取或忽略

以慣例而言,應該先送出 HUP,讓進程有機會優雅地終止(gracefully shutdown)
,如果進程沒有馬上離開,則應該再送出 TERM,若還是無法離開,最後再使用 KILL

小心使用 kill 命令,當程序不成常終止時,可能會在文件系統留下殘餘數據,造成空間浪費,或導致下次程序運行發生錯誤。例如,daemon、郵件客戶端程序、文字編輯器、瀏覽器都會產生一個小型文件鎖(lock),紀錄程序正在運行。如果程序的第二個實例被啟動,而第一個實例還在執行時,第二個實例會偵測到已經存在的 lock,回報並終止程序

有些系統(GNU/Linux, Sun Solaris)提供 pgreppkill 命令,可以透過名稱而非 PID 來追蹤或刪除進程

抓取訊號

進程會向內核註冊那些它們想要處理的信號,在 Shell 中,trap 命令可以引發此註冊行為

EXIT 訊號是 Shell 額外提供給 trap 命令使用的,此訊號數值恆為 0,語句trap '...' EXIT ,代表語句在exit() 系統調用之前在所有其他訊號之後會被引用(包含腳本的正常終止)

抓取 EXIT 完成後,腳本的離開狀態 $? 的值一樣會被保留(除非在抓取過程中重設)

須注意 DEBUGERR 訊號的抓取行為在不同 Shell 版本之間可能會有不同,可移植性低

延遲進程

有四種延遲進程的情境:

  1. sleep / 延遲片刻:調度器忽略休眠中的進程,直到計時完成才喚醒進程;sleep 只耗用極少資源,且休眠幾乎不會干擾到其他進程
  2. at / 延遲至特定時間:例如 at 21:00 < command-file 代表延遲至下午九點執行,所有延遲的進程可以 atq 查詢
  3. batch / 為資源控制而延遲:將進程加入至某個批次處理的隊列中。由於功能過於簡化,針對順序的控制很少,此命令很少用到
  4. crontab / 指定時間執行:按照 crontab 文件的設置定時執行特定工作

追蹤進程的系統調用

在執行程序時,顯示每個系統調用及目標程序執行時的參數,稱為系統調用追蹤器(system call tracer),常見的工具有 ktraceparstracetracetruss

由於 Shell 沒有除蟲機制,使用系統調用追蹤器可以對輸出結果提供很有用的補充,追蹤的輸出可能很龐大,最好紀錄在文件中

監控文件訪問最常的追蹤方式是:在日誌尋找 access()、open()、stat()、unlink() 事件

在 GNU/Linux 上,使用 strace -e trace=file 可以減少日誌量

範例:在 GNU/Linux 追蹤 Bourne Shell 的系統調用

記得設置提示號變數 PS1,用以區別原始與被追蹤的 Shell

追蹤結果顯示 Shell 調用了 clone() 以啟動 /bin/pwd 進程,其輸出與下一個 wait4() 的追蹤輸出混合在一起。命令正常終止,且 Shell 收到 CHLD 訊號,指出子進程完成

範例:追蹤瀏覽器的系統調用

須注意的是鎖定文件不見得總是以 lock 來命名,以及當瀏覽器提前死亡時,刪除鎖定文件的 unlock() 不會被執行

進程帳(process accounting)

每當一個進程完成時,內核會寫入一個簡潔的二進制紀錄到帳目文件裡,例如 /var/adm/pacct 或 /var/account/pacct

帳目轉為文字數據流之前,必須先做處理,或用特定工具讀取:acctacctcomlastcomsa

範例:找出系統有的進程帳工具

/proc 文件系統

要訪問內核裡的進程數據,與其透過無數次系統調用不斷更新,在特定的 UNIX 版本(尤其是 GNU/Linux),可以直接透過一個特殊文件來訪問——也就是 /proc 目錄

透過文件取得進程數據是很方便的,就算缺乏系統調用的界面,也可以透過程式語言來取得

/proc 中的文件,大小皆為 0,其數據並非真的存在於儲存設備中(只是作為界面)

範例:列出及讀取特定進程的 proc 文件

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。