程序、进程与线程(一)

关于进程与线程的概念解释网上有很多,但总感觉讲的不是很清楚。于是今天打算整理一下这个方面的知识,语言力求通俗易懂

一、程序与进程

举个具体的场景,某一天你爸妈不在家,你必须要自己做饭、洗衣服等等。你要做饭首先你得有做饭手册呀,这个手册中包含了做饭所需的各种步骤(比如洗米,把米放到电饭煲中,插上电饭煲电源等等),当你看完了做饭手册,你需要材料呀,也就是大米。有了这个大米你就可以把这个大米做成相应的米饭。从某种意义上来看,这就是一个完整的进程,其中的做饭手册就是程序,它包含了各种指令来指导进程的进行,我们所需的大米就是用户输入,而煮好的米饭就是相应的输出。自然,不同的用户输入就会导致不同的输出。接下来我们来看一下专业一点的解释

1、定义

程序:由若干条具有一定功能的指令所组成的解题顺序和步骤

进程:程序的一次执行,是操作系统进行资源分配和保护的基本单位

#从理论角度看,进程是对正在执行程序活动规律的一种抽象

#从实现角度看,进程是一种数据结构,目的在于有效调度和管理进入计算机主存运行的程序

2、特点

#程序是静态的,没有生命周期的概念;而进程是一个动态概念,它是有着自己的生命周期的。就像前面我们所说的做饭,你从开始阅读做饭手册到做出香喷喷的米饭就是一个进程的完整生命周期,当饭做出来以后,这条进程也就终止了。下次你还要做饭,那就是重新开启了另外一条进程。但是做饭手册则不一样,只要你不扔掉它,它就永远都在(这里不考虑氧化等物理因素)。

#一个程序可以对应多个进程,但是一个进程只能对应一个程序。你每天都可以用同一本做饭手册去做饭,开启不同的做饭进程。但是你的每一次做饭进程用的都只能是某一本具体的做饭手册。

#进程是一个能够独立运行的单位,可以和其他进程并发执行。比如当你把饭放入电饭煲之后,你肯定不会在那边傻乎乎地站着,等到饭熟了,然后再去洗衣服。正常的选择应该是,我把饭放入电饭煲之后,就先去洗衣服,等到待着饭熟了再过来把电源拔了。在这个场景中,你就相当于是一个CPU,同时开启了做饭和洗衣服两个进程,这两个进程交错进行。你先是进行一会儿做饭进程,发现该进程需要进行一些长时间等待的操作,而你又是处于空闲状态时。你就会选择去洗一会儿衣服。整个过程如下:

图中的OS调度是指操作系统的调度程序。在生活中你是可以自己去调度自己的,但是CPU不行,它是需要靠操作系统的调度程序来对它进行分配。

3、程序顺序执行与并发执行

顺序执行的特点:

#顺序性-前一程序执行完才能执行后一程序。(你必须做完饭才能去洗衣服。)

#封闭性-程序运行时独占资源。(跟顺序性有一定关系。当你(CPU)做饭时你就被做饭这个进程独占了,直到做饭进程结束才能去洗衣服)

#可再现性-在相同的执行环境和资源下,程序重复执行的结果可以重现。(如果你煮饭时用的大米量和种类等都一样,那么理论上只要是用同一本做饭手册去做,你做出来的米饭也是一样的)

并发执行的特点:

没有顺序性、封闭性和可再现性。相应的反而是间断性、非封闭性和结果的不可再现性

前面两个比较好理解。因为你可以在做饭的过程中去洗衣服,你不再被某个进程独占,而是可以交错地去执行各个程序。至于并发执行为何没有可再现性,我们可以举一些其他的例子。前面我们所举的两个进程之间是没有任何联系的,因此它们并发执行应该还是可以具有可再现性。但是对于两个相互有关联的进程,就不具备可再现性了。比如,你的银行卡里面有一万块钱,某一天你想了个歪点子。你先是让另一个人去ATM机上取钱,同时你又去柜台让服务人员帮你取钱。这时两个步骤并发进行(严格意义上是并行,每个人都是一个CPU,但不影响理解)。如果另一个人在服务人员查询你银行卡余额之前就把钱给取走了,那么这时你的余额就变成了零,服务人员不会给你一万块,因此最终你只能得到ATM机上的一万块。但是如果另一个人在服务人员查询你银行卡的余额之后并且在服务人员手动把你的钱清零之前取出钱,这时服务人员由于查询到你的余额还有一万块,他会取一万块给你,而ATM机上又取出了一万块,因此最终你可以拿到两万块。因此这个结果是没有可再现性的。

在计算机中的表现就是同样并发进行的两段程序,它们每次交错执行的具体情况都可能会不一样,那么如果涉及到了共享数据,执行结果很可能就会不一样。这也就引出了我们后面会讲的线程安全问题。Java中可以用volatile关键字来进行共享数据的同步。比如取钱这个场景。每次服务人员在查询用户余额时都要先进行更新操作,取到最新数据,这样子就可以避免服务人员取到的数据是过期的。

二、进程的状态

1、五种状态

New:进程已经被创建但是还没被执行

Ready:准备执行(在就绪队列中)

Running:正在执行,占用处理机

Waiting:阻塞中,等待某事件发生或I/O操作结束后才能进入就绪队列中

Terminated:因停止或取消,被OS从执行状态释放

2、状态转换图

3、进程调度机制

作业队列(Job Queue):在系统中所有进程作业的集合

就绪队列(Ready Queue):在主内存中,就绪并等待执行的所有进程的集合

设备队列(Device Queue):等待某一I/O设备的进程队列

进程的调度一定程度上就是进程在各个队列之间的迁移。

三、进程的内存映像

1、PCB(程序控制块)

A、主要作用:唯一标识一个进程、为进程的控制提供信息(进程的调度等等)、把程序变成了进程。

B、PCB的结构(存储的信息)

C、PCB表:OS把所有的PCB都组合在一起形成一个表结构,并把它们放在内存的某一个位置。PCB中有一个位置信息定位到该PCB所对应的进程信息所在的具体位置。如下图

#PCB表的大小限制了操作系统中可同时存在的进程个数,称为系统的并发度。

#两种组织方式:

链表结构:把具有相同状态的PCB连接在一起,如下图:

表(数组指针)结构:对具有相同状态的PCB设置自己的索引表

 

2、其他部分

Code存放程序指令,Global存放全局变量,Stack(栈)存放局部变量(比如方法的传入参数),Heap(堆)存放程序员自己分配的数据(典型的就是new出来的东西都放在Heap中)

 

三、进程的调度

1、长程调度:

决定那个进程被加入到就绪队列,将进程从硬盘加入到内存,并加入就绪队列。控制着CPU多道程序的道。

补充:什么是多道程序?多道程序其实就是多任务执行。被加载到内存的多个进程会被交错执行(并发执行)。也就是在用户看来,处于就绪队列中的进程都处于同时执行的状态。

2、短程调度:

CPU调度,决定哪个队列作为下一个被CPU执行的进程,并给它分配CPU资源

3、中程调度:

把进程从内存移除并降低了多道程序度(比如原本内存的就绪队列中有五个进程,那么多道程序度就是5。如果移除了一个,多道程序度就降为了4),放到硬盘中。稍后再把进程加入到内存中并重新从刚才停止的地方开始执行。这种机制被称为交换。

乍一看感觉这中程调度不是没事找事吗,好好地干嘛把进程从内存中移除,然后再加载。其实不然,我们先来考虑一个问题。

内存的大小时一定的,那么它所能加载的进程数量肯定也是一定的。如果某一刻内存已被现有的进程占满,并且这时又有新的进程请求被加载,怎么办?一般来说有两种解决方案,一种是拒绝加载。必须等到内存中的某个进程执行完了之后你才能加载进行。这样子的话会有一个问题,如果这些就绪队列中的进程执行时间较长,其实也不用很长,只要十来秒,待加载的进程就得等待十来秒。如果这个进程时用户刚刚创建的,那么他就必须得先卡住十来秒,然后程序才会开始运行,这种用户体验是非常差的。因此我们来看看另一种解决方案,利用中程调度。我们先把内存中的某个进程移除到硬盘,然后加载这个新的进程,这样子用户就不需要等待过长的时间。这里的话还有一个问题,我们知道从硬盘拷贝文件到内存是很耗时的一个操作。但是这里的话我们在拷贝时是按位拷贝,绕过了文件系统,大大地提高了速度。这其实就引入了虚拟内存的概念。比如你的内存有1G,而你的硬盘有4G。这时从用户的角度来看他会觉得他有5G的内存。

 

四、进程的控制

进程的控制动作由操作系统完成,操作系统提供相应的系统服务以备调用。

1、进程的创建

进程的创建实质上就是生成一个PCB

2、进程的终止

进程在终止之前需要先终止它的所有子进程。

 

3、阻塞过程

-停止执行

-更改PCB为阻塞

-保留现场

-PCB插入相应的阻塞队列

-提示重新调度

 

4、唤醒过程

-根据释放条件寻找相应的进程

-改PCB状态为就绪

-将PCB插入就绪队列

 

五、进程的组织结构

1、Unix中的进程树结构

2、Linux中的init进程

A、idle进程

由系统进程,运行在内核态。它的PID=0,也是唯一一个不由fork()或者kernel_Thread()(kernel_Thread()是内核级的fork())创建的进程。完成系统加载后,演变为进程调度、交换。(负责进程在内核态和用户态之间进行转换)

B、kthread进程

由idle通过kernel_Thread()创建的进程,它的PID=1,并始终运行在内核空间,负责所有内核进程的调度和管理。所有的内核进程都是直接或者间接以kthread进程为父进程。

C、init进程

由idle通过Kernel_Thread()创建的进程,它的PID=2,在内核空间初始化后加载init程序。init进程由进程0创建,完成系统的初始化,是系统中所有其他用户进程的祖先进程,init将变成为守护进程监视系统的其他进程。

init进程有6个进程级别:0是关机,1是单用户,2是多用户,不联网,3是多用户,4是不使用的,5是xwindows,也就是有界面的,6是重启。

3、Linux进程树的形成

这里的运行级别就是我们前面所指的Linux六种运行状态

不同的运行状态需要启动不同的服务,比如你从状态3转到转到状态2,那就是需要把网络服务关闭掉。

 

至此,程序和进程的概念我们已经基本弄清了。稍微做个总结。程序是存放在硬盘中的,当你启动程序时。操作系统会在硬盘中创建一个新进程,加入作业队列中。如果当前内存足够,操作系统会把这个新进程加载到内存,加入到就绪队列中,等待调度程序把CPU调度给它。当它获得CPU时,就进入执行状态,如果调度程序给它分配的时间片已经用完了,而它还没有执行完,此时它就会重新被加入就绪队列中进行等待。如果它遇到了一些I/O操作,那么它就会进行阻塞状态。后面等I/O操作完成后被唤醒并重新加入到就绪队列中。如果它在调度程序分配的时间片中完成了自身所带的所有任务,那么它就会被终止,释放资源。

最后,我们再来探讨一个问题,多进程是什么?简单说就是多个进程并发执行。它的出现主要解决了两个问题,一个是顺序执行中CPU利用率低,另一个是顺序执行中用户交互体现差。我们以最开始的那个场景为例。如果我们采用顺序执行,当你在煮饭的时候必须等到电饭煲把饭给煮熟了你才能去洗衣服,在这期间你是完全处于一个空闲的状态,但你只能傻傻待着,效率就很低。另一个,如果这时有人来敲门,比如快递员来送包裹了。如果你处于顺序执行的状态中,你是不能去开门的,你饭都还没煮完呢。你必须得等到饭煮完了才能去开门。这时快递员的体验就很差,他可能会以为你是个傻子。。。因此我们就需要多线程,需要并发执行。当你把饭放进电饭煲后就能去洗衣服,当有快递员送来包裹时你可以暂时停下手头的动作去开下门,然后再回来继续刚刚的工作。

程序与进程我们已经聊得差不多了,后面我们继续讲讲线程的东西。《 程序、进程与线程(二)》

 

 

  • 15
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值