文章目录
- 一、基本概念
- 1. fork() 系统调用
- 2. exec() 系列函数
- 二、典型使用场景
- 1. 创建子进程执行新程序
- 2. 父子进程执行不同代码
- 三、核心区别与注意事项
- 四、组合使用技巧
- 1. 重定向子进程的输入/输出
- 2. 创建多级子进程
- 五、常见问题与解决方案
- 僵尸进程(Zombie Process)
- 孤儿进程(Orphan Process)
- 六、总结
在UNIX/Linux系统中,fork() 和 exec() 是创建和运行新进程的核心系统调用。以下是它们的详细解释和典型用法。
一、基本概念
1. fork() 系统调用
功能:创建当前进程的一个子进程,子进程是父进程的几乎完全复制(包括内存空间、文件描述符等)。
返回值:
父进程:返回子进程的进程ID(PID,正整数)。
子进程:返回0。
错误:返回-1(如内存不足或进程数达到上限)。
2. exec() 系列函数
功能:用新程序替换当前进程的内存空间、代码段和数据段,执行新程序。
常见函数:
execl(const char *path, const char *arg, ...);
execv(const char *path, char *const argv[]);
execle(const char *path, const char *arg, ..., char *const envp[]);
execve(const char *path, char *const argv[], char *const envp[]);
execlp(const char *file, const char *arg, ...);
execvp(const char *file, char *const argv[]);
区别:l 表示参数列表(list),v 表示参数数组(vector),p 表示自动搜索PATH路径,e 表示自定义环境变量。
二、典型使用场景
1. 创建子进程执行新程序
#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(EXIT_FAILURE);
} else if (pid == 0) { // 子进程
// 执行新程序(如/bin/ls)
execl("/bin/ls", "ls", "-l", NULL);
// 如果exec返回,说明执行失败
perror("exec failed");
exit(EXIT_FAILURE);
} else { // 父进程
int status;
waitpid(pid, &status, 0); // 等待子进程结束
printf("Child process exited with status %d\n", WEXITSTATUS(status));
}
return 0;
}
执行流程:
父进程调用 fork() 创建子进程。
子进程调用 execl() 加载 /bin/ls 程序,替换自身代码。
父进程通过 waitpid() 等待子进程结束,并获取退出状态。
2. 父子进程执行不同代码
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
} else if (pid == 0) { // 子进程
printf("Child process (PID=%d)\n", getpid());
// 子进程逻辑...
} else { // 父进程
printf("Parent process (PID=%d, Child PID=%d)\n", getpid(), pid);
// 父进程逻辑...
}
return 0;
}
关键点: 父子进程通过 fork() 的返回值区分执行逻辑。 子进程继承父进程的文件描述符和内存状态(但后续修改互不影响)。
三、核心区别与注意事项
四、组合使用技巧
1. 重定向子进程的输入/输出
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
pid_t pid = fork();
if (pid == 0) { // 子进程
// 重定向标准输出到文件
int fd = open("output.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
dup2(fd, STDOUT_FILENO); // 将标准输出(1)替换为文件描述符fd
close(fd);
// 执行ls命令,输出会写入文件而非终端
execl("/bin/ls", "ls", "-l", NULL);
perror("exec failed");
} else {
wait(NULL); // 等待子进程结束
printf("Output redirected to output.txt\n");
}
return 0;
}
2. 创建多级子进程
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid1 = fork();
if (pid1 == 0) { // 一级子进程
printf("First child (PID=%d)\n", getpid());
pid_t pid2 = fork();
if (pid2 == 0) { // 二级子进程
printf("Second child (PID=%d, Parent=%d)\n", getpid(), getppid());
} else {
wait(NULL); // 一级子进程等待二级子进程
}
} else {
wait(NULL); // 父进程等待一级子进程
}
return 0;
}
五、常见问题与解决方案
僵尸进程(Zombie Process)
问题:子进程结束后,父进程未调用 wait() 获取其退出状态,导致子进程残留进程表项。
解决方案:
父进程通过 wait() 或 waitpid() 回收子进程资源。
信号处理:捕获 SIGCHLD 信号并在处理函数中调用 wait()。
孤儿进程(Orphan Process)
问题:父进程先于子进程结束,子进程被init进程(PID=1)收养。
影响:孤儿进程仍可正常运行,但可能导致资源管理复杂化。
exec() 失败处理
原因:路径错误、权限不足、文件损坏等。
检查方法:调用 exec() 后立即检查返回值(若返回则表示失败),并通过 errno 获取具体错误码。
六、总结
fork() 用于创建新进程,父子进程并行执行相同代码(通过返回值区分逻辑)。
exec() 用于加载并执行新程序,替换当前进程的内存空间。
组合使用:先 fork() 创建子进程,再在子进程中 exec() 执行新程序,实现多任务处理。
理解这两个系统调用是掌握UNIX/Linux进程管理的基础,在开发多进程应用(如服务器、shell脚本解释器)时尤为重要。