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命令(如ls
、touch
、rm
等)都是執行完就結束,也就是說該項命令被觸發後所產生的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
的按鍵。
接下來我們實際檢視一下如何使用top
與top
的介面。比如以下是我們輸入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
可見,top
與ps
的靜態結果輸出不同,top
這個程式可以持續地監測整個系統的程式任務狀態。在預設的情況下更新程式資源的時間為5秒,不過可以使用-d
來執行修改。top
主要分為兩部分介面,上面的介面為整個系統的資源使用狀態,基本上總共有六行。至於top
下半部分的畫面,則是每個程式使用的資源情況。
top
預設使用CPU使用率(%CPU
)作為排序的依據,如果你想要使用記憶體使用率排序,則可以按下M
鍵,若要恢復則按下P
鍵即可。如果想要退出top
,則按下q
。
參考
- [1] 鳥哥. 鳥哥的 Linux 私房菜: 基礎學習篇(第四版)[M]. 人民郵電出版社, 2018.
- [2] 《維基百科:守護程式》
- [3] 梅拉妮·米歇爾. 複雜[M]. 湖南科學技術出版社, 2018.