Linux進程控制
1. 進程終止
1.1. 進程終止的本質是回收資源
1.1 釋放資源
- 內存資源:
- 釋放進程的地址空間(
mm_struct
),包括代碼段、數據段、堆、棧等,通過寫時復制(CoW)共享的頁會減少引用計數,若計數為 0 則釋放物理內存。 - 銷毀頁表、頁目錄等內存管理結構。
- 釋放進程的地址空間(
- 文件資源:
- 關閉所有打開的文件描述符(File Descriptor),對應文件對象(
struct file
)的引用計數減 1,若計數為 0 則釋放文件對象(關閉文件)。 - 斷開與管道、socket 等 IPC 資源的關聯,清理相關內核結構。
- 關閉所有打開的文件描述符(File Descriptor),對應文件對象(
- 其他資源:
- 釋放信號處理表、進程定時器、進程間通信(IPC)資源(如共享內存、信號量)等。
- 從所屬進程組、會話中移除,更新進程組和會話的狀態。
1.2 處理進程狀態
- 進程狀態從運行態 / 阻塞態等轉為終止態(TASK_DEAD),不再參與調度。
- 保存進程的退出狀態(
exit_status
),包括終止原因(正常返回值或信號編號),供父進程查詢。
1.3 處理父進程與子進程的關系
- 通知父進程:通過信號
SIGCHLD
通知父進程 “子進程已終止”,父進程可通過wait()
或waitpid()
系列函數獲取子進程的退出狀態。 - 僵尸進程(Zombie Process)的產生:若父進程未及時調用
wait()
回收子進程,子進程的 PCB(進程控制塊)會暫時保留(僅釋放大部分資源),成為僵尸進程(狀態為Z
),直到父進程回收或父進程退出。 - 孤兒進程(Orphan Process)的處理:若父進程先于子進程終止,子進程會被init 進程(PID=1,或 systemd 等現代初始化進程)收養,init 進程會負責調用
wait()
回收孤兒進程,避免其成為僵尸進程。
1.2 進程退出的三種情況
-
代碼運行完畢,結果正確。
-
代碼運行完畢,結果不正確。
-
代碼異常終止。
1.3 進程常見退出方法
-
main函數返回值。
-
調用exit函數。
可以通過 echo $? 查看最近一個退出進程的退出碼。
退出碼0代表代碼運行完畢,結果正確;非0代表代碼運行完畢,結果不正確。
代碼異常終止是通過操作系統發送信號終止的。
2. 進程等待
2.1 進程等待的必要性
-
防止僵尸進程問題,進而造成內存泄漏。
-
方便父進程管理子進程,通過進程等待可以知道交給子進程的任務完成的怎么樣。
2.2 進程等待的系統調用
2.2.1 wait
等待任意一個子進程終止。
函數原型
#include<sys/types.h>
#include<sys/wait.h>pid_t wait(int* status);
參數
- status: 輸出型參數,獲取子進程退出狀態,不關心則可以設為
NULL
。
返回值
-
成功時:返回終止子進程的PID。
-
失敗時:返回 -1,并設置 errno。
2.2.2 waitpid
#include<sys/types.h>
#include<sys/wait.h>pid_t waitpid(pid_t pid , int *status , int options);
參數
-
pid:
-
pid > 0
: 等待進程ID等于pid的特定子進程。 -
pid = -1
: 等待任意子進程,等同于wait
。 -
pid = 0
: 等待與調用進程同進程組的任意子進程。 -
pid < -1
: 等于進程組ID等于pid絕對值的任意子進程。
-
-
status: 輸出型參數,獲取子進程退出狀態,不關心則可以設為
NULL
。 -
options:
-
默認為0,表示阻塞等待。
-
WNOHANG
: 非阻塞等待(通常配合循環進行使用)
-
返回值
-
成功時:返回終止子進程的PID。
-
失敗時:返回 -1,并設置 errno。
-
如果指定了
WNOHANG
且沒有子進程狀態發生改變,返回0。
2.2.3 輸出型參數status
-
傳遞 NULL,表示不關心子進程的退出狀態信息。
-
否則,操作系統會根據該參數,將子進程的退出信息反饋給父進程。
-
status不能簡單的當作整形來看待,可以當作位圖來看待。
-
可通過系統提供的宏解析status參數
-
WIFEXITED(status)
: 子進程正常終止為真。- 等價:
(status&0x7f) == 0
- 等價:
-
WEXITSTATUS(status)
: 獲取子進程的退出碼。- 等價:
(status >> 8)&0xff
- 等價:
-
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>int main () {pid_t id = fork();if (id == 0) {// child processint count = 5;while (count--) {printf("I am a child process , pid: %d , ppid: %d\n" , getpid() , getppid());sleep(1);}exit(100);} else if (id > 0) {// father processint status = 0;while (1) {pid_t res = waitpid(id , &status , WNOHANG);if (res > 0) {// if (WIFEXITED(status)) { // wifexitedif ((status&0x7f) == 0) { // printf("child process exit code: %d\n" , WEXITSTATUS(status)); // wexitstatusprintf("wait success , child process exit code: %d\n" , (status >> 8)&0xff); // wexitstatus} else {printf("signal code: %d\n" , status&0x7f);}break;} else if (res == 0) {printf("sleep 1s continuous wait\n");sleep(1);} else {perror("waitpid ");exit(1);}}} else {perror("fork ");}return 0;
}
3. 進程替換
3.1 概念
程序替換是指在進程運行過程中,替換當前進程的代碼和數據,使其執行另一個完全不同的程序,但進程ID(PID)保持不變。
-
被替換的部分
-
代碼段:新程序的代碼指令完全替換原程序的代碼指令。
-
數據段:包括初始化的全局變量、靜態變量等,被新程序的數據覆蓋。
-
堆和棧:原有堆和棧會被釋放,新程序會重新初始化自己的堆和棧結構。
-
內存映射:原進程通過
mmap
映射的共享庫或文件會被釋放,新程序會加載自己的共享庫和文件映射。
-
3.2 替換函數
#include <unistd.h>int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);int execve(const char *path, char *const argv[], char *const envp[]);
函數后綴含義
- l (list):參數以可變參數列表形式傳遞,最后一個參數必須是NULL。
-
v (vector):參數以字符串數組形式傳遞,數組最后一個元素必須是NULL。
-
p (path):在
PATH
環境變量指定的目錄中查找可執行文件。 -
e (environment):可以傳遞自定義的環境變量數組。
返回值
- exec系列函數不用返回值判斷,只要返回,就是失敗。
3.2.1 execl
execl("/bin/ls" , "ls" , "-l" , NULL);
3.2.2 execlp
execlp("ls" , "ls" , "-l" , NULL);
3.2.3 execle
char *env[] = { "PATH=/bin" , "USER=test" , NULL };
execle("/bin/ls", "ls", "-l", NULL, env);
3.2.4 execv
char *argv[] = {"ls" , "-l" , NULL};
execv("/bin/ls", argv);
3.2.5 execvp
char *argv[] = {"ls" , "-l" , NULL};
execvp("ls", argv);
3.2.6 execvpe
char *argv[] = {"ls", "-l", NULL};
char *env[] = { "PATH=/bin" , "USER=test" , NULL };
execvpe("ls", argv, env);