作者说明 · 学习记录
本站作为作者的学习记录站,不保证文章内容严谨或完全正确。
本文由 ChatGPT 5 协助创作
本文部分内容由AI生成,最终版本由作者审核与修改。
版权信息

warning

本文章为博主原创文章。遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。


AI 辅助创作

在 Linux 编程中,进程(Process) 是操作系统分配资源和调度的基本单位。每个进程都有自己独立的内存空间、代码段、数据段和执行上下文。多个进程之间常常需要协作,这就涉及到 进程间通信(IPC, Inter-Process Communication)。本篇博客会系统介绍进程的基础操作,并对常见的 IPC 方式进行详细解释与示例,带你从零开始掌握。


1. 进程与 PID

每个进程都会有一个唯一的 进程号(PID),可以通过 getpid() 获取当前进程号,通过 getppid() 获取父进程号。这在调试和进程管理时非常重要。

1
2
3
4
5
6
7
#include <stdio.h>
#include <unistd.h>

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

⚠️ 注意:如果父进程先退出,子进程会被 init 进程接管,ppid 会变为 1。

2. 创建进程 — fork()

fork() 是创建新进程的主要方式。它会复制当前进程(父进程)的大部分内容,生成一个几乎一模一样的子进程。

1
pid_t pid = fork();

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <unistd.h>

int main() {
pid_t pid = fork();
if (pid == 0) {
printf("[子进程] pid=%d, ppid=%d\n", getpid(), getppid());
} else if (pid > 0) {
printf("[父进程] pid=%d, 子进程pid=%d\n", getpid(), pid);
} else {
perror("fork 失败");
}
return 0;
}

caution

  • 子进程不会继承父进程的所有资源,例如文件锁。

  • fork() 之后,父子进程执行顺序不确定,需要通过 wait() 或其他机制控制。

3. 程序替换 — exec() 系列

子进程常常需要运行一个新的程序,这就需要 exec() 系列函数(如 execlp, execvp 等)。
exec 系列函数的作用是:用新的程序替换当前进程的代码段,而进程的 PID、文件描述符(除非设置了 FD_CLOEXEC)等保持不变。它不会创建新进程,而是在当前进程内加载并运行新程序。

常见函数

exec 系列一共有 6 个常用函数,主要区别在于 参数传递方式是否依赖 PATH 环境变量

函数 参数传递方式 是否搜索 PATH 说明
execl(path, arg0, arg1, ..., NULL) 列表(逐个传参) 最常见,参数以不定长列表形式传递,最后必须以 NULL 结尾。
execv(path, argv[]) 数组 参数通过字符串数组传递,方便动态构造参数。
execlp(file, arg0, arg1, ..., NULL) 列表 execl 类似,但会在 PATH 中搜索可执行文件。
execvp(file, argv[]) 数组 execv 类似,但会在 PATH 中搜索可执行文件。
execle(path, arg0, arg1, ..., NULL, envp[]) 列表 可以自定义环境变量,最后参数需提供 envp
execve(path, argv[], envp[]) 数组 最底层的系统调用,其它 exec 函数最终都调用它。

说明:

使用示例

  1. execl:用绝对路径调用程序
1
2
3
4
5
6
7
8
9
#include <unistd.h>
#include <stdio.h>

int main() {
printf("Before exec\n");
execl("/bin/ls", "ls", "-l", NULL);
// 只有 exec 调用失败时才会继续执行
perror("execl failed");
return 1; }
  1. execvp:在 PATH 中搜索命令
1
2
3
4
5
6
7
8
9
#include <unistd.h>
#include <stdio.h>

int main() {
char *argv[] = {"ls", "-a", NULL};
execvp("ls", argv);
perror("execvp failed");
return 1;
}
  1. execle:传入自定义环境变量
1
2
3
4
5
6
7
8
9
#include <unistd.h>
#include <stdio.h>

int main() {
char *envp[] = {"MYVAR=HelloExec", NULL};
execl("/usr/bin/env", "env", NULL, envp);
perror("execle failed");
return 1;
}

注意事项

4. 等待子进程 — wait() / waitpid()

父进程需要回收子进程退出时的资源,否则子进程会成为 僵尸进程(Zombie)。

这两个函数用于 父进程等待子进程结束回收资源,避免僵尸进程。

1. wait()

1
2
3
4
5
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

pid_t wait(int *status);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
pid_t pid = fork();
if (pid == 0) { // 子进程
printf("Child running...\n");
return 42;
} else { // 父进程
int status;
pid_t cpid = wait(&status); // 等待子进程
if (WIFEXITED(status)) { // 判断是否正常退出
printf("Child %d exited with code %d\n", cpid, WEXITSTATUS(status));
}
}
return 0;
}

caution

  • wait() 会阻塞父进程,直到任意一个子进程退出。

  • 如果父进程没有子进程,返回 -1 并设置 errno = ECHILD

2. waitpid()

1
2
3
4
5
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

pid_t waitpid(pid_t pid, int *status, int options);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>

int main() {
pid_t pid = fork();
if (pid == 0) { // 子进程
sleep(2);
return 100;
} else { // 父进程
int status;
pid_t cpid;
while ((cpid = waitpid(pid, &status, WNOHANG)) == 0) {
printf("Child still running...\n");
sleep(1);
}
if (WIFEXITED(status)) {
printf("Child %d exited with code %d\n", cpid, WEXITSTATUS(status));
}
}
return 0;
}

caution

  • waitpid() 可以实现 非阻塞等待,适合父进程同时管理多个子进程。

  • WIFEXITED(status) 判断子进程是否正常退出。

  • WIFSIGNALED(status) 判断子进程是否被信号终止。

  • 使用 waitpid() 可以精准回收指定子进程,避免僵尸进程积累。

tips

  • 父进程只需要等待任意子进程 → 用 wait() 即可。

  • 父进程需要精确控制或非阻塞 → 用 waitpid()

  • 调用前一定要理解 阻塞 vs 非阻塞,避免父进程被卡住。

5. 进程退出 — exit()_exit()

⚠️ 区别:exit() 适合普通退出,_exit() 适合在 fork() 后不希望影响父进程的环境时使用。

caution

  • 子进程执行退出函数后,子进程结束并变为僵尸🧟,内核保留子进程在进程表中的部分信息(PID、退出码等)占用少量系统资源,但不会再运行。需要父进程调用wait释放子进程资源,净化僵尸,释放灵魂👻

6. ps 命令简述

ps 命令的常见用法

1. 查看当前终端中的进程

这是最简单的用法,不加任何选项,ps 命令只会显示当前终端会话中运行的进程。

1
ps

运行后你可能会看到类似下面的输出:

1
2
3
PID TTY          TIME CMD
1234 pts/0 00:00:00 bash
5678 pts/0 00:00:00 ps

2. 查看所有进程

如果你想查看系统中所有用户的所有进程,可以使用 -e 选项(代表所有进程)或 -a 选项(代表所有进程,除了会话引导进程)。

1
ps -e

或者使用 -ef 组合,这是最常用的组合之一,它提供了更详细的信息:

1
ps -ef

3. 查看用户进程

如果你只想查看某个特定用户(例如 root)的进程,可以使用 -u 选项。

1
ps -u root

4. 通过管道和 grep 查找特定进程

在实际工作中,你通常会需要查找某个特定的进程,比如 Nginx 或 MySQL。这时,ps 命令通常会配合 grep 命令一起使用。grep 可以从 ps 的输出中过滤出你想要的信息。

例如,查找所有与 Nginx 相关的进程:

1
ps -ef | grep nginx

这里的 | 是管道符,它的作用是将 ps -ef 命令的输出作为 grep 命令的输入。

ps 命令的常用选项总结

选项 描述
-e 显示所有进程
-f 显示完整格式的列表
-a 显示所有进程(不包括会话引导进程)
-u 按用户过滤进程
-aux 最经典的组合,显示所有用户的详细进程信息

实际应用示例

假设你想查看当前系统中 CPU 或内存占用最高的几个进程,ps 命令可以与 headsort 等命令结合使用。

查看 CPU 占用最高的 5 个进程:

1
ps -aux --sort=-%cpu | head -n 6

这里的 --sort=-%cpu 表示按 CPU 占用率降序排列,head -n 6 则取前 6 行(包括标题行)。

解析 ps -aux 命令的输出

当你运行 ps -aux 命令时,你会看到一个包含多个列的表格。每一列都提供了关于进程的重要信息。下面是对这些列的详细解释:

示例解析

假设你运行 ps -aux 看到以下一行输出:

1
root      1234  0.0  0.1 23456 1234 ?        S    Sep01  0:05 /usr/sbin/sshd -D

通过这些信息,你可以快速了解一个进程是由谁运行的、它消耗了多少资源、处于什么状态,以及它是什么程序。这对于系统监控和故障排除非常有帮助。

Linux-Process

创建时间:9月 10, 2025

最后更新:9月 12, 2025

字数统计:3.1k字

预计阅读:12min