會計可以做網(wǎng)站么真實的網(wǎng)站制作
個人主頁:chian-ocean
文章專欄-Linux
前言:
當一個進程發(fā)起某種操作(如I/O請求、信號、鎖的獲取等),但該操作需要的資源暫時不可用時,進程會被操作系統(tǒng)掛起,進入“等待隊列”或“阻塞狀態(tài)”。在此期間,進程不占用CPU,但仍保留其內(nèi)存、文件描述符等資源
進程等待的必要性
僵尸進程的存在
僵尸進程的成因
- 當子進程終止后,它的退出狀態(tài)需要由父進程通過調(diào)用
wait()
或waitpid()
系統(tǒng)調(diào)用回收。 - 如果父進程未回收子進程的退出狀態(tài),子進程會以“僵尸進程”的形式保留在進程表中。
特征:
- 在 Linux 系統(tǒng)中,可以用
ps
命令查看,僵尸進程的狀態(tài)為Z
(Zombie)。 - 僵尸進程是操作系統(tǒng)保留的一個條目,主要用于父進程檢查子進程的退出狀態(tài)。
如下:
從圖片中可以看到一個典型的 僵尸進程 的現(xiàn)象:
- 進程
27864
被強制終止(kill -9 27864
),但它的父進程(27863
)沒有調(diào)用wait()
或waitpid()
來回收其子進程的退出狀態(tài)。 - 因此,
27864
被標記為<defunct>
狀態(tài),即僵尸進程。 ps
輸出的STAT
列中顯示Z+
,這是僵尸進程的狀態(tài)標識。
進程等待
進程等待是操作系統(tǒng)中一種重要的狀態(tài),指的是某個進程由于資源不足或條件未滿足,暫時無法繼續(xù)執(zhí)行而被掛起的現(xiàn)象。
- 使用
wait()
或waitpid()
回收子進程
wait ( )
參數(shù):
-
int *status:
- 用于保存子進程的狀態(tài)信息(如退出碼或終止信號)。
- 如果不需要獲取子進程狀態(tài),可以將其傳入
NULL
。
返回值:
- 成功:
- 返回已終止的子進程的 PID。
- 失敗:
- 返回
-1
,并設(shè)置errno
。 - 常見錯誤包括:
ECHILD
:當前進程沒有子進程。EINTR
:調(diào)用被信號中斷。
- 返回
wait()
的作用
- 阻塞父進程:
wait()
會阻塞父進程,直到任意一個子進程狀態(tài)發(fā)生變化(通常是終止)。
- 回收子進程資源:
- 子進程終止后,其資源仍然保留在系統(tǒng)中,直到父進程調(diào)用
wait()
或waitpid()
回收它。 - 如果父進程不調(diào)用
wait()
或waitpid()
,子進程會變成 僵尸進程。
- 子進程終止后,其資源仍然保留在系統(tǒng)中,直到父進程調(diào)用
示例:
#include<iostream>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;void childtast()
{for(int i = 0; i < 10; i++) // 循環(huán)打印從 0 到 9 的數(shù)字{cout << i << endl; // 輸出當前的循環(huán)變量 i}sleep(3); // 睡眠 3 秒,模擬子進程的運行延遲
}int main()
{pid_t id = fork(); // 創(chuàng)建子進程cout << "id" << ":" << id << endl;if(id == 0) // 判斷是否是子進程{sleep(3); // 子進程先睡眠 3 秒childtast(); // 子進程調(diào)用 childtast(),打印數(shù)字并睡眠}// 父進程等待任意一個子進程終止pid_t ret = wait(NULL); // 父進程調(diào)用 wait(),阻塞等待子進程終止if(ret == id) // 判斷 wait() 返回的進程 ID 是否是創(chuàng)建的子進程 ID{cout << "ret" << ":" << ret << endl; // 輸出子進程的 IDcout << "wait success" << endl; // 輸出等待成功的消息}sleep(3); // 父進程再睡眠 3 秒,模擬延遲return 0;
}
fork()
創(chuàng)建子進程:
- 父進程和子進程同時運行。
- 父進程的
id
是子進程的 PID,子進程的id
是 0。
子進程的任務(wù):
- 子進程先睡眠 3 秒,然后執(zhí)行
childtast()
,打印0
到9
。
父進程的等待:
- 父進程調(diào)用
wait(NULL)
,阻塞自身,直到子進程終止。 - 當子進程完成任務(wù)并退出后,
wait()
返回子進程的 PID。
父進程的后續(xù)操作:
- 父進程輸出子進程的
PID
和等待成功的消息。 - 父進程再睡眠 3 秒后退出。
waitpid ( )
waitpid()
是 wait()
的增強版本,提供了更靈活的功能,允許父進程:
- 等待特定的子進程。
- 非阻塞等待子進程。
- 獲取子進程的狀態(tài)(如退出狀態(tài)或被信號終止)。
pid_t waitpid(pid_t pid, int *status, int options);
參數(shù)說明
-
pid
:-
pid > 0
:等待特定的子進程(指定的 PID)。 -
pid == 0
:等待與當前進程同一個進程組的任意子進程。 -
pid < -1
:等待進程組 ID 為|pid|
的任意子進程。wait(NULL) //等價于 waitpid(-1,NULL,0);
-
pid == -1
:等效于wait()
,等待任意子進程。
-
status
字段的結(jié)構(gòu)
status
:
- 指向一個整數(shù)的指針,用于存儲子進程的狀態(tài)信息(退出狀態(tài)、信號等)。
- 若不關(guān)心狀態(tài)信息,可將其設(shè)為
NULL
。
在 Linux 系統(tǒng)中,status
是一個整數(shù),表示子進程狀態(tài)的多種可能性,底層通過位字段表示:
位字段 | 含義 |
---|---|
位 0-7 | 子進程退出的信號或退出碼(低 8 位)。 |
位 8-15 | 退出狀態(tài)(高 8 位,存儲正常退出碼)。 |
位 16-23 | 暫停信號編號。 |
代碼解析字段
#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;
int main()
{ pid_t id = fork(); cout << "id" << ":" << id <<endl; if(id == 0) { sleep(3); exit(1); } int status; pid_t ret = waitpid(-1,&status,0); if(ret == id) {cout << "ret" << ":" << ret <<endl; cout<< "wait success" <<endl; } cout <<"status :" << status << endl;cout << "退出碼" << ((status >> 8)& 0xff ) <<" "<< "信號碼" << (status & 0x7f)<< endl;return 0;
}
完整運行流程
fork()
創(chuàng)建子進程:
- 父進程創(chuàng)建子進程,并返回子進程的 PID。
子進程邏輯:
- 子進程休眠 3 秒后正常退出,退出碼為
1
。
父進程邏輯:
- 父進程調(diào)用
waitpid()
阻塞等待子進程終止。 - 獲取子進程的狀態(tài)信息,并解析退出碼和信號碼。
父進程輸出狀態(tài)信息:
- 輸出子進程的 PID、狀態(tài)值、退出碼和信號碼。
解析邏輯:
-
退出碼:
(status >> 8) & 0xff
- 獲取高 8 位的退出碼。
-
信號碼:
status & 0x7f
- 獲取低 7 位的信號碼.
示例1:進程正常退出的退出碼。
示例2:提取被9號信號殺死的進程信號碼
id:1667 // 父進程輸出,子進程的 PID 是 1667 id:0 // 子進程輸出,表明當前是子進程 ret:1667 // 父進程成功等待到子進程結(jié)束,返回子進程 PID wait success // 父進程確認子進程終止 status :9 // 父進程獲取子進程狀態(tài)值為 9 退出碼0 信號碼9 // 父進程解析狀態(tài)值:// - 退出碼 0:子進程未通過 exit() 返回退出碼// - 信號碼 9:子進程被 SIGKILL 信號終止
庫中提供的宏替換
解析退出碼和信號編號
WIFEXITED(status)
:- 如果為真,表示子進程正常退出,其退出碼存儲在高 8 位。
- 使用
(status >> 8) & 0xff
提取退出碼。
WEXITSTATUS(status)
:- 獲取退出碼的宏,等價于
(status >> 8) & 0xff
。 - 必須確保
WIFEXITED(status)
為真后使用。
- 獲取退出碼的宏,等價于
解析退出碼和信號編號
WIFEXITED(status)
- 如果為真,表示子進程正常退出,其退出碼存儲在高 8 位。
- 使用
(status >> 8) & 0xff
提取退出碼。
WEXITSTATUS(status)
:==status & 0x7f
- 獲取退出碼的宏,
- 必須確保
WIFEXITED(status)
為真后使用。
options
參數(shù)介紹
阻塞與非阻塞
特性 | 阻塞 | 非阻塞 |
---|---|---|
進程狀態(tài) | 等待資源時掛起,無法執(zhí)行其他任務(wù)。 | 立即返回,不會掛起,進程可執(zhí)行其他任務(wù)。 |
適用場景 | 簡單任務(wù)、對實時性要求不高的任務(wù)。 | 多任務(wù)并發(fā)、實時性要求高的任務(wù)。 |
復(fù)雜性 | 實現(xiàn)簡單,邏輯清晰。 | 邏輯復(fù)雜,需要輪詢或回調(diào)處理資源狀態(tài)。 |
CPU 使用 | 不浪費 CPU 資源,進程處于掛起狀態(tài)。 | 需要輪詢資源狀態(tài),可能增加 CPU 占用。 |
資源管理 | 等待資源的管理交由操作系統(tǒng)處理。 | 需要程序主動檢查資源狀態(tài),增加開發(fā)復(fù)雜度。 |
options
:
- 用于指定額外的選項:
0
:阻塞等待。WNOHANG
:非阻塞等待。WUNTRACED
:返回暫停的子進程狀態(tài)(子進程因SIGSTOP
信號暫停)。WCONTINUED
:返回恢復(fù)運行的子進程狀態(tài)(子進程因SIGCONT
信號繼續(xù)運行)。
WNOHANG
- 非阻塞模式:
- 如果沒有子進程終止,
waitpid()
會立即返回,而不是阻塞父進程。
- 如果沒有子進程終止,
- 返回值:
- 如果有子進程狀態(tài)變化,則返回子進程的 PID。
- 如果沒有子進程狀態(tài)變化,則返回
0
。
非阻塞輪詢
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <chrono>
#include <thread>
using namespace std;
int main() {pid_t pid = fork(); // 創(chuàng)建子進程if (pid == 0) {// 子進程邏輯cout << "Child process running..." << endl;sleep(5); // 模擬子進程任務(wù),延遲 5 秒cout << "Child process exiting..." << endl;exit(42); // 子進程以退出碼 42 正常退出} else if (pid > 0) {// 父進程邏輯int status;while (true) {pid_t ret = waitpid(-1, &status, WNOHANG); // 非阻塞檢查子進程狀態(tài)if (ret == 0) {// 子進程尚未終止,父進程繼續(xù)其他工作cout << "Child process still running. Parent doing other work..." << endl;this_thread::sleep_for(chrono::seconds(1)); // 模擬父進程任務(wù)} else if (ret > 0) {// 子進程已終止,解析狀態(tài)if (WIFEXITED(status)) {cout << "Child process " << ret << " exited with code " << WEXITSTATUS(status) << endl;} else if (WIFSIGNALED(status)) {cout << "Child process " << ret << " was terminated by signal " << WTERMSIG(status) << endl;}break; // 結(jié)束輪詢} else {// waitpid 出錯perror("waitpid failed");break;}}} else {// fork 失敗perror("fork failed");return 1;}return 0;
}
執(zhí)行結(jié)果:
多進程下的進程等待
阻塞等待多個子進程
示例代碼:等待所有子進程完成
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;int main() {// 創(chuàng)建多個子進程for (int i = 0; i < 3; ++i) {pid_t pid = fork();if (pid == 0) {// 子進程cout << "Child " << i << " (PID: " << getpid() << ") running..." << endl;sleep(2 + i); // 每個子進程休眠不同時間cout << "Child " << i << " (PID: " << getpid() << ") exiting..." << endl;exit(i); // 子進程以其序號為退出碼}}// 父進程:等待所有子進程完成int status;while (true) {pid_t ret = wait(&status); // 阻塞等待任意一個子進程結(jié)束if (ret == -1) {// 沒有子進程可等待時退出循環(huán)cout << "All child processes have finished." << endl;break;// 解析子進程狀態(tài)if (WIFEXITED(status)) {cout << "Child process " << ret << " exited with code: " << WEXITSTATUS(status) << endl;} else if (WIFSIGNALED(status)) {cout << "Child process " << ret << " was terminated by signal: " << WTERMSIG(status) << endl;}}return 0;
}
代碼執(zhí)行:
非阻塞輪詢等待多個子進程
示例代碼:非阻塞等待多個子進程
通過 waitpid()
配合 WNOHANG
實現(xiàn)父進程的非阻塞輪詢,定期檢查是否有子進程完成。
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <chrono>
#include <thread>
using namespace std;int main() {// 創(chuàng)建多個子進程for (int i = 0; i < 3; ++i) {pid_t pid = fork();if (pid == 0) {// 子進程cout << "Child " << i << " (PID: " << getpid() << ") running..." << endl;sleep(2 + i); // 每個子進程休眠不同時間cout << "Child " << i << " (PID: " << getpid() << ") exiting..." << endl;exit(i); // 子進程以其序號為退出碼}}// 父進程:非阻塞輪詢等待所有子進程完成int status;int completed = 0; // 已完成的子進程計數(shù)while (completed < 3) {pid_t ret = waitpid(-1, &status, WNOHANG); // 非阻塞檢查子進程狀態(tài)if (ret > 0) {// 有子進程狀態(tài)變化completed++;if (WIFEXITED(status)) {cout << "Child process " << ret << " exited with code: " << WEXITSTATUS(status) << endl;} else if (WIFSIGNALED(status)) {cout << "Child process " << ret << " was terminated by signal: " << WTERMSIG(status) << endl;}} else if (ret == 0) {// 沒有子進程狀態(tài)變化,父進程繼續(xù)其他工作cout << "No child process exited yet. Parent doing other work..." << endl;this_thread::sleep_for(chrono::seconds(1)); // 模擬其他任務(wù)} else {// 錯誤處理perror("waitpid failed");break;}}cout << "All child processes have finished." << endl;return 0;
}
代碼執(zhí)行: