Linux進(jìn)程管理之創(chuàng)建、終止、回收與替換操作完全指南
引言
進(jìn)程是Linux系統(tǒng)的核心概念之一,理解進(jìn)程的創(chuàng)建、終止、回收和替換是系統(tǒng)編程的基石。本文將系統(tǒng)性地介紹Linux進(jìn)程管理的各個(gè)方面,包括父子進(jìn)程關(guān)系、寫時(shí)復(fù)制技術(shù)、進(jìn)程終止方式、僵尸進(jìn)程處理、進(jìn)程回收機(jī)制以及exec函數(shù)族的使用。
一、父子進(jìn)程與寫時(shí)復(fù)制
1.1 fork創(chuàng)建進(jìn)程
在Linux中,通過(guò)fork()系統(tǒng)調(diào)用創(chuàng)建新進(jìn)程:
#include <unistd.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子進(jìn)程代碼
printf("子進(jìn)程: PID=%d\n", getpid());
} else {
// 父進(jìn)程代碼
printf("父進(jìn)程: 創(chuàng)建了子進(jìn)程PID=%d\n", pid);
}
return 0;
}1.2 寫時(shí)復(fù)制(Copy-On-Write)
傳統(tǒng)理解:fork()會(huì)完全復(fù)制父進(jìn)程的內(nèi)存空間給子進(jìn)程,效率低下。
現(xiàn)代Linux(2.6+內(nèi)核)實(shí)現(xiàn):
- 立即共享:
fork()剛完成時(shí),子進(jìn)程與父進(jìn)程共享所有內(nèi)存頁(yè) - 按需復(fù)制:只有當(dāng)父子進(jìn)程中的任意一方嘗試修改某個(gè)內(nèi)存頁(yè)時(shí),內(nèi)核才會(huì)復(fù)制該頁(yè)
- 效率優(yōu)勢(shì):避免了不必要的內(nèi)存復(fù)制,大幅提升性能
int shared_data = 100; // 父子進(jìn)程共享
pid_t pid = fork();
if (pid == 0) {
// 子進(jìn)程
shared_data = 200; // 此時(shí)觸發(fā)寫時(shí)復(fù)制
printf("子進(jìn)程修改后: %d\n", shared_data);
} else {
// 父進(jìn)程
sleep(1);
printf("父進(jìn)程的值: %d\n", shared_data); // 仍為100
}二、進(jìn)程的終止:8種情況詳解
進(jìn)程可以通過(guò)多種方式終止,了解這些情況對(duì)編寫健壯程序至關(guān)重要。
2.1 正常終止方式
| 方式 | 說(shuō)明 | 代碼示例 |
|---|---|---|
| 1. main函數(shù)return? | 在main函數(shù)中使用return語(yǔ)句 | return 0; |
| 2. exit()庫(kù)函數(shù)? | 執(zhí)行完整清理工作 | exit(0); |
| 3. exit()/Exit()? | 立即退出,不執(zhí)行清理 | _exit(0); |
exit()與_exit()的關(guān)鍵區(qū)別:
// exit()示例
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("這條消息會(huì)被輸出"); // 在緩沖區(qū)
exit(0); // 刷新緩沖區(qū),輸出消息
// 還會(huì)執(zhí)行atexit()注冊(cè)的清理函數(shù)
}
// _exit()示例
#include <stdio.h>
#include <unistd.h>
int main() {
printf("這條消息可能不會(huì)輸出"); // 在緩沖區(qū)
_exit(0); // 不刷新緩沖區(qū),消息丟失
// 不執(zhí)行任何清理函數(shù)
}exit函數(shù)參數(shù)說(shuō)明:
exit(0); // 成功退出 exit(EXIT_SUCCESS); // 同exit(0) exit(EXIT_FAILURE); // 失敗退出,值為1 exit(1); // 自定義錯(cuò)誤碼
2.2 異常終止方式
| 方式 | 說(shuō)明 | 觸發(fā)條件 |
|---|---|---|
| 4. abort()? | 產(chǎn)生SIGABRT信號(hào) | abort(); |
| 5. 信號(hào)終止? | 被信號(hào)殺死 | kill(pid, SIGKILL); |
| 6. 主線程退出? | 多線程程序主線程return | 主線程返回 |
| 7. pthread_exit? | 主線程調(diào)用退出函數(shù) | pthread_exit(NULL); |
| 8. 線程被取消? | 線程被pthread_cancel | 最后一個(gè)線程被取消 |
三、進(jìn)程終止后的狀態(tài)管理
3.1 僵尸進(jìn)程(Zombie Process)
產(chǎn)生原因:
- 子進(jìn)程先于父進(jìn)程終止
- 父進(jìn)程沒(méi)有調(diào)用
wait()或waitpid()回收子進(jìn)程狀態(tài) - 子進(jìn)程用戶空間被釋放,但內(nèi)核PCB仍保留
識(shí)別僵尸進(jìn)程:
# 使用ps命令查看 ps aux | grep Z # 或 ps -eo pid,stat,command | grep '^.*Z' # 使用top命令查看 top # 在Tasks行查看zombie數(shù)量
top命令顯示示例:
top - 14:25:00 up 1 day, 3:45, 2 users, load average: 0.00, 0.01, 0.05 Tasks: 120 total, 1 running, 119 sleeping, 0 stopped, 1 zombie %Cpu(s): 0.3 us, 0.3 sy, 0.0 ni, 99.3 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st MiB Mem : 1986.8 total, 245.3 free, 987.2 used, 754.3 buff/cache MiB Swap: 2048.0 total, 2048.0 free, 0.0 used. 857.8 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 47317 root 20 0 0 0 0 Z 0.0 0.0 0:00.00 a.out <defunct>
危害:
- 占用內(nèi)核PCB資源
- 大量僵尸進(jìn)程導(dǎo)致內(nèi)核內(nèi)存耗盡
- 系統(tǒng)不穩(wěn)定甚至崩潰
3.2 孤兒進(jìn)程(Orphan Process)
產(chǎn)生原因:
- 父進(jìn)程先于子進(jìn)程終止
- 子進(jìn)程被init進(jìn)程(PID=1)收養(yǎng)
特點(diǎn):
- 不會(huì)對(duì)系統(tǒng)造成危害
- 由新的父進(jìn)程(init)負(fù)責(zé)回收
- 無(wú)需特別處理
四、進(jìn)程回收機(jī)制
4.1 wait函數(shù) - 阻塞回收
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status);
功能:阻塞等待任意子進(jìn)程退出并回收狀態(tài)
參數(shù):
status:存儲(chǔ)子進(jìn)程退出狀態(tài),NULL表示不關(guān)心狀態(tài)
返回值:
- 成功:返回 回收的子進(jìn)程PID
- 失?。悍祷?1
狀態(tài)檢查宏:
if (WIFEXITED(status)) {
// 正常結(jié)束
printf("退出碼: %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
// 信號(hào)終止
printf("被信號(hào)殺死: %d\n", WTERMSIG(status));
}完整示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
} else if (pid == 0) {
// 子進(jìn)程
printf("子進(jìn)程運(yùn)行3秒\n");
sleep(3);
exit(42); // 退出碼42
} else {
// 父進(jìn)程
printf("父進(jìn)程等待子進(jìn)程...\n");
int status;
pid_t child_pid = wait(&status);
if (WIFEXITED(status)) {
printf("子進(jìn)程%d正常退出,返回值: %d\n",
child_pid, WEXITSTATUS(status));
}
}
return 0;
}4.2 waitpid函數(shù) - 精確控制回收
#include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options);
參數(shù)詳解:
| 參數(shù) | 含義 | 常用值 |
|---|---|---|
| pid? | 指定回收的進(jìn)程 | >0:特定子進(jìn)程-1:任意子進(jìn)程0:同組進(jìn)程 |
| status? | 退出狀態(tài)指針 | 同wait() |
| options? | 控制選項(xiàng) | 0:阻塞等待WNOHANG:非阻塞 |
阻塞模式示例:
// 等價(jià)于 wait(status) waitpid(-1, status, 0);
非阻塞模式示例:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子進(jìn)程運(yùn)行5秒
sleep(5);
exit(0);
}
// 父進(jìn)程非阻塞回收
int status;
pid_t result;
do {
result = waitpid(pid, &status, WNOHANG);
if (result == 0) {
printf("子進(jìn)程還未退出,父進(jìn)程可以做其他事...\n");
sleep(1);
}
} while (result == 0);
printf("子進(jìn)程已回收\(chéng)n");
return 0;
}五、exec函數(shù)族:進(jìn)程替換
5.1 exec基本概念
功能:用新程序替換當(dāng)前進(jìn)程的代碼段
特點(diǎn):
- 執(zhí)行成功不返回(原代碼被覆蓋)
- 失敗返回-1
- 通常與
fork()搭配使用
執(zhí)行exec前后的內(nèi)存變化:
執(zhí)行前: 執(zhí)行后:
+-----------------+ +-----------------+
| 原程序代碼段 | | 新程序代碼段 |
| main() { | | (如ls的實(shí)現(xiàn)代碼) |
| exec("ls"); | → | |
| ... | | |
| } | | |
+-----------------+ +-----------------+
| 數(shù)據(jù)段、堆棧等 | | 數(shù)據(jù)段、堆棧等 |
| 保持不變 | | 可能被新程序重置 |
+-----------------+ +-----------------+
5.2 exec函數(shù)族成員
函數(shù)名后綴含義:
- l:參數(shù)列表(list),逐個(gè)傳遞
- v:參數(shù)數(shù)組(vector),數(shù)組傳遞
- p:使用PATH環(huán)境變量查找程序
- e:自定義環(huán)境變量
| 函數(shù) | 參數(shù)查找 | 參數(shù)傳遞 | 環(huán)境變量 |
|---|---|---|---|
| execl? | 路徑+文件名 | 列表 | 繼承 |
| execlp? | PATH查找 | 列表 | 繼承 |
| execv? | 路徑+文件名 | 數(shù)組 | 繼承 |
| execvp? | PATH查找 | 數(shù)組 | 繼承 |
5.3 使用示例
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子進(jìn)程:執(zhí)行l(wèi)s -l命令
// 方法1:execl
execl("/bin/ls", "ls", "-l", "/home", NULL);
// 方法2:execv
// char *args[] = {"ls", "-l", "/home", NULL};
// execv("/bin/ls", args);
// 方法3:execlp(使用PATH)
// execlp("ls", "ls", "-l", "/home", NULL);
// 如果exec失敗才會(huì)執(zhí)行到這里
perror("exec failed");
_exit(1);
} else {
// 父進(jìn)程
wait(NULL);
printf("子進(jìn)程執(zhí)行完畢\n");
}
return 0;
}調(diào)用自己的程序:
// 假設(shè)當(dāng)前目錄有可執(zhí)行程序myapp
char *args[] = {"./myapp", "arg1", "arg2", NULL};
execv("./myapp", args);六、相關(guān)工具函數(shù)
6.1 system函數(shù)
#include <stdlib.h> int system(const char *command);
功能:執(zhí)行shell命令(內(nèi)部使用fork+exec實(shí)現(xiàn))
限制:不能執(zhí)行需要修改父進(jìn)程狀態(tài)的命令
示例:
system("ls -l"); // 列出目錄
system("date"); // 顯示日期6.2 工作目錄管理
#include <unistd.h> // 獲取當(dāng)前工作目錄 char *getcwd(char *buf, size_t size); // buf: 存儲(chǔ)路徑的緩沖區(qū) // size: 緩沖區(qū)大小 // 返回: 指向buf的指針,失敗返回NULL // 改變當(dāng)前工作目錄 int chdir(const char *path); // path: 新路徑 // 返回: 0成功,-1失敗
示例:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
char cwd[1024];
// 獲取當(dāng)前目錄
if (getcwd(cwd, sizeof(cwd)) != NULL) {
printf("當(dāng)前目錄: %s\n", cwd);
}
// 改變目錄
if (chdir("/tmp") == 0) {
printf("切換到/tmp成功\n");
getcwd(cwd, sizeof(cwd));
printf("新目錄: %s\n", cwd);
}
return 0;
}七、綜合應(yīng)用實(shí)例
7.1 安全的子進(jìn)程管理框架
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
// 信號(hào)處理:避免僵尸進(jìn)程
void sigchld_handler(int sig) {
int saved_errno = errno;
while (waitpid(-1, NULL, WNOHANG) > 0) {
// 循環(huán)回收所有已終止的子進(jìn)程
}
errno = saved_errno;
}
int main() {
// 注冊(cè)SIGCHLD信號(hào)處理
struct sigaction sa;
sa.sa_handler = sigchld_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART | SA_NOCLDSTOP;
sigaction(SIGCHLD, &sa, NULL);
// 創(chuàng)建多個(gè)子進(jìn)程
for (int i = 0; i < 3; i++) {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
continue;
} else if (pid == 0) {
// 子進(jìn)程執(zhí)行任務(wù)
printf("子進(jìn)程%d啟動(dòng) (PID=%d)\n", i, getpid());
sleep(i + 1); // 模擬工作
printf("子進(jìn)程%d結(jié)束\n", i);
exit(0);
} else {
printf("父進(jìn)程創(chuàng)建了子進(jìn)程%d (PID=%d)\n", i, pid);
}
}
// 父進(jìn)程繼續(xù)工作
printf("父進(jìn)程繼續(xù)執(zhí)行其他任務(wù)...\n");
for (int i = 0; i < 10; i++) {
printf("父進(jìn)程工作 %d/10\n", i + 1);
sleep(1);
}
printf("父進(jìn)程結(jié)束\n");
return 0;
}7.2 進(jìn)程池模式示例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define WORKER_COUNT 3
void worker_process(int id) {
printf("工作進(jìn)程%d (PID=%d) 啟動(dòng)\n", id, getpid());
// 執(zhí)行實(shí)際工作
for (int i = 0; i < 3; i++) {
printf("工作進(jìn)程%d: 任務(wù)%d\n", id, i);
sleep(1);
}
printf("工作進(jìn)程%d 結(jié)束\n", id);
exit(0);
}
int main() {
printf("主進(jìn)程啟動(dòng) (PID=%d)\n", getpid());
// 創(chuàng)建工作進(jìn)程
for (int i = 0; i < WORKER_COUNT; i++) {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
exit(1);
} else if (pid == 0) {
worker_process(i);
}
}
// 等待所有工作進(jìn)程完成
int status;
for (int i = 0; i < WORKER_COUNT; i++) {
pid_t child_pid = wait(&status);
if (WIFEXITED(status)) {
printf("工作進(jìn)程%d正常結(jié)束\n", child_pid);
}
}
printf("所有工作進(jìn)程完成,主進(jìn)程結(jié)束\n");
return 0;
}總結(jié)與最佳實(shí)踐
關(guān)鍵要點(diǎn)回顧
| 主題 | 核心概念 | 重要函數(shù) |
|---|---|---|
| 進(jìn)程創(chuàng)建? | 寫時(shí)復(fù)制優(yōu)化性能 | fork() |
| 進(jìn)程終止? | 8種終止方式,區(qū)別exit和_exit | exit(), _exit() |
| 僵尸進(jìn)程? | 父進(jìn)程未回收的終止子進(jìn)程 | wait(), waitpid() |
| 進(jìn)程回收? | 阻塞/非阻塞回收狀態(tài) | waitpid(pid, status, WNOHANG) |
| 進(jìn)程替換? | 執(zhí)行新程序,不返回 | execl(), execv()系列 |
| 工具函數(shù)? | 系統(tǒng)命令、目錄管理 | system(), getcwd(), chdir() |
最佳實(shí)踐建議
- 始終檢查系統(tǒng)調(diào)用返回值,特別是
fork()、exec()、wait()系列 - 及時(shí)回收子進(jìn)程,避免僵尸進(jìn)程積累
- 使用非阻塞waitpid管理多個(gè)子進(jìn)程,避免父進(jìn)程阻塞
- fork+exec是標(biāo)準(zhǔn)模式:先創(chuàng)建進(jìn)程,再替換為實(shí)際要運(yùn)行的程序
- 處理SIGCHLD信號(hào):自動(dòng)回收子進(jìn)程,提高程序健壯性
- 注意exec的參數(shù)格式:最后一個(gè)參數(shù)必須是NULL
- 區(qū)分exit和_exit:需要清理時(shí)用
exit(),緊急退出用_exit()
常見(jiàn)問(wèn)題排查
- 僵尸進(jìn)程過(guò)多:父進(jìn)程沒(méi)有正確調(diào)用
wait()系列函數(shù) - 子進(jìn)程沒(méi)執(zhí)行exec:檢查exec參數(shù)是否正確,特別是路徑和NULL結(jié)尾
- 資源泄漏:確保文件描述符、內(nèi)存等在子進(jìn)程中正確釋放
- 競(jìng)爭(zhēng)條件:父進(jìn)程在子進(jìn)程之前終止可能導(dǎo)致意外結(jié)果
通過(guò)掌握這些進(jìn)程管理技術(shù),您將能夠編寫出健壯、高效的Linux系統(tǒng)程序。理解進(jìn)程的完整生命周期(創(chuàng)建→運(yùn)行→終止→回收)是系統(tǒng)編程的基礎(chǔ),也是進(jìn)一步學(xué)習(xí)多線程、進(jìn)程間通信等高級(jí)主題的前提。
到此這篇關(guān)于Linux進(jìn)程管理之創(chuàng)建、終止、回收與替換操作完全指南的文章就介紹到這了,更多相關(guān)Linux進(jìn)程管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ubuntu環(huán)境下的php相關(guān)路徑與修改方法
這篇文章主要介紹了ubuntu環(huán)境下的php相關(guān)的路徑,需要的朋友可以參考下2020-12-12
Ubuntu搭建web站點(diǎn)并發(fā)布公網(wǎng)訪問(wèn)詳細(xì)步驟(內(nèi)網(wǎng)穿透)
這篇文章主要給大家介紹了關(guān)于Ubuntu搭建web站點(diǎn)并發(fā)布公網(wǎng)訪問(wèn)(內(nèi)網(wǎng)穿透)的相關(guān)資料,內(nèi)網(wǎng)穿透是一種實(shí)現(xiàn)在外網(wǎng)任意地點(diǎn)訪問(wèn)內(nèi)網(wǎng)的方法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
Typecho程序偽靜態(tài)規(guī)則大全(包括Linux/Windows)
Typecho程序的偽靜態(tài)規(guī)則不同于wp直接默認(rèn)可用,需要我們手工加載到空間中才可以生效。下面老左整理了這款程序在不同的主機(jī)環(huán)境中的偽靜態(tài)規(guī)則,希望對(duì)大家有所幫助2012-09-09
Linux中使用命令more,less,cat查看文件內(nèi)容
今天小編就為大家分享一篇關(guān)于Linux中使用命令more,less,cat查看文件內(nèi)容,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03
CentOS系統(tǒng)下Apache配置多域名或多端口映射的方法
我們大多情況是一臺(tái)服務(wù)器一個(gè)IP,這樣配置起來(lái)很簡(jiǎn)單,但是如何想多域名多端口映射的話就沒(méi)那么簡(jiǎn)單了,下面這篇文章主要介紹了CentOS系統(tǒng)下Apache配置多域名或多端口映射的方法,需要的朋友可以參考學(xué)習(xí),下面來(lái)一起看看吧。2016-12-12
Tomcat中的catalina.bat原理詳細(xì)解析
這篇文章主要給大家詳細(xì)介紹了關(guān)于Tomcat中catalina.bat的原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)下吧。2017-08-08

