Linux:程序模型和程序管理

2023-04-12 06:00:28

1 程序與程式

在Linux系統中,執行一個程式或命令就可以觸發一個程序,系統會給予這個程序一個ID,稱為PID,同時根據觸發這個程序的使用者與相關屬性關係,基於這個PID一組有效的許可權設定。如下圖所示(圖片來自《鳥哥的Linux私房菜》[1]):

舉個常見的例子,我們要作業系統的時候通常是利用ssh連執行緒式或直接在主機上登入,然後獲取shell。預設的shell是bash,對應的路徑為/bin/bash,那麼同時間的每個人登入都是執行/bin/bash,不過每個人獲取的許可權不同,如下圖所示:

也就是說,當我們的登入並執行/bin/bash程式時,系統已經給了我們一個PID,這個PID就是根據登陸者的UID/GID(/etc/passwd)而來,而所謂使用者的許可權就是這個bash程序的許可權。當這個程序執行其它作業時,比如我們在shell中執行touch命令時,這個程序觸發出來的其它程序也會沿用這個程序的相關許可權

我們對程式與程序做一個總結:

  • 程式(program):通常為二進位制程式,儲存在儲存媒介中(如磁碟等),以物理檔案的形式存在。
  • 程序(process):程式被觸發後,執行者的許可權與屬性、程式的程式碼與所需資料等都會被載入到記憶體中,作業系統給予這個記憶體中的單元一個識別符號(PID),可以說程序就是一個正在執行中的程式

程序彼此之間是有關係的。從下圖來看,連續執行兩個bash後,第二個bash的父程序就是前一個bash,通過Parent PID(PPID)可獲取其父程序的PID:

此外,子程序可以獲取父程序的環境變數。

下面這個例子我們會展示子程序和父程序的關係。我們在當前的bash環境下,再觸發一次bash,並用ps -l命令檢視程序相關的輸出資訊,

bash-3.2$ ps -l
  UID   PID  PPID        F CPU PRI NI       SZ    RSS WCHAN     S     ADDR TTY           TIME CMD
  501  9892  9827     4006   0  31  0 408650672   1968 -      S           0 ttys001    0:00.01 /bin/bash
  501  9905  9892     4006   0  31  0 408657840   2736 -      S           0 ttys001    0:00.01 /bin/bash

可以看到第一個bash的PID和第二個bash的PPID都是9892,這是因為第二個bash是來自第一個所產生的。

很多常常會發現,「咦,我們們將有問題的程序關閉了(比如Ctrl+C殺掉),怎麼過一陣子它又自動產生了?而且新產生程序的PID還與原先不同這是怎麼回事呢?」如果不是crontab計劃任務的影響,那麼肯定有一個父程序存在,所以我們殺掉子程序後,父程序又會主動再生成一個。那怎麼辦呢?「擒賊先擒王」。我們用ps auxf找出那個父程序,然後將它殺掉即可(後文會提到)。

fork and exec:程序呼叫的流程

子程序和父程序之間的關係比較複雜,最大的複雜點在於程序之間的呼叫。Linux程式的呼叫通常稱為fork-and-exec流程。程序都會藉由父程序以複製(fork)的方式產生一個一模一樣的子程序,然後被複製出來的子程序再以exec的方式來執行時機要執行的程式碼,最終就成為一個子程序。整個流程有點像下面這張圖:

  • 系統先以fork的方式複製一個與父程序相同的臨時程序,這個程序與父程序唯一的差別就是PID不同,但這個臨時程序還會多一個PPID引數。
  • 然後臨時程序開始以exec的方式載入實際要執行的程序。以上圖來講,新的程式名稱為qqq,最終子程序的程序程式碼就會變成qqq了。

系統或網路服務:常駐在記憶體裡的程序

一般的Linux命令(如lstouchrm等)都是執行完就結束,也就是說該項命令被觸發後所產生的PID很快就會被終止,那麼有沒有一直在執行的程序呢?

當然有,我們把在後臺啟動並一直持續不斷地執行,也即常駐在記憶體當中的程序稱為守護行程(daemon)。常見的服務包括系統本身所需要的服務(例如crond、atd、rsyslogd等)和負責網路連線的服務(例如apache、named、postfix、vsftpd等)。網路服務比較有趣的地方在於,它會啟動一個可以複雜網路監聽的埠(port),以提供外部使用者端(client)的連線請求。

PS1:在Linux系統中,一般daemon型別的程序都會在檔名後面加上d。

PS2:「守護行程」這個概念由麻省理工學院MAC專案的程式設計師發明。費南多·柯巴託於1963年在MAC專案任務。根據他的說法,他的團隊最早採用daemon這個概念,其靈感來源於麥克斯韋妖——一種物理學和熱力學中虛構的媒介,能幫助排列分子。他對此表示:「我們別出心裁地開始使用daemon這個詞來描述後臺程序,它們不知疲倦地處理系統中的雜務。」Unix系統繼承了這個術語。作為一種在後臺起作用的超自然存在,麥克斯韋妖與古希臘神話中的代蒙一致[2]。關於麥克斯韋妖的更多有趣資訊可以參見梅拉妮·米歇爾的《複雜》[3]一書。

2 程序管理

想要檢視系統上正在執行中的程序,可以利用靜態的ps或者是動態的top命令,還可以利用pstree來檢視程序樹之間的關係。

ps:將某個時間點的程序執行情況擷取下來
ps aux可檢視系統中所有的程序(注意,沒有-號):

~/Orion-Orion # ps -aux                                                                                         root@qi
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4504    48 pts/0    Ss    2022   0:01 sh /root/start.sh
root         7  0.0  0.0  65520   420 ?        Ss    2022   0:07 /usr/sbin/sshd
root         8  0.0  0.0  20052   264 pts/0    S+    2022   0:00 /bin/bash
...
root     39717  0.1  0.0  93648  7608 ?        Ss   08:40   0:02 sshd: root@notty
root     39727  0.0  0.0   9752  2924 ?        Ss   08:40   0:00 bash
root     40140  0.5  0.0  95296  9220 ?        Rs   08:54   0:02 sshd: root@notty
root     40150  0.0  0.0   9752  2832 ?        Ss   08:54   0:00 bash

ps -l則可以僅檢視自己的bash相關的程序:

~/Orion-Orion # ps -l                                                                                           root@qi
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S     0 42331 41909  0  80   0 -  5015 wait   pts/312  00:00:00 bash
0 R     0 42379 42331  0  80   0 -  6920 -      pts/312  00:00:00 ps

我們可以看到,系統整體執行的程序是非常多的,但使用ps -l僅會列出與你的操作環境(bash)有關的程序,即最上層的父程序會是你自己的bash而沒有擴充套件到systemd(後續會介紹)這個程序中。我們接下來來看看ps -l顯示出來的資料有哪些?

  • F:表示這個程序標識(process flags),說明這個程序的許可權,常見號碼有:
    • 若為4表示此程序的許可權為root。
    • 若為表示此程序僅執行復制(fork)而沒有實際執行(exec)。
    • 若為0表示程序標識沒有設定。
  • S: 代表這個程序的狀態(STAT),主要的狀態有:
    • R(Running):該程序正在執行中(running) 或是可執行的(runnable)
    • S(Sleep):程序處於可被喚醒(signal)的睡眠狀態,也即所謂空閒狀態(idle)。這種狀態一般是程序主動進入的。
    • D:程序處於不可被喚醒的睡眠狀態,通常這個程序可能在等待I/O的情況(例如列印)。這種狀態一般是程序被動進入的。
    • T(Stopped)停止狀態,可能是在任務控制(後臺暫停)或跟蹤(traced)狀態。
    • Z(Zombie)殭屍狀態(即所謂defunct),程序已經終止但卻無法被刪除至記憶體外。
  • UID/PID/PPID:代表程序被該UID所擁有/程序的PID號碼/此程序的父程序PID號碼。
  • C:代表CPU使用率,單位為百分比。
  • PRI/NI:Priority/Nice的縮寫,代表此程序被CPU所執行的優先順序,數值越小代表該程序越快被CPU執行。詳細的PRI與NI將在下一小節說明。
  • ADDR/SZ/WCHAN:都與記憶體相關,ADDR是kernel function,指出該程序在記憶體的哪個部分,如果是個running的程序,一般就會顯示-;SZ代表此程序用掉多少記憶體;WCHAN表示目前進場是否執行,同樣的,若為-表示正在執行中。
  • TTY:登入者的終端位置,若為遠端登入則使用動態終端介面名稱(pts/n)。
  • TIME:使用CPU的時間,注意是程序實際花費CPU的時間,而不是執行時間。關於這兩個時間之間的區別可參見我的部落格《Python:對程式做效能分析及計時統計》
  • CMD:就是command的縮寫,表示觸發此程序的命令是什麼。

所以你看到的ps -l輸出資訊中,它說明的是:bash程序屬於UID為0的使用者,狀態為睡眠(Sleeping),之所以為睡眠,是因為它觸發了ps(狀態為Running)。此程序的PID是42331,執行優先順序為80,執行bash所獲取的終端介面為pts/0。執行狀態為等待(wait)。

接下來我們用ps auxf列出類似程序樹的程序顯示:

(base) root@qi:~/Orion-Orion# ps -auxf
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4504    48 pts/0    Ss    2022   0:01 sh /root/start.sh
root         7  0.0  0.0  65520   420 ?        Ss    2022   0:07 /usr/sbin/sshd
root     38677  0.0  0.0  93284  7412 ?        Ss   07:55   0:00  \_ sshd: root@notty
root     38687  0.0  0.0   9756  2824 ?        Ss   07:55   0:00  |   \_ bash
root     38765  0.0  0.0   4504  1712 ?        S    07:55   0:00  |       \_ sh /root/.vscode-server/bin/b7886d7461186a5
root     41898  0.1  0.0 661960 58144 ?        Sl   10:38   0:06  |       |       \_ /root/.vscode-server/bin/b7886d7461
root     42331  0.0  0.0  20060  3788 pts/312  S    10:40   0:00  |       |           \_ bash
root     43592  0.0  0.0  36276  3248 pts/312  R+   11:41   0:00  |       |                \_ ps -auxf
...
root     39717  0.0  0.0  93648  7608 ?        Ss   08:40   0:02  \_ sshd: root@notty
root     39727  0.0  0.0   9752  2924 ?        Ss   08:40   0:00  |   \_ bash
root     43493  0.0  0.0   4376   672 ?        S    11:40   0:00  |       \_ sleep 180
root     41362  0.0  0.0  93296  7360 ?        Ss   10:33   0:01  \_ sshd: root@notty
root     41372  0.0  0.0   9752  2824 ?        Ss   10:33   0:00      \_ bash
root     43492  0.0  0.0   4376   700 ?        S    11:39   0:00          \_ sleep 180
root         8  0.0  0.0  20052   264 pts/0    S+    2022   0:00 /bin/bash

因為我是用ssh網路連線進入伺服器來執行一些測試的,可以看出程序之間是相關性的。從上面的例子來看,我是通過sshd提供的網路服務獲取的一個程序,該程序提供bash給我使用,而我通過bash再去執行VSCode-Server服務啟動指令碼,以執行VSCode-Server服務程序,然後該程序再提供給我一個bash, 我通過這個bash再去執行ps auxf(瘋狂套娃哈哈哈)。

這裡說一個題外話,sshd程序是我們前面提到的deamon,是不能隨意殺掉的哦! 殺掉了不僅你會馬上斷開連線,下次再用ssh連你也連不上了。

除了f這個選項,我們還可以使用pstree來完全檢視這個程序樹。

(base) root@qi:~/Orion-Orion# pstree 
sh─┬─bash
   └─sshd─┬─sshd───bash─┬─sh───node─┬─node───12*[{node}]
          │             │           ├─node─┬─node───10*[{node}]
          │             │           │      ├─node───11*[{node}]
          │             │           │      ├─python───{python}
          │             │           │      └─11*[{node}]
          │             │           ├─node───11*[{node}]
          │             │           ├─node─┬─bash───pstree
          │             │           │      └─11*[{node}]
          │             │           └─10*[{node}]
          │             └─sleep
          └─2*[sshd───bash───sleep]      

除此之外,我們必須要知道的是殭屍(zombie) 程序是什麼?通常,造成殭屍程序的原因在於該程序應該已經執行完畢,或是應該要終止了,但該程序的父程序卻無法完整地將該程序結束掉,而造成該程序一直存在記憶體中。如果你發現在某個程序的CMD/COMMAND後面接上了defunct時,就代表該程序是殭屍程序,例如:

apache  8683  0.0  0.9 83384 9992 ?   Z  14:33   0:00 /usr/sbin/httpd <defunct>

系統不穩定的時候就容易造成所謂的殭屍程序,可能是因為程式寫得不好,或是使用者的操作習慣不良等所造成的。如果你發現系統中有很多殭屍程序時,記得要找出該程序的父程序,然後好好做個追蹤,好好進行主機的環境優化,看看有什麼地方需要改善,而不是直接將它kill掉。不然萬一它一直產生就麻煩了。

事實上,通常殭屍程序都已經無法管理,而直接交給 systemd 這個程序來負責,偏偏systemd是系統第一個執行的程序,它是所有程序的父程序。我們是無法殺掉該程序的(殺掉它,系統就死掉了),所以如果產生殭屍程序,而系統過了一陣子還沒有辦法通過核心非經常性的特殊處理來將該程序刪除時,那你只好通過reboot的方式來將該程序kill掉。

systemd是目前Linux系統上主要的系統守護行程管理工具,由於init一方面對於程序的管理是序列化的,容易出現阻塞情況,另一方面init也僅僅是執行啟動指令碼,並不能對服務本身進行更多的管理。所以最新系統(RedHat7,CentOS7,Ubuntu15…)大都由systemd取代了init作為預設的系統程序管理工具。

top:動態檢視程序的變化

相對於ps是選取一個時間點的程序狀態,top可以持續監測程序執行的狀態,使用方式如下:

top [-d 數位] | top [-bnp]

它的選項與引數如下:

  • -d:後面可以接秒數,就是整個程序介面更新的秒數,預設是5秒。
  • -b:以批次的方式執行top,還有更多的引數可以使用,通常會搭配資料重定向來將批次的結果輸出為檔案。
  • -n:與-b搭配,意義是需要執行幾次top的輸出結果。
  • -p:指定某些PID來執行檢視檢測。

top的執行過程中可以使用下列的按鍵命令:

  • ?:顯示在top中可以輸入的按鍵命令
  • P:以CPU的使用排序顯示。
  • M:以Memory的使用排序顯示。
  • N:以PID來排序。
  • T:由該程序使用的CPU時間累積(TIME+)排序
  • k:給予某個PID一個訊號(signal)。
  • r:給予某個PID重新制定一個nice值。
  • q:退出top的按鍵。

接下來我們實際檢視一下如何使用toptop的介面。比如以下是我們輸入top -d 2命令得到的結果,該命令表示每兩秒鐘更新一次top,檢視整體資訊。

top - 13:19:06 up 202 days,  5:00,  3 users,  load average: 89.81, 75.65, 68.67
Tasks:  74 total,   1 running,  73 sleeping,   0 stopped,   0 zombie
%Cpu(s): 32.2 us,  4.5 sy, 35.7 ni, 27.5 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st
KiB Mem : 52701926+total, 52750784 free, 49904712 used, 42436377+buff/cache
KiB Swap:  8388604 total,  7983868 free,   404736 used. 46639996+avail Mem 
    <==如果加入k或r時,就會有相關的字樣出現在這裡。
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                         
38775 root      20   0 1106596 236232  37596 S   1.0  0.0   1:28.68 node                                                            
12191 root      20   0 1662128  61256  11176 S   0.5  0.0 205:36.04 python                                                          
    1 root      20   0    4504     48      0 S   0.0  0.0   0:01.03 sh                                                              
    7 root      20   0   65520    420      4 S   0.0  0.0   0:07.19 sshd    
...
41787 root      20   0   20060   3700     32 S   0.0  0.0   0:00.01 bash                                                            
41898 root      20   0  968200  62276  33216 S   0.0  0.0   0:14.26 node  

可見,topps的靜態結果輸出不同,top這個程序可以持續地監測整個系統的程序任務狀態。在預設的情況下更新程序資源的時間為5秒,不過可以使用-d來執行修改。top主要分為兩部分介面,上面的介面為整個系統的資源使用狀態,基本上總共有六行。至於top下半部分的畫面,則是每個程序使用的資源情況。

top預設使用CPU使用率(%CPU)作為排序的依據,如果你想要使用記憶體使用率排序,則可以按下M鍵,若要恢復則按下P鍵即可。如果想要退出top,則按下q

參考