目录
一、前言
二、fork()函数
2.1 fork()函数的基本概念
2.2 问题一的解答
2.3 问题二的解答
2.4 问题三的解答
2.5 问题四的解答
2.6 问题五的解答
一、前言
在上节内容中我们已经学会了使用我们的getpid()和我们的getppid()去查看我们进程的pid,并且学习到了两种查看进程的方式,在我们的正文开始之前,我先对上文的一些内容做一些补充。
在第二种查看进程的方式中我们看到一个进程的详细信息:
即上图的右半部分,这里我们主要对其中的 cwd 和 exe 进行说明。
我们经常会遇到一个概念,叫做文件的当前目录,那什么是当前目录呢,这是什么意思呢,子在进程中 cwd 就指向该进程的当前目录,其实就是在默认情况下,进程启动所在的路径,就是当前的工作目录。
那 exe 又是什么呢,exe 指向的是可执行程序的位置。
在Linux中我们可通过指令来修改当前的工作目录:
chdir + 路径 :修改工作目录
二、fork()函数
2.1 fork()函数的基本概念+提出问题
这里有一个问题,那就是如何去创建一个进程呢,最简单的方法就是执行我们的可执行程序,系统就会生成一个进程。我们还可以通过fork()这个函数在代码去主动去创建进程。
我们可以先用man指令查询一下这个函数:
简单来说,fork()函数的作用就是创建一个当前进程的一个子进程。那我们要怎么理解去创建一个进程这样的一个概念呢?
创建一个进程,其实就是系统申请内存,保存我们当前的可执行程序,生成其对应的一个PCB,并将该可执行程序及其PCB加入到我们的进程列表中。
接下来就让我们先来看看fork()创建的进程有和特征。
先写一段这样的代码,我们去观测一下该子进程的信息,至于为什么要这样写,我们等会再说,先跑起来看看。
我们会发现确实出现了两个进程,且其中一个进程的ppid是我们一个进程的pid。
很明显,在我们的红色框框内第一个进程跟我们上节所讲的进程差不多,其父进程会是我们的命令行解释器。而这个第二个进程就应该是我们fork()函数所创建出来的子进程了。那我们再来看一段代码。
再来看看这段代码的运行结果。
不难发现听音乐和下电影在同时运行,可是我们printf函数明明就只执行了一次为什么会输出两个不同的值,难道是我们的子进程继承了我们父进程的代码吗?就算是继承我父进程的代码,那我的fork()为什么会有两个返回值呢,这也太不符合我们之前所学了。那我们不妨把我们所有的问题全部抛出了一个一个解决。
问题:
- fork()函数干了什么事,我们为什么要使用它?
- 为什么fork()函数会有两个不同的返回值?
- 为什么fork()的返回值会给父进程返回子进程的pid,而给子进程返回0?
- fork之后,子进程和父进程谁先执行?
- 如何去理解一个变量却又不同的返回值?
2.2 问题一的解答
刚刚我们有讲,fork()函数的作用是创建了一个子进程,那这个子进程有什么用呢?
我们创建子进程主要为了使用其有两个返回值的特性,来帮助我们实现多任务并行运行这一场景。举个例子,比如上文我们想边下载电影边听音乐的场景。可是我们新建出来的子进程按道理来说是不会有代码的,那其是如何满足我们的需求的呢?
所以在fork之后,我们的父进程和我们的子进程会执行一样的代码,那这里可能就有人会问了,那fork之前的代码呢,子进程就不执行了吗? 是的,确实是不执行了,但是不是因为其看不到父进程的所有代码,而是子进程同样继承了父进程的寄存器。
2.3 问题二的解答
问题又来了,为什么fork()会返回两个不同的值呢? 在上文我们有提到,一个进程是怎么被创建出来的,即当其可执行程序和PCB被加入到进程队列时,该进程就已经被创建成功了,那这个时候,fork()函数有没有结束呢? 没有,只能说其创建进程的任务完成了,但其还又一个语句没有执行呢,什么呢,那就是return。再回答第一个问题的时候我们就有提到,子进程会继承父进程的寄存器,那么此时寄存器是不是指向的是return语句呢? 很显然是的,那么父进程返回一个值,子进程返回一个值,不就有两个返回值了吗? 放张图,加深一下理解。
2.4 问题三的解答
为什么fork()的返回值会给父进程返回子进程的pid,而给子进程返回0呢?
这是因为父进程它很有可能不只有一个子进程,那其要是想对它的子进程进行管理该怎么做呢?那肯定是要根据其子进程的标识符呀,那么进程的标识符是什么呢,就是其pid。给自己的返回自己的pid没什么意义,所以fork就给子进程返回0。
2.5 问题四的解答
创建完成子进程,这仅仅是一个开始,创建完成子进程之后,系统的其他进程,父进程,子进程都等着被系统调度执行。当父子进程的PCB都被创建且都在运行队列(操作系统会把其将要执行的代码放入一个队列中进行排队,这个队列就叫运行队列)中排队的时候,哪个进程先被调度,那个进程就先被执行。但是这个调度顺序是不确定的,由操作系统本身决定,由各自PCB中的调度信息(时间片、优先级等)+ 调度器算法共同决定的。
2.6 问题五的解答
为什么有两个返回值我们已经知道了,但是为什么这两个返回值不相同呢?
这里就要提到,进程的独立性,其独立性首先就体现在各自的PCB是不会相互影响的。虽然父子进程共享代码,但是代码是只读的,无法被修改。但是数据却是可以被修改的,如果存在一个全局变量,我父进程需要根据这个全局变量作为判断条件,而我的子进程却会修改这个全局变量,那这不就影响到了我们的父进程了吗?所以数据每个进程都得想办法私有一份,怎么私有都拷贝下来吗,如果代码量很大的话,拷贝的代价就有点大了,而我们的操作系统是不会允许这样降低效率的事情发生的,那么我们该怎么办呢?
这里主要是使用到了写时拷贝的方法,父子进程还是一样的共享代码,只有当自己要修改其中的某个变量时,才把这个变量拷贝过来,以达到不会相互影响的状态。