一、fork( )的概念
fork()
是一个在 Unix 系统中创建新进程的系统调用。调用 fork()
会创建一个新的子进程,这个子进程是调用进程的副本。新进程(子进程)和原始进程(父进程)在调用 fork()
之后会拥有相同的内存映像、文件描述符和环境。子进程从调用 fork()
的位置开始执行,而父进程继续执行。通常情况下,fork()
被用于创建一个新的进程,然后在新的进程中执行不同的程序,这个过程称为进程创建。
在 fork()
调用后,父进程和子进程的执行路径会分开,它们可以并行执行不同的任务。父进程会得到子进程的进程 ID(PID),而子进程会得到 0 作为返回值。因此,通过 fork()
调用的返回值可以确定当前代码是在父进程中执行还是在子进程中执行。
二、fork( )的使用
下面是一个简单的示例,演示了 fork()
的基本用法:
#include<stdio.h>
#include<unistd.h>
int main(void)
{
int ret;
// 调用 fork() 函数创建子进程
ret = fork();
// 检查 fork() 返回值
if(-1 == ret){
// 如果返回值为 -1,表示 fork() 调用失败
perror("fork error"); // 打印错误信息
return 1; // 退出程序,返回非零值表示错误
}
else if(0 == ret){
// 如果返回值为 0,表示当前代码是在子进程中执行
printf("I am child, PID = %d, PPID = %d\n", getpid(), getppid()); // 打印子进程的 PID 和父进程的 PID
}
else
{
// 如果返回值大于 0,表示当前代码是在父进程中执行,返回值为子进程的 PID
printf("I an father, PID = %d, PPID = %d, CPID = %d\n", getpid(), getppid(), ret); // 打印父进程的 PID、父进程的 PPID 和子进程的 PID
}
return 0; // 返回 0 表示正常退出
}
这段代码通过调用 fork()
函数创建子进程,并根据 fork()
的返回值判断当前代码是在父进程还是子进程中执行。在父子进程中分别打印出相应的进程信息,如进程 ID(PID)、父进程 ID(PPID)等。输出结果如下:
I an father, PID = 96774, PPID = 3088, CPID = 96775
I am child, PID = 96775, PPID = 96774
通过结果就能很明显的看出父进程和子进程他们之间的关系。
三、fork( )的注意事项
在使用 fork()
函数时需要注意以下几个方面:
-
错误处理:在调用
fork()
函数后,需要检查返回值以处理错误情况。如果返回值小于 0,则表示fork()
调用失败,需要进行错误处理。 -
父子进程的区别:在调用
fork()
后,父进程和子进程会执行相同的代码,但它们拥有不同的内存空间。需要注意的是,子进程是父进程的副本,但是它们有各自独立的内存空间,因此在它们的内存空间中做出的更改不会影响对方。 -
返回值:
fork()
在父进程中返回子进程的进程 ID(PID),在子进程中返回 0。因此,通过返回值可以确定当前代码是在父进程中执行还是在子进程中执行。 -
文件描述符:父进程和子进程会继承相同的文件描述符。需要注意的是,在
fork()
之后,父子进程共享相同的打开文件描述符。因此,如果在父进程中打开了文件,子进程也会继承这些打开的文件。在子进程中关闭文件描述符时要小心,以避免对父进程中使用的文件造成影响。 -
资源释放:父进程和子进程会共享一些系统资源,如打开的文件、内存分配等。需要在适当的时候释放这些资源,以避免资源泄露或竞争条件。
-
僵尸进程和孤儿进程:在使用
fork()
创建子进程时,需要确保适当地回收子进程的资源,以避免产生僵尸进程。可以使用wait()
或waitpid()
函数等待子进程的结束,并处理子进程的退出状态。另外,如果子进程的父进程提前结束,而子进程还在运行,那么子进程会变成孤儿进程,这时候可以将其重新绑定到 init 进程,即通过setsid()
函数创建新会话,或者将其父进程 ID 设置为 1。
做大做强,再创辉煌!