Linux:進程模型和進程管理
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這個詞來描述后臺進程,它們不知疲倦地處理系統中的雜務?!盪nix系統繼承了這個術語。作為一種在后臺起作用的超自然存在,麥克斯韋妖與古希臘神話中的代蒙一致[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.

在Linux系統中,執行一個程序或命令就可以觸發一個進程,系統會給予這個進程一個ID,稱為PID,同時根據觸發這個進程的用戶與相關屬性關系,基于這個PID一組有效的權限設置。舉個常見的例子,我們要操作系統的時候通常是利用ssh連接程序或直接在主機上登錄,然后獲取shell。默認的shell是bash,對應的路徑為/bin/bash,那么同時間的每個人登錄都是執行/bin/bash,不過每個人獲取的權限不同。
浙公網安備 33010602011771號