Linux操作系統基礎篇:深度解析進程的常用命令
在日常生活中,你是否常常一邊聽著音樂,一邊編輯文檔,還時不時切換到瀏覽器查閱資料?在計算機的世界里,Linux 系統也在進行著類似的 “多任務” 操作,而這一切的背后,離不開 “進程” 這個關鍵角色。進程,簡單來說,就是正在運行的程序實例。當你在 Linux 系統中啟動一個程序,比如打開文本編輯器 Vim,系統就會為這個程序創建一個進程,分配必要的資源,如內存、CPU 時間等,讓它能夠在系統中 “活躍” 起來。這里要注意區分程序(Program)和進程(Process)。
程序是存儲在磁盤上的可執行文件,它是靜態的,就像一本寫滿指令的 “說明書”,靜靜地等待被執行;而進程則是程序的動態執行過程,它有自己的生命周期,從創建、運行到最終結束,就像一個充滿活力的 “執行者”,按照程序的指令在系統中穿梭。進程的動態性體現在它會隨著程序的執行而不斷變化狀態。在執行過程中,進程可能因為等待輸入輸出、獲取資源等原因暫停,也可能在得到 CPU 的調度后繼續運行 ,這種動態變化使得 Linux 系統能夠高效地管理和調度多個任務。
一、進程控制塊(PCB)
在 Linux 系統中,進程的管理離不開進程控制塊(Process Control Block,簡稱 PCB),它就像是進程的 “秘密檔案”,記錄了進程的各種關鍵信息,是操作系統對進程進行管理和調度的重要依據。
當一個程序被加載到內存成為一個真正的進程時,操作系統會創建一個 PCB 來描述它。PCB 中存儲了進程的標識符、狀態、優先級、內存指針、程序計數器、I/O 狀態信息等。以進程標識符(PID)來說,它是唯一標識一個進程的數字,就像我們每個人的身份證號碼一樣,操作系統通過 PID 來識別和管理不同的進程 。進程狀態則記錄了進程當前是處于運行、就緒、等待等哪種狀態,以此決定進程的調度順序。
除了這些核心字段,PCB 中還有一些其他字段也發揮著重要作用。比如優先級字段,它決定了進程在競爭 CPU 資源時的優先程度,優先級高的進程更容易獲得 CPU 時間,從而優先執行;內存指針字段,它指向進程的代碼段、數據段和棧段,讓操作系統清楚進程的內存布局,便于進行內存管理 。
在 Linux 中,PCB 是通過task_struct結構體來實現的。這個結構體定義在 Linux 內核代碼中,包含了與進程相關的所有信息,是內核進行進程管理和調度的核心。如果你對task_struct結構體的具體內容感興趣,可以通過查看 Linux 內核源代碼來一探究竟 。
通過一些命令,我們可以查看進程的相關信息,從而了解 PCB 中的部分內容。比如使用ps -ef命令,可以查看當前系統中所有進程的詳細信息,包括進程 ID、父進程 ID、用戶、CPU 使用率、內存使用率等;top命令則提供了一個交互式的進程狀態監視界面,能實時顯示進程的 CPU 使用率、內存使用率等動態信息 ,讓你對進程的運行狀態一目了然。
二、Linux進程的創建
在 Linux 系統中,創建進程是一個常見且重要的操作,而fork()函數就是實現這一操作的關鍵 “魔法” 函數。
fork()函數的作用是創建一個子進程,這個子進程是父進程的一個副本。當fork()函數被調用時,操作系統會為子進程分配一個新的進程控制塊(PCB),并復制父進程的大部分資源,包括代碼段、數據段、堆、棧等 。這里的復制采用了 “寫時拷貝”(Copy-On-Write,簡稱 COW)技術,也就是說,在子進程創建之初,父子進程共享相同的物理內存頁面,只有當其中一個進程試圖修改數據時,才會真正復制一份數據到新的物理內存頁面,這樣可以節省內存資源,提高創建進程的效率 。
在代碼層面,fork()函數調用一次會返回兩次,在父進程中返回子進程的進程 ID(PID),而在子進程中返回 0 。通過判斷fork()函數的返回值,我們可以區分父子進程,從而讓它們執行不同的代碼邏輯。下面是一個簡單的示例代碼:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
// 創建子進程失敗
perror("fork error");
return 1;
} else if (pid == 0) {
// 子進程
printf("I am the child process, my PID is %d, my parent's PID is %d\n", getpid(), getppid());
} else {
// 父進程
printf("I am the parent process, my PID is %d, and I just created a child with PID %d\n", getpid(), pid);
}
return 0;
}
在這個示例中,fork()函數創建了一個子進程。父進程和子進程都會繼續執行fork()函數之后的代碼,通過判斷pid的值,我們可以讓父子進程分別打印出不同的信息,展示它們的身份和關系 。
在父子進程共享代碼和數據方面,雖然它們在創建之初共享相同的物理內存頁面,但這并不意味著它們的數據是完全共享的。當其中一個進程對數據進行寫操作時,由于寫時拷貝機制,會為該進程分配新的物理內存頁面來存儲修改后的數據,而不會影響另一個進程的數據 。例如,父子進程都有一個全局變量count,如果子進程修改了count的值,父進程中的count值并不會改變 ,這體現了進程之間數據的獨立性。
寫時拷貝技術在進程創建中有著顯著的優勢。它避免了在進程創建時對大量數據的不必要復制,大大提高了進程創建的速度,減少了內存的占用 。尤其是在創建大量進程或者復制大量數據的場景下,寫時拷貝技術能夠顯著提升系統的性能和資源利用率 。比如在服務器應用中,可能需要頻繁創建子進程來處理客戶端的請求,使用寫時拷貝技術可以讓服務器更高效地響應請求,減少系統開銷 。
除了fork()函數,exec系列函數也是進程創建和控制中的重要角色,它們的作用是用一個新的程序替換當前進程的內存空間,包括代碼段、數據段、堆和棧等 。也就是說,當一個進程調用exec系列函數時,它會放棄當前正在執行的程序,轉而執行一個新的程序 。
exec系列函數有多個變體,如execl、execv、execlp、execvp、execle、execvpe等 ,它們的主要區別在于參數的傳遞方式和對環境變量的處理。以execl函數為例,它的函數原型為int execl(const char *path, const char *arg, ...);,其中path是要執行的程序的路徑,arg是傳遞給程序的參數列表,參數列表以NULL結尾 。而execlp函數的原型為int execlp(const char *file, const char *arg, ...);,它會根據環境變量PATH來查找要執行的程序,不需要指定完整的路徑 。
在實際應用中,fork()和exec()常常結合使用 。比如在 Shell 中,當我們輸入一個命令時,Shell 會先調用fork()創建一個子進程,然后子進程調用exec()函數來執行用戶輸入的命令 。這樣可以保證 Shell 進程在執行命令的過程中不會被阻塞,能夠繼續接受用戶的輸入 。下面是一個簡單的示例代碼,展示了fork()和exec()的結合使用:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork error");
exit(1);
} else if (pid == 0) {
// 子進程
execlp("ls", "ls", "-l", NULL);
// 如果exec執行失敗,會執行到這里
perror("exec error");
exit(1);
} else {
// 父進程
wait(NULL); // 等待子進程結束
printf("Child process has finished.\n");
}
return 0;
}
在這個示例中,父進程調用fork()創建子進程,子進程調用execlp()函數執行ls -l命令,列出當前目錄下的文件列表 。如果exec執行成功,子進程的內存空間會被ls程序替換,不會再返回原來的代碼;如果執行失敗,會打印錯誤信息并退出 。父進程通過wait(NULL)等待子進程結束,然后打印提示信息 。
三、Linux進程的前臺、后臺與守護進程
在 Linux 系統中,進程有著不同的 “生活方式”,其中前臺進程、后臺進程和守護進程是最為常見的三種類型,它們在運行特性、與用戶交互方式以及應用場景等方面都有著明顯的區別。
3.1 前臺進程:與用戶直接互動的 “活躍分子”
前臺進程是與用戶直接交互的進程,它會獨占當前的終端(Terminal)。當你在終端中輸入一個命令并直接執行,如ls -l,這就啟動了一個前臺進程。在這個進程運行期間,終端會被它占用,你無法在終端中輸入其他命令,直到該進程執行完畢或被你手動終止 。比如,當你運行一個需要用戶不斷輸入數據的程序時,它就是前臺進程,你必須專注于與這個進程交互,等待它的響應 。
3.2 后臺進程:默默工作的 “幕后英雄”
后臺進程則是在后臺運行的進程,它不會占用終端的輸入輸出,你可以在啟動后臺進程后繼續在終端中執行其他命令 。在命令行尾加上 “&” 符號,就可以將一個命令放到后臺執行 。例如,如果你要運行一個耗時較長的腳本test.sh,不想讓它阻塞終端,可以使用./test.sh &的方式啟動它 。
后臺進程繼承當前會話(Session)的標準輸出(stdout)和標準錯誤(stderr),所以它的輸出依然會同步地在命令行下顯示,但它不再繼承當前會話的標準輸入(stdin),你無法向這個任務輸入指令 。如果你想查看當前終端后臺運行的任務,可以使用 jobs 命令;若要將后臺中的命令調至前臺繼續運行,使用 fg 命令;而 bg 命令則可以將一個在后臺暫停的命令,變成在后臺繼續執行 。
3.3 守護進程:系統穩定運行的 “忠誠衛士”
守護進程(Daemon)是一種特殊的后臺進程,它完全脫離控制終端和會話,在系統后臺默默地運行,不受用戶登錄和注銷的影響 。它的主要特點包括無控制終端,避免受到終端的干擾;不占用前端資源,允許正常執行其他 bash 命令 。Linux 系統的大多數服務器就是用守護進程實現的,比如 Internet 服務器 inetd、Web 服務器 httpd、郵件服務器 sendmail、數據庫服務器 mysqld 等 。
這些守護進程在系統啟動時就開始運行,除非強行終止,否則會一直運行到系統關機 。守護進程一般以 root 用戶權限運行,因為它們需要使用某些特殊的端口(1 - 1024)或者資源 。而且,守護進程的父進程一般都是 init 進程,它是非交互式程序,沒有控制終端,所以任何輸出都需要特殊處理,通常會將標準輸入、輸出、錯誤重定向到/dev/null(黑洞文件)或者日志文件 。
為了讓進程變成守護進程,可以使用nohup命令,它可以在你退出帳戶 / 關閉終端之后繼續運行相應的進程 。例如,nohup ./test.sh > a.txt 2>&1 &,這個命令將test.sh腳本以守護進程的方式運行,輸出重定向到a.txt文件 。也可以在代碼層面通過調用daemon函數或者手動實現一系列步驟,如創建子進程、調用setsid創建新會話、改變工作目錄、重設文件權限掩碼、關閉不需要的文件描述符等 。
以 Web 服務器httpd為例,它作為守護進程,在系統后臺持續運行,監聽特定的端口(如 80 或 443),等待客戶端(如瀏覽器)的請求 。當收到請求時,它會處理請求并返回相應的網頁內容,為用戶提供 Web 服務 ,整個過程無需用戶手動干預,也不會受到終端操作的影響 。再比如系統日志進程syslogd,它負責記錄系統的各種日志信息,從系統啟動開始就一直默默運行,不斷地將系統產生的日志消息記錄到指定的日志文件中,為系統的維護和故障排查提供重要依據 。
四、Linux進程管理常用命令
在 Linux 系統中,熟練掌握進程管理命令是高效運維和開發的關鍵。這些命令就像是一把把 “瑞士軍刀”,在監控和控制進程時發揮著不可或缺的作用。下面,我們就來深入了解一些常用的進程管理命令 。
4.1 ps:進程狀態查看利器
ps(Process Status)命令用于查看當前系統中運行的進程狀態,它就像是進程世界的 “攝影師”,能為我們拍攝進程的 “快照”,提供有關進程的詳細信息 。
該命令將顯示進程的詳細信息,例如進程 ID、占用 CPU 的百分比、進程的狀態、運行時間等等。
圖片
常用選項及示例:
-ef:以完整格式顯示所有進程信息,包括用戶 ID(UID)、進程 ID(PID)、父進程 ID(PPID)、CPU 使用率(C)、啟動時間(STIME)、終端設備(TTY)、進程運行所占的 CPU 時間(TIME)以及啟動該進程的命令(CMD)等 。例如,執行ps -ef,會得到類似如下的輸出:
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:30? 00:00:01 /sbin/init
user 101 1 0 09:00 pts/0 00:00:00 bash
-aux:顯示所有用戶的進程,包括用戶(USER)、PID、CPU 使用率(% CPU)、內存使用率(% MEM)、虛擬內存大小(VSZ)、常駐內存大小(RSS)、終端設備(TTY)、進程狀態(STAT)、啟動時間(START)、進程使用的 CPU 時間(TIME)以及啟動進程的命令(COMMAND)等 。例如,ps -aux的輸出如下:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 20016 1212? Ss 08:30 0:00:01 /sbin/init
user 101 0.1 0.5 12288 5124 pts/0 R+ 09:00 0:00:00 bash
- -ef和-aux的區別在于,-ef是 UNIX 風格的選項,更關注進程的關系(如 PPID 和 UID);而-aux是 BSD 風格的選項,更側重于進程的資源使用(如 % CPU 和 % MEM) 。
- -C:根據命令名稱查找進程,例如ps -C firefox,可以查找出firefox進程的相關信息 。
- -p:根據進程 ID 查找進程,如ps -p 1234,可以查看進程 ID 為 1234 的進程狀態 。
4.2 top:實時監控的動態儀表盤
top命令是 Linux 系統中常用的實時系統監控工具,它就像是一個動態的 “儀表盤”,能夠實時展示系統中各個進程的資源占用狀況,類似于 Windows 的任務管理器 。
該命令將顯示進程的詳細信息,例如進程 ID、占用 CPU 的百分比、進程的狀態、運行時間等等。您還可以使用 top 命令來查看進程的資源使用情況,例如 CPU、內存和 I/O。
圖片
主要功能和用途:
- 實時監控:提供一個實時的、動態的視圖,展示系統當前的狀態,包括系統運行時間、登錄用戶數、平均負載等 。平均負載是指在特定時間間隔內,系統處于運行狀態和不可中斷狀態的平均進程數,是衡量系統負載的重要指標 。
- 進程管理:允許用戶查看系統中各個進程的運行狀態,包括PID、用戶、優先級、虛擬內存使用量、物理內存使用量、共享內存量、狀態(如運行、睡眠、停止等)、CPU使用率、內存使用率、運行時間以及命令行名稱等 。用戶可以通過交互命令對進程進行排序、殺死、調整優先級等操作 。
- 資源監控:監控 CPU 的總體使用率、用戶空間占用率、系統空間占用率等;展示物理內存和交換空間(swap)的使用情況,包括總量、已用量、空閑量等 。
基本用法:在終端中輸入top命令并回車,即可啟動top程序。默認情況下,它會顯示系統中所有進程的列表,并按照 CPU 使用率進行排序 。
常用選項:
- -u:僅顯示指定用戶的進程,如top -u root,只顯示root用戶的進程 。
- -n:指定top命令更新的次數,之后自動退出,例如top -n 5,表示更新 5 次后退出 。
- -d:設置屏幕更新的間隔時間,默認為 3 秒,如top -d 5,表示每 5 秒更新一次屏幕 。
- -b:以批處理模式運行,通常與重定向結合使用,將輸出保存到文件中,例如top -b -n 1 > top_output.txt,將單次更新的數據快照保存到top_output.txt文件中 。
- -H:以線程模式顯示,顯示每個線程的詳細信息,而非僅顯示進程 。
交互命令:在 top 運行時,用戶可以通過一系列交互命令來改變顯示的內容或排序方式 。例如,按P鍵以 CPU 使用率排序;按 M 鍵以內存使用率排序;按T鍵以時間 / 累計時間排序;按 f 或 F 鍵進入字段管理界面,允許用戶自定義顯示的字段;按k鍵殺死一個進程,需要輸入進程的 PID 和信號;按r鍵重新設定進程的優先級;按 q 鍵退出top 。
4.3 htop:更強大的交互式監控工具
htop是一款強大的交互式系統監控工具,它在top的基礎上進行了增強,提供了更直觀的界面和更豐富的操作功能 。該命令將顯示進程的詳細信息,例如進程 ID、占用 CPU 的百分比、進程的狀態、運行時間等等。您可以使用 htop 命令來查看進程的資源使用情況,例如 CPU、內存和 I/O,并且可以使用鍵盤快捷鍵來進行交互式操作。
圖片
特點和優勢:
- 友好的界面:使用彩色顯示,信息一目了然,支持鼠標操作,操作更加便捷 。
- 詳細的進程信息:不僅顯示進程基本信息,還展示每個進程完整命令行,方便用戶了解進程的具體執行情況 。
- 強大的交互功能:提供了更多的快捷鍵操作,例如按F1鍵顯示幫助頁面;按F2鍵進入設置菜單;按F3鍵搜索進程;按F4鍵過濾進程;按F5鍵以樹狀視圖顯示進程層次結構;按F6鍵選擇不同的排序方式,如按 CPU 使用率、內存使用率等;按F7和F8鍵增加或減少進程的nice值;按F9鍵殺死進程;按F10鍵退出htop 。
顯示界面:htop命令顯示的界面主要由標題欄、進程列表、柱狀圖區域和快捷鍵提示欄四個部分組成 。標題欄位于界面的頂部,顯示系統的整體狀態,包括 CPU 使用率、內存占用、進程數等;進程列表位于界面的主要部分,顯示當前運行的進程及其相關信息;柱狀圖區域位于界面的左側或右側或頂部,以柱狀圖的形式展示系統資源的使用情況,如 CPU 使用率、內存占用、磁盤讀寫等;快捷鍵提示欄位于界面的底部,顯示常用的快捷鍵操作,幫助用戶快速了解和使用htop的功能 。
常用選項:
- -d:設置更新之間的延遲,例如htop -d 5,表示屏幕更新之間的延遲為 5 秒 。
- -u:僅顯示用戶擁有的進程,如htop -u user,只顯示user用戶的進程 。
- -p:僅顯示具有特定 ID 的進程,如htop -p 1234,只顯示進程 ID 為 1234 的進程 。
- -s:對給定列的進程進行排序,如htop -s %CPU,按 CPU 使用率對進程進行排序 。
- -t:在命令列的樹視圖中顯示進程層次結構 。
- --no-color:在單色模式下打開htop,禁用顏色 。
4.4 pgrep:精準查找進程 ID
pgrep是一個用于根據名稱、用戶、組和其他標準搜索進程的實用程序,它能幫助我們快速找到匹配給定模式的運行進程的進程 ID(PID) 。
常用選項及示例:
- -u:查找特定用戶擁有的進程,例如pgrep -u root,查找root用戶擁有的所有進程 。
- -g:查找特定組中的進程,如pgrep -g group_name,查找屬于group_name組的進程 。
- -P:查找給定父 PID 的子進程,例如pgrep -P 1234,查找父進程 ID 為 1234 的所有子進程 。
- -f:與完整命令行進行匹配(不僅僅是進程名稱),如pgrep -f "python app.py",查找執行python app.py命令的進程 。
- -l:顯示進程名稱及其 PID,例如pgrep -l bash,輸出結果中會同時顯示進程 ID 和進程名稱 。
- -o:僅返回第一個匹配的進程,如pgrep -o firefox,只返回第一個匹配的firefox進程的 ID 。
- -v:反向搜索(返回與模式不匹配的進程),例如pgrep -v -f "bash",返回所有不是執行bash命令的進程 。
- -c:打印出匹配的數量,如pgrep -c java,輸出當前系統中java進程的數量 。
- -x:完全匹配(即匹配命令的全名),例如pgrep -x sshd,只匹配進程名為sshd的進程,而不會匹配包含sshd的其他進程名 。
- -d:指定 PID 的分隔符,默認是換行符,如pgrep ssh -d' ',輸出的多個ssh進程 ID 之間用空格分隔 。
- -i:匹配時不區分大小寫,例如pgrep -i FIREFOX,可以匹配到firefox進程 。
- -n:僅選擇最新的(最近啟動的)匹配進程,如pgrep -n chrome,返回最近啟動的chrome進程的 ID 。
4.5 pkill:精準終止進程
pkill命令用于根據進程名稱或其他屬性終止進程,它結合了pgrep和kill的功能,能夠更方便地終止符合條件的進程 。
常用選項及示例:
- -9:強制殺死進程,這是最常用的選項之一,用于終止那些難以正常結束的進程 。例如,pkill -9 firefox,強制殺死所有firefox進程 。
- -15:默認信號,用于正常終止進程,嘗試讓進程優雅地關閉 。例如,pkill -15 apache2,正常終止所有apache2進程 。
- -u:指定運行用戶,例如pkill -9 -u redis redis-server,強制殺死redis用戶運行的redis-server進程 。
- -f:與完整命令行進行匹配,如pkill -f "python app.py",終止執行python app.py命令的進程 。
4.6 kill:靈活的進程信號發送
kill命令用于向進程發送信號,它可以讓我們對進程進行各種控制,如終止進程、暫停進程、恢復進程等 。
信號介紹:在 Linux 系統中,信號是一種異步事件通知機制,用于向進程傳遞各種事件或請求 。常見的信號有:
- SIGTERM(15):默認的終止信號,進程收到該信號后,會嘗試正常終止,清理資源等 。
- SIGKILL(9):強制終止信號,進程收到該信號后,會立即終止,不會進行任何清理操作,通常用于終止那些無法正常響應SIGTERM信號的進程 。
- SIGSTOP(17,19,23):暫停進程,進程收到該信號后,會停止運行,但不會釋放資源 。
- SIGCONT(18,20,25):恢復被暫停的進程,讓其繼續運行 。
使用示例:
- 要終止進程 ID 為 1234 的進程,可以使用kill 1234(默認發送SIGTERM信號),如果該進程沒有響應,可以使用kill -9 1234強制終止 。
- 要暫停進程 ID 為 5678 的進程,可以使用kill -STOP 5678;要恢復該進程,可以使用kill -CONT 5678 。
4.7 killall:按名稱批量終止進程
killall命令用于根據進程名稱終止一組進程,它可以一次性終止所有符合條件的進程,非常方便 。
使用示例:
- 要終止所有httpd進程,可以使用killall httpd,它會向所有名為httpd的進程發送SIGTERM信號,嘗試正常終止它們 。
- 如果需要強制終止,可以使用killall -9 httpd,向所有httpd進程發送SIGKILL信號 。
4.8 pgrep:強大的進程查找
pgrep命令是一個強大的進程查找工具,它可以根據各種條件查找進程 。除了前面提到的選項,還可以結合正則表達式進行更靈活的查找 。示例:
- 要查找所有命令行中包含apache的進程,可以使用pgrep -f apache 。
- 要查找當前用戶運行的所有bash進程,可以使用pgrep -u $USER bash 。
4.9 pidof:快速獲取進程 ID
pidof命令用于快速獲取指定進程名稱的進程 ID,它的輸出非常簡潔,只包含進程 ID 。
使用示例:要獲取nginx進程的 ID,可以使用pidof nginx,如果系統中有多個nginx進程,它會輸出所有進程的 ID,以空格分隔 。
4.10 nice和renice:調整進程優先級
在 Linux 系統中,進程的優先級決定了它在競爭 CPU 資源時的優先程度 。nice和renice命令用于調整進程的優先級,讓我們可以根據實際需求合理分配系統資源 。
- nice 命令:nice命令用于以指定的優先級啟動一個新進程 。優先級的范圍是 - 20(最高優先級)到 19(最低優先級),默認優先級是 0 。例如,要以較高的優先級(-5)啟動python程序,可以使用nice -n -5 python my_script.py。這里,-n選項用于指定優先級 。
- renice 命令:renice命令用于調整已經運行的進程的優先級 。例如,要將進程 ID 為 1234 的進程優先級調整為 10,可以使用renice 10 1234 。如果要調整所有屬于user用戶的進程優先級為 5,可以使用renice 5 -u user 。