fork详解(附经典计算题)
概念
fork 函数可以创建一个新的进程,称为子进程,而调用 fork 的进程则为父进程。子进程是父进程的副本,它会获得父进程数据空间、堆、栈等资源的副本,但父子进程拥有相互独立的地址空间。这意味着,虽然子进程复制了父进程的这些资源,但后续父子进程对这些资源的修改不会相互影响 。同时,父子进程会共享父进程中打开的文件描述符,即父、子进程中相同编号的文件描述符在内核中指向同一个 file 结构体,file 结构体的引用计数会增加。
函数原型及返回值
函数原型:pid_t fork(void) ,该函数不需要传入参数。其中pid_t本质是int类型,在#include <sys/types.h>中定义,同时还需要包含头文件#include <unistd.h> 。
返回值:具有 “调用一次,返回两次” 的特点。如果创建子进程成功,在父进程中,fork 返回新创建子进程的进程 ID(大于 0 的整数);在子进程中,fork 返回 0。如果创建子进程失败,fork 返回 - 1,此时可以通过errno查看具体的错误原因,比如达到进程数上限(EAGAIN) 、没有足够空间给新进程分配(ENOMEM)等。
工作机制
当父进程调用 fork 函数时,操作系统会复制当前父进程的状态信息,包括代码段、数据段、堆栈指针、寄存器值等。子进程从 fork 函数调用之后的下一条指令开始执行。不过,在不同的 Linux 系统下,无法确定 fork 之后是子进程先运行还是父进程先运行,这取决于系统的调度策略。
实际应用场景
多任务处理:例如网络服务器程序中,父进程可以负责监听客户端的连接请求,每当有新的请求到来,就调用 fork 创建子进程,由子进程来处理具体的客户端请求,而父进程继续监听新的连接,这样可以实现并发处理多个客户端请求 。
执行不同程序 :shell 在执行命令时会用到 fork。子进程从 fork 返回后,可以调用 exec 系列函数,用新的程序替换当前进程的内存映像,从而执行不同的程序。在 UNIX 系统中,fork 和 exec 是分开的,这使得子进程在 fork 和 exec 之间有机会更改自身属性,如进行 I/O 重定向、修改用户 ID、安排信号处理等操作。
示例
fork(); // 第1个fork
fork() && fork() || fork(); // 第2、3、4个fork(组合)
fork(); // 第5个fork
不算main进程,共产生19个子进程
将上述5个fork分别记为F1、F2、F3、F4、F5。
F1产生一个子进程,F5将进程翻倍,重点考虑中间的3个,熟悉fork的返回值即可判断,&&只有当左边为真才考虑右边,||只有当左边为假才考虑右边
如图,main进程下得到5个执行到F5的进程,同理F1也可得到5个,共10个,F5再翻倍,共20个,减去1个main进程共19个子进程