我有一万种方法让你对系统调用 - fork 了如指掌

Posted 做1个快乐的程序员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我有一万种方法让你对系统调用 - fork 了如指掌相关的知识,希望对你有一定的参考价值。

1、认识并使用fork

  我们知道fork是一个系统调用函数,其作用是创建子进程。这个fork有许多细枝末节的难点和知识点需要我们学习并熟练掌握,我们接下来将采用代码实验的方法,深入学习fork的一些操作和难点。

1.1 查看fork使用手册

  fork函数其返回值是一个pid_t类型的值,说白了就是一个无符号整型unsigned int,其形参为void,说明不用传任何参数。函数调用成功给父进程返回创建子进程的pid,并给子进程返货0,创建失败返回-1。

1.2 使用fork
  写一段代码来感受一些fork的作用及返回值。

图一:程序代码
图二:实验结果
代码和运行结果如上图:当我们调用fork时:这时打印出了两个进程的pid和ppid。其中pid为18980的进程是我们自己的进程,pid为18981的进程是我们创建出来的子进程,其父进程就是18980, 说明这两个进程是父子关系。

  像我们下面的代码,printf(“I am running…\\n”);是哪一个进程执行的?答案是当前进程,即上面的父进程,因为 fork是创建子进程,创建子进程的前提是我自己这个进程要先运行,printf是在子进程创建之前运行,也是当前进程执行。所以fork之前的代码是父进程执行,fork之后的代码(图里的while)默认是父子都可执行的。

1.3 fork两个返回值的使用
  fork函数会有两次返回值,给父进程返回子进程的pid,给子进程返回0。我们可以根据fork()的这一特点来完成多执行流的运行。我们之前的时候,像if-else这种多分支判定永远只能有一个执行流,if和else if是不可能同时执行的,但是现在在这里,是可以的。

图一:程序代码
图二:实验结果
  这样,我们就可以把子进程和父进程做的事情分离了。他们是同时进行的,但是如果是单核CPU,他们还是不断进行切换的。

2、深入了解fork的底层

  我们知道,fork是创建子进程,子进程也是一个新的进程,也是要被操作系统管理的,操作系统管理进程是要先描述再组织,描述就是将进程的各种信息封装成数据结构,管理就是操作系统对进程的管理就变成了对集成的数据结构的管理,这就是操作系统四大功能之一进程管理的本质。
  那么,fork()创建子进程的过程,OS做了什么?操作系统要为子进程创建其自己的数据结构,包括PCB - task-struct,进程地址空间 - mm_struct,页表、文件系统等等,并且子进程的数据和代码来源就是父进程,所以fork后父子进程才能同时执行父进程原有的代码。

2.1 fork为何要给子进程返回0,给父进程返回子进程的pid
  在现实世界中,任何一个小孩都只有一个父亲,但是一个父亲可以对应多个小孩,所以 父:孩 = 1:n 。对于父亲来说,回到家面对多个孩子肯定要具体到人名,而孩子却不需要,只需要叫爸,即父子进程立场: 父进程不需要标识,子进程需要标识。
  我们fork一个子进程是要子进程执行任务,父进程需要区分子进程,而子进程不需要,因此 我们需要给父进程返回子进程的pid本质就是父进程需要通过pid区分子进程。 子进程不需要返回父进程,不需要知道父进程是谁,因为任务是父进程给他的,他只需要知道自己调用成功与否就可以,因此给自己返回0。
🍋🍋🍋🍋🍋🍋🍋🍋 🍇🍇🍇🍇🍇🍇🍇🍇🍋🍋🍋🍋🍋🍋🍋🍋 🍇🍇🍇🍇🍇🍇🍇🍇🍋🍋🍋🍋🍋🍋🍋🍋 🍇🍇🍇🍇🍇🍇

2.2 如何理解fork有两个返回值的问题?(重点)
fork之前只有一个进程-父进程在运行,在fork之后就有了两条执行流:id=0为子,id>0为父。
  fork是函数吗?-是的,我们说过fork是系统调用。fork是函数的话,就不能将其简单的看成一个过程调用,我们眼中要有一个概念: fork子进程的时候要做的工作就是拷贝父进程形成子进程对应的数据结构,包括task_struct、mm_struct、页表、文件、其他信息等,当我们拷贝父进程的过程中,形成子进程,OS也要对子进程进行管理,管理子进程也要描述+组织,上面进行了描述,组织就是将task_struct链接进入系统的进程列表中,有可能还要进入runqueue中,然后返回pid。

  在上面的过程中,子进程就创建好了吗?OS就能看到多了一个task_struct吗?-能看到!既然多了一个task_struct,那么操作系统就可以调用父子两个进程,所以多执行流在OS管理(描述+组织)子进程的时候就已经开始了。fork是代码的结合,所以多执行流不是在fork之后才开始,而是在fork内部就已经开始了。
  所以在 return pid之前就已经是两个执行流了,父进程执行父进程代码,子进程执行子进程代码,两个进程都要return pid,所以fork的返回值为pid_t,会有一个变量id来接受返回值,本质就是对该变量进行写入,两个进程对同一个变量进行写入,他们的虚拟地址是一样,但是物理地址一定不一样,因为写入时会发生写时拷贝。

  综上,在fork内部两个执行流已经开始分别执行,最后的return pid也是分开执行的,所以会返回两次。不要理解为fork函数完成的功能是调用结束返回两个返回值,fork只要把task_struct创建出来,将task_struct添加到运行队列中,那么fork的核心工作已经完了,最后的返回是为了让父子进程得到结果。

3、fork常见问题

3.1 fork常规用法
  (1.一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
  (2.一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

3.2 fork调用失败的原因
  (1.系统中有太多的进程。进程创建就要创建结构体等资源,消耗内存资源。
  (2.实际用户的进程数超过了限制。

以上是关于我有一万种方法让你对系统调用 - fork 了如指掌的主要内容,如果未能解决你的问题,请参考以下文章

Node.js 的进程与线程你真的了如指掌了吗

02 | 学习路径:爬过这六个陡坡,你就能对Linux了如指掌

Steam好评率94%惊悚新作,一万个玩家心里的一万种恐怖

如何在excel表格中分解店铺指标?

UEM用户行为了如指掌!

钢铁是怎样炼成中谁对敌人的全部准备活动了如指掌?