进程创建与fork()的恩怨情仇

一、述说进程:

1、进程(process)是个什么?

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed),或者更加简称之为“运行中的程序”(但并非一个程序这么简单)。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元(CPU调度单位)。

2、概念解析:

首先,进程是一个实体,任何一个进程都有自己独立的内存空间:文本区、数据区、堆、栈等。其次,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有在它获得一系列系统资源而被系统执行时,它才能成为一个活动的实体,我们称其为进程。

我们可以将一个进程拆分成三部分:程序、数据、进程控制模块(Processing Control Block,PCB)。PCB是一种数据结构而且PCB是系统感知进程存在的唯一标志(因为PCB存储进程标识、现场信息、调度信息),常驻内存以提高速度。PCB的存在使得多道程序可以并发执行。

3、进程的几种状态:

这里写图片描述

(1)、三种(或者五种)状态:

新建:新建进程;
结束:进程结束;
就绪状态:除了CPU之外的其他资源都已经分配;
运行状态:已经获取CPU正在执行;
等待状态(阻塞):CPU以及主要资源都缺少,需要I/O。

(2)、状态转换的步骤:

①、新建进程,分配资源除CPU之外的主要资源;
②、分配CPU资源;
③、时间片到;
④、需要I/O;
⑤、获取I/O结束;
⑥、进程执行结束,释放回收资源。

注意:等待—>运行,就绪—>等待这两种情况不可能出现。一旦进入等待就必须重新进入就绪状态(等待状态各种资源都缺少,而就绪状态只差CPU,而要进入运行状态只能是从已经拥有除了CPU之外的各种资源的就绪状态开始)

二、fork()函数:

1、函数原型:

#include<unistd.h>
pid_t fork(void);//pid_t:(Process ID _ Type)int类型

(1)、函数作用:
一个现有的进程调用fork函数,创建一个新的进程。现有(原有)进程成为新建进程的父进程(父进程),新建进程成为原有进程的子进程(子进程)。父子进程共享正文段,但并不共享存储空间,每个进程有自己独立的数据空间、堆和栈的副本。
(2)、返回值(return value):
这个返回值有点奇怪:三种返回值。其实并不奇怪,三中返回值并非同时返回,也并不是在一个进程中返回。

先来看看老外咋解释的(man fork中原话):

RETURN VALUE:
On success, the PID of the child process is return int the parent, and 0 is return int child; 
On failure -1 is return in the parent, No child process is created,and errno is set approprivately.

成功:在父进程中返回子进程的PID,在子进程中返回0(因为他自己没儿子,所以是0);
失败:在父进程中返回-1,子进程没有被创建,并且设置合适的errno值。

2、举例说明返回值:

这里写图片描述

其中getpid()以及getppid()函数分别是:获取当前进程pid(进程标识符)和获取当前进程父进程的pid。
我们看到父进程pid=3371,由于3371调用fork,所以它创建了一个3372的子进程(3372紧接着3371),父进程中接收到的返回值为子进程pid,子进程中接收到的返回值为0;

三、fork()的恩怨情仇实例分析:

1、fork()创建子进程以后,子进程与父进程谁先执行:

一般来说这是不确定的,取决于系统内和中所采用的调度算法,根据上例,我们发现,在我所用测试环境下(Redhat 6.4,之后的情况都已该环境为例),是父进程先执行,子进程在父进程结束以后再执行。但是对于父进程休眠,而子进程不休眠的情况,我们再作以测试:

这里写图片描述

测试中,我们发现先执行pid=3365的进程,再执行pid=3364的进程,由于父进程先于子进程创建,父进程pid自然大于子进程pid。所以在父进程休眠时,子进程抢占到CPU资源优先执行了。

2、子进程与父进程真的不共享内存空间吗?

我们根据下面这个例子来测试:

这里写图片描述

子进程只能共享父进程在创建子进程之后的正文段(即调用fork()函数之后的正文段),所以子进程不会输出“process is begin!”但是会输出“process is over!”(这就像儿子在出生之后,并不会知道父亲年轻时干过什么,但是他会有继承自父亲的一套DNA系统与血肉之躯)。由于父进程先睡眠1秒,所以子进程先执行,在执行过程中,由于子进程有父进程数据与内存空间的副本,所以子进程先输出tag = 5,这是拷贝父进程的数据。而子进程修改tag = 10以后,在父进程苏醒之后再输出tag,并不会输出tag = 10,因为他们并不共享数据与内存空间,各自修改数据值自然不会影响对方的数据。

3、若是子进程结束以前父进程结束了,会有什么问题产生?

我们将返回值测试那个例子稍作修改,如图所示:
这里写图片描述

我们将return 0;之前的sleep去掉,会发现有怪异的现象出现:首先为什么在父进程与子进程执行过程中会出现shell的输入提示?其次为什么子进程输出完毕之后“停”在那儿不动了?它是死了吗?也就是说为什么本应在子进程输出之后才出现的shell命令输入提示出现在了子进程与父进程之间?
其实我这样一问,问题也就解决了:首先明确shell也是一个进程。它(shell)为什么该出现时不出现,不该出现时出现了?说明它把CPU资源抢去了,所以shell会先执行。
过程是这样的:父进程申请了CPU资源,本应在父进程执行完之后CPU的权限就不属于他了,而shell就抢占CPU资源输出“[root@server-desktop 桌面]”这句话等待shell输入。但是父进程“不老实”,在执行过程中他生了个儿子,他想把CPU资源交给他儿子使用,但是他命不好,死得早,还没等到他儿子长大,他就已经作古了,他儿子手无缚鸡之力抢不过shell(shell优先级高),所以shell就先输出,之后CPU空闲了,就让这个没有爸比的可怜孩子(孤儿进程)占用一下吧。死了爸爸的子进程才能在“[root@server-desktop 桌面]”之后执行并输出。但是shell依旧在等待输入,所以会显示第二个异常(这本就不是异常,在输出屏幕上只能这样表示而已),我们依旧可以在命令行下正常输入命令与执行。
但是一个新的问题产生了,CPU并不认识这个孤儿进程,并不会给他分配资源,他为什么这么可爱,能得到CPU资源并执行呢?那就是下一个问题。

4、init“孤儿所”:

首先,我们用代码测试一下:

这里写图片描述

我们修改代码之后,在原来基础上编译,可以执行,这就印证了上面的说法(shell依旧在等待状态)。再者我们看这个孤儿进程他父亲不是死了么?“pid = 1”的进程是谁呢?是人贩子吗?当然不是了,这个pid等于1的进程是个好叔叔,还是一个有钱有权的好叔叔。他就是init进程,想必大家在有桌面的系统下经常用“init 3”和“init 5”。
那么init是干啥的?他为什么成了这个孤儿进程的父进程?pid=1就是init,他就是爸爸,所有进程的爸爸(操作系统启动时创建的第一个进程,其他进程都是由他创建或者由它的子孙创建),在init看见这个没有父亲的孤儿时,他就把孤儿收养了(毕竟是自己子子孙孙),并给这个孤儿CPU资源让其运行完毕后替其“收尸”,所以说init进程是所有孤儿进程的父进程,他家也是个“孤儿所”了。

其实进程调度是个复杂的问题,笔者当前只能理解到这一步,记录下痛苦的愉快的(痛并快乐着)的学习过程,希望能对这块生疏的朋友有所帮助。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值