Linux 进程控制是操作系统对进程生命周期(创建、运行、终止、资源回收)的管理机制,核心涉及进程创建、进程终止、进程等待、进程程序替换等操作,以及相关的关键概念(如僵尸进程、孤儿进程)。
进程创建的核心是通过系统调用从已存在的进程(父进程)生成新进程(子进程),Linux 中最常用的是 fork()
函数,另有 vfork()
(较少使用)。
fork()
是创建新进程的主要系统调用,功能是复制当前进程(父进程)的地址空间,生成一个几乎完全相同的子进程。
vfork()
也用于创建子进程,但与 fork()
有本质区别:
- 子进程共享父进程的地址空间(不复制),即父子进程操作同一块内存;
- 子进程优先运行,直到子进程调用
exit()
或 exec
系列函数后,父进程才会恢复运行。
由于共享地址空间容易引发冲突(如子进程修改变量影响父进程),且存在安全风险,现在已很少使用,推荐用 fork()
。
进程终止即进程生命周期结束,分为正常终止和异常终止。
-
main 函数返回:return n
等价于 exit(n)
(会触发 exit
函数)。
-
调用 exit ():标准库函数,会先刷新缓冲区(如 printf
的输出缓冲区),再调用 _exit()
终止进程。
-
调用 _exit ()/_Exit ():系统调用(或标准库的底层实现),直接终止进程,不刷新缓冲区。
-
exit 与 _exit 的区别:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
printf("Hello");
exit(0);
}
由信号(Signal)触发,如:
- 手动输入
Ctrl+C
(触发 SIGINT
信号);
- 访问非法内存(触发
SIGSEGV
信号);
- 除以 0(触发
SIGFPE
信号)。
父进程需要等待子进程终止并回收其资源(如 PCB、内存),否则子进程会变成僵尸进程(占用系统资源)。进程等待通过 wait()
或 waitpid()
实现。
比 wait()
更灵活,可指定等待的子进程、设置非阻塞等。
-
原型:
pid_t waitpid(pid_t pid, int *status, int options);
-
参数:
pid
:指定等待的子进程(pid > 0
等待 PID 为 pid
的子进程;pid = -1
等待任意子进程,同 wait()
);
status
:接收退出状态(需用宏解析,如 WIFEXITED(status)
判断是否正常终止,WEXITSTATUS(status)
获取退出码);
options
:WNOHANG
表示非阻塞(若子进程未终止,立即返回 0)。
-
示例:父进程等待子进程终止并获取退出码
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
sleep(2);
return 10;
}
int status;
pid_t ret = waitpid(pid, &status, 0);
if (ret > 0) {
if (WIFEXITED(status)) {
printf("子进程 %d 正常终止,退出码:%d\n", ret, WEXITSTATUS(status));
}
}
return 0;
}
进程程序替换是用新程序的代码和数据覆盖当前进程的地址空间,实现 “运行另一个程序” 的效果(进程 PID 不变,仅替换代码、数据、堆栈等)。通过 exec
系列函数实现。
exec
有 6 个变体,区别在于参数传递方式、是否带路径、是否自定义环境变量:
函数名 |
特点(参数形式 / 路径 / 环境变量) |
execl |
参数为列表(const char *path, const char *arg, ... ),需完整路径 |
execlp |
同 execl ,但可自动搜索 PATH 环境变量(无需完整路径) |
execle |
同 execl ,且可自定义环境变量(最后一个参数为环境变量数组) |
execv |
参数为数组(const char *path, char *const argv[] ),需完整路径 |
execvp |
同 execv ,可自动搜索 PATH |
execvpe |
同 execvp ,可自定义环境变量 |
- 示例:子进程替换为
ls -l
命令
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("子进程替换为 ls -l...\n");
execlp("ls", "ls", "-l", NULL);
perror("exec error");
exit(1);
}
wait(NULL);
return 0;
}
-
僵尸进程:
子进程终止后,父进程未调用 wait()
/waitpid()
回收资源,子进程的 PCB 仍保留在系统中(仅占少量内存)。僵尸进程过多会耗尽进程号资源,导致无法创建新进程。
解决:父进程及时调用 wait()
回收;若父进程已退出,僵尸进程会被 init
(或 systemd
)收养并回收。
-
孤儿进程:
父进程先于子进程终止,子进程会被 init
(或 systemd
)进程(PID=1)收养,成为 “孤儿进程”。孤儿进程终止后会被收养者回收,不会成为僵尸进程。
Linux 进程控制通过 fork()
创建进程、exit()
终止进程、wait()
/waitpid()
回收资源、exec
系列函数替换程序,核心目标是管理进程生命周期,避免资源泄漏(如僵尸进程),确保系统稳定运行。