欢迎光临
我们一直在努力

Linux 进程管理

Linux 进程管理

遵循统一结构:函数原型 → 核心参数/说明 → 完整代码示例 → 返回值解析,覆盖进程创建、替换、退出、回收、状态解析、进程组管理全知识点,兼顾理论与实战。

一、进程基础核心概念

核心概念精准定义关键属性
进程 程序的一次执行实例,是操作系统资源分配和调度的基本单位 唯一 PID、独立地址空间、动态生命周期
PCB 进程控制块,内核用 task_struct 结构体实现,是进程的“身份证” 包含 PID/PPID/PGID、进程状态、内存映射、文件描述符表、信号处理规则
进程组 一组关联进程的集合,共享同一个 PGID(进程组 ID) 组长进程 PID = PGID,子进程默认继承父进程的进程组,终端信号(如 Ctrl+C)作用于整个进程组
写时复制 fork 后的核心机制,父子进程共享代码段和数据段,仅当某一进程修改数据时才复制对应内存页 大幅减少 fork 开销,提升进程创建效率

二、进程标识:获取 PID/PPID

1. getpid – 获取当前进程 PID

函数原型

#include <unistd.h>
pid_t getpid(void);

核心参数/说明
  • 无参数,直接调用即可。
  • 作用:返回当前进程的唯一进程 ID,常用于进程身份标识、日志打印。
完整代码示例

#include <stdio.h>
#include <unistd.h>

int main() {
// 获取当前进程 PID
pid_t pid = getpid();
printf("当前进程 PID:%d\\n", pid);
return 0;
}

返回值解析
  • 成功:返回非负整数(当前进程的 PID)。
  • 失败:无失败场景,内核始终能返回有效 PID。

2. getppid – 获取父进程 PID

函数原型

#include <unistd.h>
pid_t getppid(void);

核心参数/说明
  • 无参数,直接调用即可。
  • 作用:返回当前进程的父进程 PID,用于判断进程亲缘关系。
完整代码示例

#include <stdio.h>
#include <unistd.h>

int main() {
pid_t my_pid = getpid();
pid_t parent_pid = getppid();
printf("当前进程 PID:%d,父进程 PID:%d\\n", my_pid, parent_pid);
return 0;
}

返回值解析
  • 成功:返回非负整数(父进程的 PID)。
  • 失败:无失败场景。

三、进程创建:fork / vfork(核心系统调用)

1. fork – 标准子进程创建

函数原型

#include <unistd.h>
pid_t fork(void);

核心参数/说明
  • 无参数,调用后内核完成 3 件事:1. 复制父进程 PCB;2. 分配新 PID;3. 采用写时复制机制共享内存。
  • 关键特性:父子进程代码段共享,数据段/堆/栈独立,执行顺序由 CPU 调度器决定(不可预测)。
完整代码示例(含写时复制验证)

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
int global_val = 100; // 全局变量,验证写时复制
pid_t pid = fork(); // 分裂为父子进程

if (pid > 0) {
// 父进程分支:等待子进程执行完毕,再修改全局变量
wait(NULL); // 阻塞等待子进程退出,避免僵尸进程
global_val += 10;
printf("[父进程] PID:%d,global_val:%d\\n", getpid(), global_val);
} else if (pid == 0) {
// 子进程分支:独立修改全局变量
global_val += 20;
printf("[子进程] PID:%d,父进程 PID:%d,global_val:%d\\n",
getpid(), getppid(), global_val);
return 0; // 子进程正常退出
} else {
// fork 创建失败
perror("fork 失败");
return 1;
}
return 0;
}

返回值解析
返回值所属进程含义
> 0 父进程 返回新创建的子进程 PID
= 0 子进程 表示当前为子进程,无返回值意义
-1 父进程 创建失败(如系统进程数达到上限、内存不足),同时设置 errno

2. vfork – 高效进程创建(仅用于 exec 场景)

函数原型

#include <unistd.h>
pid_t vfork(void);

核心参数/说明
  • 无参数,与 fork 的核心区别:
  • 不采用写时复制,直接共享父进程地址空间(子进程修改数据会直接影响父进程);
  • 强制子进程先执行,父进程阻塞,直到子进程调用 exec 或 _exit;
  • 仅适用于“子进程创建后立即执行新程序”的场景,效率远高于 fork。
完整代码示例(配合 exec 使用)

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
pid_t pid = vfork();

if (pid > 0) {
// 父进程:子进程执行 exec 后才会唤醒
int status;
wait(&status);
printf("[父进程] 子进程执行完毕\\n");
} else if (pid == 0) {
// 子进程:必须立即调用 exec,禁止修改父进程数据
execlp("ls", "ls", "-l", NULL);
// 若 exec 失败,必须用 _exit 退出,禁止用 exit(避免刷新父进程缓冲区)
_exit(1);
} else {
perror("vfork 失败");
return 1;
}
return 0;
}

返回值解析
  • 与 fork 完全一致:父进程返回子进程 PID,子进程返回 0,失败返回 -1。

四、进程替换:exec 函数族(核心工具)

总述

exec 函数族的核心作用:在当前进程地址空间中加载并执行新程序,替换原进程的代码段、数据段、堆、栈,PID 保持不变。

  • 命名规则:l(参数列表)、v(参数数组)、p(自动搜索 PATH)、e(自定义环境变量)。
  • 核心特性:执行成功后,原进程后续代码不再执行;仅当执行失败时,才会返回并执行后续错误处理代码。

1. execlp – 最常用的进程替换函数(自动搜 PATH)

函数原型

#include <unistd.h>
int execlp(const char *file, const char *arg, ... /*, (char *)NULL */);

核心参数/说明
参数含义
file 要执行的程序名(如 “ls”、“ps”),内核会在 PATH 环境变量中自动查找
arg 程序的命令行参数,第一个参数必须等于程序名,后续为具体参数
可变参数,以 (char *)NULL 结尾,标识参数列表结束
完整代码示例(子进程执行系统命令)

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
pid_t pid = fork();

if (pid > 0) {
// 父进程:回收子进程
int status;
waitpid(pid, &status, 0);
} else if (pid == 0) {
// 子进程:替换为 ls -l 命令
printf("子进程执行 ls -l 命令:\\n");
execlp("ls", "ls", "-l", "–color=auto", NULL);
// 若执行到这里,说明 execlp 失败
perror("execlp 失败");
_exit(1);
} else {
perror("fork 失败");
return 1;
}
return 0;
}

返回值解析
  • 成功:无返回值(原进程代码已被替换,无法执行后续代码)。
  • 失败:返回 -1,同时设置 errno(如程序不存在、权限不足)。

2. execvp – 数组传参的进程替换(自动搜 PATH)

函数原型

#include <unistd.h>
int execvp(const char *file, char *const argv[]);

核心参数/说明
参数含义
file 要执行的程序名,同 execlp
argv 命令行参数数组,最后一个元素必须为 NULL
完整代码示例

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
pid_t pid = fork();
if (pid == 0) {
// 定义参数数组
char *const argv[] = {"ps", "aux", NULL};
// 执行 ps aux 命令
execvp("ps", argv);
perror("execvp 失败");
_exit(1);
} else if (pid > 0) {
wait(NULL);
} else {
perror("fork 失败");
return 1;
}
return 0;
}

返回值解析
  • 与 execlp 完全一致:成功无返回,失败返回 -1。

3. execl / execve – 绝对路径传参

函数原型

// 列表传参,需绝对路径
#include <unistd.h>
int execl(const char *path, const char *arg, ... /*, (char *)NULL */);

// 底层系统调用,自定义环境变量,需绝对路径
int execve(const char *path, char *const argv[], char *const envp[]);

核心参数/说明
  • path:程序的绝对路径(如 “/bin/ls”),不再自动搜索 PATH。
  • envp:自定义环境变量数组(如 char *envp[] = {"PATH=/bin", "USER=root", NULL};)。
完整代码示例(execl)

// 执行 /bin/date 命令,输出当前时间
execl("/bin/date", "date", NULL);

返回值解析
  • 与其他 exec 函数一致:成功无返回,失败返回 -1。

五、进程退出:return / exit / _exit(生命周期终止)

1. 核心对比表

退出方式函数原型所属类型缓冲区处理适用场景
return return int status; 语言关键字 刷新缓冲区 main 函数正常退出
exit #include <stdlib.h>void exit(int status); 标准库函数 刷新缓冲区、执行 atexit 注册的清理函数、关闭文件描述符 进程通用正常退出
_exit #include <unistd.h>void _exit(int status); 系统调用 不刷新缓冲区、直接终止 子进程异常退出、vfork 子进程退出

2. 关键说明

  • 退出码 status:仅低 8 位有效(取值范围 0-255),父进程通过 WEXITSTATUS 宏获取。
  • 退出码约定:0 表示正常退出,非 0 表示异常退出(可自定义含义)。

3. 完整代码示例(对比 exit 与 _exit)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
pid_t pid = fork();

if (pid == 0) {
// 子进程 1:用 exit 退出,刷新缓冲区
printf("子进程 1(exit):输出内容"); // 无换行,缓冲区未刷新
exit(0);
}

pid = fork();
if (pid == 0) {
// 子进程 2:用 _exit 退出,不刷新缓冲区
printf("子进程 2(_exit):输出内容"); // 无换行,缓冲区不会刷新
_exit(0);
}

// 父进程回收两个子进程
wait(NULL);
wait(NULL);
printf("\\n父进程执行完毕\\n");
return 0;
}

返回值解析
  • 三者均无返回值,直接终止进程,将退出状态传递给父进程。

六、进程回收:wait / waitpid(解决僵尸进程核心)

1. wait – 基础阻塞回收

函数原型

#include <sys/wait.h>
pid_t wait(int *status);

核心参数/说明
参数含义
status 输出参数,存储子进程的退出状态;若传入 NULL,表示不关心退出状态
核心特性 1. 阻塞父进程,直到任意一个子进程退出;2. 自动回收子进程的 PCB 资源,避免僵尸进程;3. 一次调用只能回收一个子进程
完整代码示例(配合退出状态宏)

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程正常退出,退出码 9
printf("子进程 PID:%d,即将退出\\n", getpid());
return 9;
} else if (pid > 0) {
int status;
// 阻塞等待子进程退出
pid_t wpid = wait(&status);
printf("回收的子进程 PID:%d\\n", wpid);

// 解析退出状态
if (WIFEXITED(status)) {
printf("子进程正常退出,退出码:%d\\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("子进程被信号杀死,信号编号:%d\\n", WTERMSIG(status));
}
} else {
perror("fork 失败");
return 1;
}
return 0;
}

返回值解析
  • 成功:返回被回收的子进程 PID。
  • 失败:返回 -1(如无未回收的子进程、被信号中断),同时设置 errno。

2. waitpid – 精准可控回收(核心重点)

函数原型

#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);

核心参数/说明
参数取值与含义
pid -1:回收任意一个子进程(最常用)>0:回收指定 PID 的子进程0:回收同进程组的任一子进程<0:回收进程组 ID 为 `
status 同 wait,存储退出状态,可传 NULL
options 0:阻塞模式(默认)WNOHANG:非阻塞模式(无子进程退出则立即返回)WUNTRACED:报告被停止的子进程状态WCONTINUED:报告被恢复运行的子进程状态
完整代码示例(3 种核心场景)
场景 1:非阻塞循环回收所有子进程(解决多子进程僵尸问题)

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
// 创建 3 个子进程
for (int i = 0; i < 3; i++) {
pid_t pid = fork();
if (pid == 0) {
printf("子进程 %d(PID:%d)运行中\\n", i+1, getpid());
sleep(i+1); // 模拟不同运行时长
return i+1; // 不同退出码
} else if (pid < 0) {
perror("fork 失败");
return 1;
}
}

// 父进程:非阻塞循环回收所有子进程
int status;
pid_t wpid;
while ((wpid = waitpid(1, &status, WNOHANG)) > 0) {
if (WIFEXITED(status)) {
printf("回收子进程 PID:%d,退出码:%d\\n", wpid, WEXITSTATUS(status));
}
}

printf("所有子进程已回收完毕\\n");
return 0;
}

场景 2:回收指定 PID 的子进程

// 假设子进程 PID 为 12345
waitpid(12345, &status, 0);

场景 3:回收同进程组的子进程

// 回收当前进程所在进程组的任一子进程
waitpid(0, &status, 0);

返回值解析(核心)
返回值含义
> 0 成功回收的子进程 PID
0 非阻塞模式(WNOHANG)下,无子进程退出,所有子进程仍在运行
-1 回收失败(如无未回收的子进程、参数错误),同时设置 errno

七、退出状态解析:核心宏

基于 wait/waitpid 的 status 参数,内核提供专用宏解析退出原因,仅在对应判断宏为真时,才能调用取值宏。

函数原型(直接调用)

// 正常退出相关
int WIFEXITED(int status);
int WEXITSTATUS(int status);

// 信号终止相关
int WIFSIGNALED(int status);
int WTERMSIG(int status);

// 进程停止/恢复相关(扩展)
int WIFSTOPPED(int status);
int WSTOPSIG(int status);
int WIFCONTINUED(int status);

核心参数/说明
宏作用适用场景
WIFEXITED 判断子进程是否正常退出(return/exit/_exit) 正常退出场景
WEXITSTATUS 获取正常退出的退出码(仅低 8 位) WIFEXITED 为真时
WIFSIGNALED 判断子进程是否被信号杀死(如 kill -9、段错误) 异常终止场景
WTERMSIG 获取杀死子进程的信号编号 WIFSIGNALED 为真时
WIFSTOPPED 判断子进程是否被信号停止(如 Ctrl+Z) 进程暂停场景
WSTOPSIG 获取使子进程停止的信号编号 WIFSTOPPED 为真时
WIFCONTINUED 判断子进程是否被信号恢复(如 fg 命令) 进程恢复场景
完整代码示例(含异常终止模拟)

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程:模拟被信号杀死(空指针解引用,触发段错误 SIGSEGV,信号编号 11)
int *p = NULL;
*p = 100; // 非法访问,触发信号
_exit(0); // 不会执行到
} else if (pid > 0) {
int status;
waitpid(pid, &status, 0);

// 完整解析退出状态
if (WIFEXITED(status)) {
printf("正常退出,退出码:%d\\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("被信号杀死,信号编号:%d\\n", WTERMSIG(status));
}
} else {
perror("fork 失败");
return 1;
}
return 0;
}

返回值解析
  • 布尔型宏(WIFEXITED/WIFSIGNALED 等):返回非 0 表示真,0 表示假。
  • 取值型宏(WEXITSTATUS/WTERMSIG 等):返回对应数值(退出码/信号编号)。

八、特殊进程:孤儿进程 & 僵尸进程(重点避坑)

1. 孤儿进程

定义

父进程先于子进程退出,子进程失去父进程,被 init 进程(PID=1) 或 systemd 进程收养。

核心特性
  • 子进程正常运行,不会变成僵尸进程。
  • 退出后由收养进程自动回收,无需手动处理。
代码示例

#include <stdio.h>
#include <unistd.h>

int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程:父进程退出后,子进程成为孤儿进程
printf("子进程 PID:%d,父进程 PID:%d\\n", getpid(), getppid());
sleep(10); // 等待父进程退出
printf("孤儿进程,新父进程 PID:%d\\n", getppid()); // 变为 1
} else if (pid > 0) {
// 父进程:立即退出
printf("父进程 PID:%d,即将退出\\n", getpid());
return 0;
} else {
perror("fork 失败");
return 1;
}
return 0;
}

2. 僵尸进程

定义

子进程已退出,但父进程未调用 wait/waitpid 回收其 PCB 资源,子进程残留在内核进程表中,状态为 Z(Zombie)。

危害
  • 占用 PID 和内核进程表项,系统 PID 数量有限,过多僵尸进程会导致无法创建新进程。
解决方法
  • 父进程主动调用 wait/waitpid 回收(推荐);
  • 父进程退出,子进程变为孤儿进程,由 init 进程回收;
  • 注册信号处理函数,捕获 SIGCHLD 信号,在信号处理函数中回收子进程(高级)。
  • 代码示例(模拟僵尸进程 + 解决)

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/wait.h>

    int main() {
    pid_t pid = fork();
    if (pid == 0) {
    // 子进程:运行 2 秒后退出
    printf("子进程 PID:%d,运行 2 秒后退出\\n", getpid());
    sleep(2);
    return 0;
    } else if (pid > 0) {
    // 父进程:休眠 10 秒,期间子进程退出成为僵尸进程
    printf("父进程 PID:%d,休眠 10 秒(此时子进程为僵尸进程)\\n", getpid());
    sleep(10);

    // 回收子进程,解决僵尸问题
    wait(NULL);
    printf("子进程已回收,僵尸进程消失\\n");
    sleep(5); // 验证是否还有僵尸进程
    } else {
    perror("fork 失败");
    return 1;
    }
    return 0;
    }

    九、进程组管理(扩展核心)

    1. getpgid – 获取进程组 ID

    函数原型

    #include <unistd.h>
    pid_t getpgid(pid_t pid);

    核心参数/说明
    • pid:进程 ID;若传入 0,表示获取当前进程的进程组 ID。
    • 作用:获取指定进程所属的进程组 ID。
    完整代码示例

    #include <stdio.h>
    #include <unistd.h>

    int main() {
    pid_t pid = getpid();
    pid_t pgid = getpgid(0); // 获取当前进程的进程组 ID
    printf("进程 PID:%d,进程组 ID:%d\\n", pid, pgid);
    return 0;
    }

    返回值解析
    • 成功:返回进程组 ID(非负整数)。
    • 失败:返回 -1,设置 errno。

    2. setpgid – 设置进程组(创建进程组)

    函数原型

    #include <unistd.h>
    int setpgid(pid_t pid, pid_t pgid);

    核心参数/说明
    参数含义
    pid 要设置的进程 ID;若为 0,表示当前进程
    pgid 要设置的进程组 ID;若为 0,表示以 pid 为进程组 ID(创建新进程组)
    核心规则 1. 进程只能设置自己或子进程的进程组;2. 子进程必须在执行 exec 前设置;3. 进程组组长的 PID = PGID
    完整代码示例(创建新进程组)

    #include <stdio.h>
    #include <unistd.h>

    int main() {
    pid_t pid = fork();
    if (pid == 0) {
    // 子进程:创建新进程组,自身为组长
    setpgid(0, 0);
    printf("子进程(组长)PID:%d,进程组 ID:%d\\n", getpid(), getpgid(0));
    sleep(10);
    } else if (pid > 0) {
    // 父进程:查看自身进程组
    printf("父进程 PID:%d,进程组 ID:%d\\n", getpid(), getpgid(0));
    sleep(10);
    } else {
    perror("fork 失败");
    return 1;
    }
    return 0;
    }

    返回值解析
    • 成功:返回 0。
    • 失败:返回 -1,设置 errno。

    十、进程管理常用命令(实战工具)

    命令核心作用常用参数示例
    ps 查看进程状态 aux:所有进程详细信息-ef:查看父子进程关系-o:自定义输出字段 `ps aux
    top 实时监控进程资源 M:按内存排序P:按 CPU 排序k:杀死指定进程 top -p 12345
    pstree 树状展示进程关系 -p:显示 PID-u:显示用户 pstree -p
    kill 向进程发送信号 -l:查看所有信号-9:强制杀死(SIGKILL)-15:优雅退出(SIGTERM) kill -9 12345
    killall 按进程名杀死进程 -9:强制杀死 killall -9 firefox
    pgrep 按进程名查询 PID -g:按进程组查询 pgrep ls
    赞(0)
    未经允许不得转载:171主机测评 » Linux 进程管理
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址