Linux系统编程——fork函数的使用方法
在 Linux 系统编程 中,fork()
函数是创建新进程的关键系统调用。fork()
在当前进程(父进程)中创建一个几乎完全相同的子进程。子进程和父进程从调用 fork()
的位置继续执行,但它们是两个独立的进程,每个进程都有自己的进程标识符(PID)和资源。
以下是 fork()
函数的详细使用方法和注意事项。
1. fork()
函数简介
函数原型:
#include <unistd.h>
pid_t fork(void);
-
返回值:
-
如果
fork()
调用成功,父进程 返回子进程的 PID(正整数),子进程 返回 0。 -
如果调用失败,返回 -1,错误信息可以通过
errno
获取。
-
-
PID(进程标识符):
-
父进程可以通过返回值得到子进程的 PID。
-
子进程的 PID 是由操作系统自动分配的。
-
-
子进程的资源:
-
子进程从父进程复制了几乎所有的资源,包括内存、文件描述符、环境变量等。
-
子进程有自己的地址空间,与父进程独立运行。
-
2. fork()
的工作原理
fork()
系统调用会创建一个与父进程几乎相同的子进程。子进程有自己独立的进程 ID(PID),并从 fork()
函数返回。父进程和子进程都从 fork()
之后的位置开始执行。父子进程的 代码、数据、堆、栈 等内容是相同的,但它们有各自的内存空间。
-
父进程:
fork()
返回子进程的 PID。 -
子进程:
fork()
返回 0。
3. 简单示例
下面是一个简单的 fork()
示例,展示了父子进程的不同返回值。
示例代码:
#include <stdio.h>
#include <unistd.h>int main() {pid_t pid = fork(); // 创建一个子进程if (pid < 0) {// 错误处理,fork() 失败perror("fork failed");return 1;}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;
}
说明:
-
fork()
被调用后,父进程和子进程都从同一位置开始执行。 -
父进程:
pid
存储子进程的 PID,因此父进程会打印 "This is the parent process" 以及子进程的 PID。 -
子进程:
pid
为 0,因此子进程会打印 "This is the child process"。
输出示例:
This is the parent process, PID = 1234, Child PID = 1235
This is the child process, PID = 1235
4. fork()
的使用场景
fork()
函数常用于以下几种场景:
-
创建子进程:在多进程系统中,通过
fork()
创建子进程来执行并发任务。 -
执行子程序:父进程创建子进程后,子进程可以通过
exec()
函数系列来执行不同的程序。例如,父进程创建子进程后,子进程可以执行ls
、cat
等命令。 -
后台任务:父进程创建子进程后,可以让子进程在后台执行某些任务,父进程继续执行其他任务。
5.fork()创建子进程执行并发任务
我们可以使用fork模拟服务器不断等待用户输入,并且每次得到正确的输入都与之进行交互的场景。
示例:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main()
{pid_t pid;int data;while(1){printf("input your data:\n");scanf("%d",&data);if(data == 1){pid = fork();if(pid == 0){while(1){printf("do net request...pid = %d\n",getpid());sleep(3);}}else{}}else{printf("do nothing\n");}}return 0;
}
编译执行后:
input your data:
4
do nothing
input your data:
1
input your data:
do net request...pid = 101459
do net request...pid = 101459
4
do nothing
input your data:
do net request...pid = 101459
do net request...pid = 101459
1
input your data:
do net request...pid = 101460
do net request...pid = 101459
do net request...pid = 101460
1
input your data:
do net request...pid = 101461
do net request...pid = 101459
do net request...pid = 101460
do net request...pid = 101461
do net request...pid = 101459
do net request...pid = 101460
do net request...pid = 101461
从输出可以看到,当系统得到的输入为1时,就会创建一个子进程,子进程会不断地打印“do net request...”并打印出来子进程的PID。子进程不断执行的同时,也不会影响我们重新再对系统进行输入,当得到的输入为1时,就会再次创建一个子进程。
6. fork()
的注意事项
1. 父子进程共享文件描述符
fork()
创建的子进程会继承父进程的所有文件描述符,但文件描述符指向的文件句柄会被子进程和父进程共享。因此,父子进程可以共享文件,但在文件内容修改时要注意同步。
2. 父子进程的资源独立
尽管父子进程最初几乎相同,但它们是独立的。对某一进程的资源(如内存)做出的修改不会影响到另一个进程。
3. 进程的 PID
-
父进程:
fork()
返回子进程的 PID。 -
子进程:
fork()
返回 0,子进程的 PID 是父进程返回的 PID。
4. fork()
调用失败的处理
fork()
调用可能会失败,特别是在系统资源不足时。此时,fork()
会返回 -1,表示创建子进程失败。需要通过 errno
获取错误原因(如 ENOMEM
)。
if (pid < 0) {perror("fork failed");exit(1);
}
5. fork()
与 exit()
配合使用
当父进程或子进程完成任务后,通常会调用 exit()
来终止进程。父进程和子进程的退出不会自动结束另一进程,除非显式调用 exit()
或使用 wait()
等等待机制来同步进程。
7. 进程的父子关系
每个进程都有一个父进程,创建它的进程称为“父进程”。通过 fork()
,父进程创建子进程。父进程可以通过 wait()
等函数等待子进程完成,而子进程通过 getppid()
可以获取父进程的 PID。
示例:父进程等待子进程结束
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t pid = fork(); // 创建子进程if (pid < 0) {perror("fork failed");return 1;}if (pid == 0) {// 子进程执行printf("Child process with PID %d\n", getpid());return 0; // 子进程结束} else {// 父进程等待子进程结束wait(NULL); // 等待任何子进程结束printf("Parent process with PID %d\n", getpid());}return 0;
}
8. fork()
和 exec()
配合使用
fork()
通常与 exec()
系列函数一起使用。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;}if (pid == 0) {// 子进程执行新的程序execlp("/bin/ls", "ls", "-l", NULL); // 执行 ls -l 命令} else {// 父进程继续执行wait(NULL); // 等待子进程结束printf("Parent process finished\n");}return 0;
}
总结
-
fork()
用于创建一个新的子进程,父进程和子进程从fork()
返回的地方开始继续执行。 -
父进程和子进程 在
fork()
调用之后拥有独立的内存空间和资源,但共享文件描述符。 -
fork()
调用失败 时返回-1
,需要处理错误。 -
使用
wait()
可以使父进程等待子进程结束,避免产生僵尸进程。 -
exec()
系列函数通常与fork()
配合使用,允许子进程执行新的程序。
fork()
是多进程编程的基础,理解 fork()
的行为和父子进程的关系对于进行系统编程和开发多进程应用非常重要。