第3课_使用fork系统调用创建进程
热度🔥:22 免费课程
授课语音
使用 fork
系统调用创建进程
1. fork
系统调用概述
fork
是一个系统调用,用于在 Unix-like 操作系统中创建一个新进程。它会复制当前进程(父进程),并返回两次:一次在父进程中,返回新创建子进程的进程ID;另一次在子进程中,返回0。父子进程在此后并行运行,互相独立。
1.1 fork
的基本原理
fork
系统调用会创建一个与父进程几乎完全相同的子进程。子进程拥有与父进程相同的代码、数据、堆栈和打开的文件描述符,但它们拥有独立的进程ID、内存空间和进程表项。
- 父进程:调用
fork
的进程,返回值为子进程的进程ID。 - 子进程:由
fork
创建的新进程,返回值为0。
2. fork
的行为
2.1 父进程与子进程的区分
当 fork
被调用时,操作系统会进行以下操作:
- 复制父进程的内存空间。
- 创建一个新的进程ID(PID)给子进程。
- 子进程继承父进程的所有资源,如打开的文件描述符、信号处理方式等。
2.2 返回值
- 在父进程中,
fork
返回子进程的进程ID(大于0)。 - 在子进程中,
fork
返回0。
2.3 父子进程的独立性
父子进程在执行过程中是独立的。它们可以并发执行,并且有各自独立的进程控制块(PCB)。修改子进程的数据不会影响父进程的数据,反之亦然。
3. 使用 fork
创建进程
3.1 基本语法
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid = fork(); // 创建子进程
if (pid < 0) {
// 错误处理:fork失败
perror("Fork failed");
return 1;
} else if (pid == 0) {
// 子进程的代码
printf("This is the child process. PID: %d\n", getpid());
} else {
// 父进程的代码
printf("This is the parent process. PID: %d, Child PID: %d\n", getpid(), pid);
}
return 0;
}
3.2 代码解析
fork()
:创建一个新进程,并返回进程ID。父进程中返回子进程的ID,子进程中返回0。getpid()
:返回当前进程的进程ID。pid_t pid
:pid
是fork
函数的返回值类型,表示进程ID。
3.3 输出示例
This is the parent process. PID: 1234, Child PID: 1235
This is the child process. PID: 1235
- 父进程和子进程的 PID 会不同。
- 输出的顺序可能不同,因为父子进程是并发执行的。
4. fork
的常见问题
4.1 子进程的资源继承
- 文件描述符:父进程打开的文件描述符会被子进程继承,但文件描述符指向的文件是父子进程共享的,文件的读写操作会影响父子进程。
- 环境变量:子进程会继承父进程的环境变量,但它可以修改自己的环境变量。
- 内存空间:父子进程有独立的地址空间,修改一个进程的内存不会影响另一个进程。
4.2 僵尸进程(Zombie Process)
当子进程执行完毕并退出时,它会将退出状态传给父进程。如果父进程未调用 wait
或 waitpid
函数来收集子进程的退出状态,子进程将会变为“僵尸进程”,直到父进程清理它为止。
4.3 孤儿进程(Orphan Process)
如果父进程在子进程执行之前退出,子进程会被 init
进程(进程ID为1)收养。孤儿进程的父进程是 init
。
5. fork
的进阶用法:exec
系列函数
子进程创建后,通常会使用 exec
系列函数来加载新的程序,从而实现程序的替换。fork
和 exec
常常一起使用:
exec
系列函数:用来加载一个新的程序,替换当前进程的内存空间。- 常见的
exec
函数:execl
、execp
、execv
等。
5.1 示例:使用 fork
和 exec
执行新的程序
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
perror("Fork failed");
return 1;
} else if (pid == 0) {
// 子进程执行新的程序
execlp("/bin/ls", "ls", "-l", NULL);
// 如果 execlp 调用成功,这一行代码不会被执行
perror("Exec failed");
} else {
// 父进程代码
printf("This is the parent process. PID: %d\n", getpid());
}
return 0;
}
5.2 代码解析
- 在子进程中,
execlp
会替换当前进程的代码并执行/bin/ls
程序,输出目录列表。 - 如果
execlp
调用失败,会打印错误信息。
6. 总结
fork
是 Unix-like 操作系统中创建新进程的核心系统调用。- 父进程和子进程通过返回值来区分,父进程返回子进程的进程ID,子进程返回0。
- 父子进程之间独立执行,但它们继承了很多资源,如文件描述符、环境变量等。
- 在使用
fork
时,要注意管理僵尸进程和孤儿进程。 fork
和exec
常常结合使用,通过exec
系列函数实现进程的替换。