一、進程等待
1.1 進程等待必要性
- 子進程退出后,若父進程不管不顧,可能會產生 “僵尸進程”,進而造成內存泄漏。
- 進程一旦變為僵尸狀態,即使使用?
kill -9
?也無法將其殺死,因為無法殺死一個已死的進程。 - 父進程需要了解子進程的任務完成情況,比如子進程運行結束后結果是否正確,是否正常退出。
- 父進程通過進程等待的方式,回收子進程資源并獲取其退出信息。
1.2 進程等待的方法
wait()
?函數
wait()
?函數用于阻塞等待子進程的結束,并回收其資源。以下是一個簡單的示例代碼:
#include <sys/wait.h>
#include <stdio.h>int main() {pid_t child_pid = fork();if (child_pid == 0) {// 子進程執行任務exit(42); // 子進程退出碼42} else {int status;pid_t terminated_pid = wait(&status); // 阻塞等待if (WIFEXITED(status)) {printf("子進程 %d 退出碼: %d\n", terminated_pid, WEXITSTATUS(status));}}return 0;
}
waitpid()
?函數
waitpid()
?函數的原型為?pid_t waitpid(pid_t pid, int *status, int options);
,它可以更靈活地等待指定子進程的結束。
1.3 獲取子進程?status
wait
?和?waitpid
?都有一個?status
?參數,這是一個輸出型參數,由操作系統填充。若傳遞?NULL
,表示不關心子進程的退出狀態信息;否則,操作系統會根據該參數將子進程的退出信息反饋給父進程。
status
?不能簡單地當作整型來看待,可將其視為位圖,具體細節可參考下面的圖片(只研究?status
?低 16 比特位):
狀態位 | 說明 |
---|---|
低8位(0-7) | 子進程退出碼 (正常終止時有效) |
第8位(8-15) | 信號編號 (被信號終止時有效) |
其他標志位 | 通過宏檢測狀態類型(如WIFEXITED) |
1.4 阻塞和非阻塞
核心結論
-
本質區別:
-
阻塞(Blocking):調用者線程暫停執行,直到操作完成(如數據到達、資源就緒)。
-
非阻塞(Non-blocking):調用立即返回,無論操作是否完成,需通過輪詢或事件通知獲取結果。
-
-
選擇依據:
-
阻塞:適合簡單邏輯、單任務場景,代碼直觀但資源利用率低。
-
非阻塞:適合高并發、實時響應需求,需配合多路復用(如
epoll
)或異步通知(如回調)
-
深度解析
運行機制對比
特性 | 阻塞模式 | 非阻塞模式 |
---|---|---|
線程狀態 | 掛起(Sleeping) | 持續運行(Running) |
CPU 占用 | 低(等待時不消耗 CPU) | 高(需輪詢檢查狀態) |
響應延遲 | 操作完成后立即響應 | 需主動檢測或等待通知 |
代碼復雜度 | 低(線性執行) | 高(需處理中間狀態和錯誤碼) |
典型應用場景
以下是阻塞和非阻塞模式的典型應用代碼示例:
阻塞模式示例:
#include <stdio.h>
#include <fcntl.h>int main() {int fd = open("file.txt", O_RDONLY); char buf[1024]; read(fd, buf, sizeof(buf)); // 阻塞直到數據就緒 printf("Data: %s\n", buf); return 0;
}
非阻塞模式示例:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>#define BUFFER_SIZE 1024int main() {int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = INADDR_ANY;connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); char data[BUFFER_SIZE] = "Hello, Server!";int len = strlen(data);while (1) { if (send(sockfd, data, len, MSG_DONTWAIT) == -1) { if (errno == EAGAIN) { usleep(1000); // 數據未就緒,短暫等待后重試 continue; } } break; } close(sockfd);return 0;
}
二、進程程序替換
2.1 替換原理
使用?fork
?創建子進程后,子進程執行的是與父進程相同的程序(但可能執行不同的代碼分支)。若子進程想執行一個全新的程序,可通過進程的程序替換來實現。當進程調用一種?exec
?函數時,該進程的用戶空間代碼和數據會完全被新程序替換,并從新程序的啟動例程開始執行。調用?exec
?并不會創建新進程,因此調用前后該進程的 ID 不會改變。
2.2 替換函數
函數名 | 參數傳遞方式 | PATH 搜索 | 環境變量 | 典型用途 |
---|---|---|---|---|
execl | 參數列表(可變參數) | 否 | 繼承當前環境 | 已知絕對路徑的固定參數調用 |
execv | 參數數組(char *[] ) | 否 | 繼承當前環境 | 動態構建參數的固定路徑調用 |
execlp | 參數列表 | 是 | 繼承當前環境 | 執行 PATH 中的命令(如 Shell) |
execvp | 參數數組 | 是 | 繼承當前環境 | 動態執行 PATH 中的命令 |
execle | 參數列表 | 否 | 自定義環境變量 | 需嚴格控制環境的場景 |
execvpe | 參數數組 | 是 | 自定義環境變量 | 動態參數 + 自定義環境 |
2.3 函數解釋
2.4 命名理解
l(list)
:表示參數采用列表形式。v(vector)
:參數使用數組。p(path)
:有?p
?則自動搜索環境變量?PATH
。e(env)
:表示自己維護環境變量。
以下是調用示例:
#include <unistd.h>
#include <stdio.h>int main() {char *const argv[] = {"ps", "-ef", NULL};char *const envp[] = {"PATH=/bin:/usr/bin", "TERM=console", NULL};execl("/bin/ps", "ps", "-ef", NULL);// 帶 p 的,可以使用環境變量 PATH,無需寫全路徑execlp("ps", "ps", "-ef", NULL);// 帶 e 的,需要自己組裝環境變量execle("ps", "ps", "-ef", NULL, envp);execv("/bin/ps", argv);// 帶 p 的,可以使用環境變量 PATH,無需寫全路徑execvp("ps", argv);// 帶 e 的,需要自己組裝環境變量execve("/bin/ps", argv, envp);// 如果 exec 調用失敗,會執行到這里perror("exec 調用失敗");return 1;
}