程序、进程与线程的概念浅析

操作系统由开始的单道批处理系统到多道批处理系统再发展为分时系统、实时系统等,其显著变化之一就是对程序的处理由单通道顺序处理变为多通道并发处理,不断提高CPU的利用率,由此提出了进程与线程的概念。

程序(Program)

程序有多种含义,在操作系统中,程序就是一系列有序指令的集合,可以作为软件存放于某种介质上,其本身没有任何运行的含义,是一个静态的概念。例如程序员写出来的代码放到计算机中编译为机器指令,就成为了程序,至于程序如何操纵计算机,系统会通过调度来让进程和线程完成。

进程(Process)

定义

对于进程的定义,从不同的角度可以有不同的定义,其中较典型的定义有以下几种:

·进程是程序的一次执行。

·进程是一个程序及其数据在处理机上顺序执行时所发生的活动。

·进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位,是一个动态的概念。

以上描述都是对早期面向进程设计的OS中进程的概念,而在当代面向线程设计的OS中,进程是线程的容器,进程不再拥有资源分配和调度的功能。

进程实体

进程实体是进程的具体化表达,一个进程实体由三部分组成:程序、数据集合和进程控制块(Process Control Block,PCB)。程序是一个进程运行所需完成功能的对应执行代码,数据集合是进程运行所需的相关数据段,PCB则是系统用来描述和记录进程的基本情况和活动过程,进而控制和管理进程的一种数据结构。
一般情况下都把进程实体简称为进程,例如创建进程实质上就是创建进程实体中的PCB。从进程实体的概念出发,进程也可以看做是进程实体的运行过程,但这样表达的话进程就成了一个单纯的抽象概念。

特征

·动态性:进程的实质就是程序在多道程序系统中的一次执行过程或者说是进程实体的执行过程,它由调度而产生,由撤销而消亡,具有一定周期性,因此具有动态性。动态性也是进程的最基本特征。

·并发性:多个进程实体可以共存于内存中,并能在一段时间内同时运行,进程也是多道程序处理OS为并发执行而提出的概念。

·独立性:传统OS中进程是一个能独立运行、独立获得资源和独立接受调度的基本单位,凡未建立PCB的程序都不能作为一个独立单位参与运行。

·异步性:由于进程间的相互制约,进程可以按各自独立的、不可预知的速度向前推进。正是这个原因才导致了程序若参与并发运行会产生结果的不可再现性,故而提出进程概念。
进程的基本状态及转换

进程的基本状态及转换

就绪、运行、阻塞

就绪(Ready)状态

就绪状态指进程已经处于准备好运行的状态,即进程已经分配到除CPU以外的所有运行所需资源后,只要再获得CPU就可以立即运行的状态。系统中通常有多个同时处于就绪状态的进程,系统将会把这些处于就绪状态的进程按一定的策略(算法)排成队列,这个队列被称为就绪队列。

运行(Running)状态

该状态指进程已经获得CPU,正在执行程序的状态。在单处理机系统中,同一时间段内只会有一个进程处于运行状态,因为处理机一次只能分配给一个进程。

阻塞(Block)状态

阻塞状态指正在运行的进程由于发生某些事件(如I/O请求、申请缓冲区失败、进程同步等)暂时无法运行的状态。此时OS就会把处理机分配给另一个进程,让受阻进程暂停运行。通常OS也会把处于阻塞状态的进程排成一个或多个队列,称为阻塞队列。
引起进程阻塞的事件:

(1)向系统请求共享资源失败
(2)等待某种操作的完成
(3)新数据尚未到达
(4)等待新任务的到达
       进程的三种基本状态的转换如下图


 

进程经常会发生状态转换。如处于就绪状态的进程在就绪队列中被提出并被分配了处理机,该进程就由就绪状态变成了运行状态;而处于运行状态的进程在其得到的时间片消耗完后若进程还未结束则该进程进入就绪状态;处于运行状态的进程在遇到某些事件(如I/O请求)导致该进程暂时无法运行下去的时候则会进入阻塞状态;处于阻塞状态的进程在使其受阻的事件得到解决并且被OS从阻塞队列中提出时又会变为就绪状态。需要注意的是只有就绪状态和运行状态在一定条件下可以相互转换。

创建、终止

创建状态

任何进程都不是凭空产生,都需要被OS创建出来,一个进程从无到有,一直到进入就绪状态之前的过程都因进程所需资源无法得到满足,这一过程中进程处于创建状态。创建一个进程需要多个步骤才能完成:首先由进程申请一个空白PCB,并向PCB中写入用于控制和管理进程的信息;然后OS为该进程分配运行时所需的资源(除CPU);最后将进程插入到就绪队列中等待分配处理机(如果当前系统资源允许)。

引起创建进程的事件(Creation of Process)
(1)用户登录

分时系统中,一个用户成功登录后,OS会为该用户创建一个进程(Linux中叫做终端)
(2)作业调度

多道批处理系统中,OS按一定算法将调度作业,并将其放入内存,为其创建进程
(3)提供服务

用户提出某种请求后,OS将创建进程来提供服务
(4)应用请求

以上三种情况都是OS为用户创建进程,而应用请求则是用户进程自己创建新的进程

终止状态

一个进程由于某些原因(如正常结束、异常终止等)被终结时就进入了终止状态。进入终止状态的进程不能再运行,但在OS中依然还存在,这个进程保存了状态码和一些计时统计数据供其他进程收集。在与之相关的进程提取其有用的信息后,OS将删除该进程,将PCB清零并返还系统。
引起进程终止的事件(Termination of Process)
(1)正常结束

表示一个进程的所有任务已经完成,准备退出运行进入终止态。批处理系统中通常会在程序最后安排一条Halt指令用于向OS表示运行结束,当程序运行到Halt指令时将会产生一个中断,通知OS本进程已经完成。而分时系统中,用户可利用Logs off来表示。
(2)异常结束

指进程没有完成任务但发生了某些异常事件导致进程无法运行(与阻塞不同)。常见的异常事件有越界错误、保护错误、非法指令、运行超时、等待超时、算术运算错误、I/O故障等。
(3)外界干预

指进程应外界请求而终止。主要干预有:OS或系统管理员干预、父进程请求、父进程终止等。
进程的终止过程
(1)根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程的状态;
(2)如果该进程处于运行态,立即停止该进程运行,并填调度标志为真,用于指示该进程被终止后应重新进行调度;
(3)若该进程还有子孙进程,也一并终止;
(4)将被终止的进程所拥有的资源还给其父进程或系统;
(5)将被终止的进程的PCB从PCB表中移出,等待其他进程来搜集信息;
(6)搜集完成,将该进程的PCB清零,交还给系统用于创建新进程。

挂起操作

挂起操作作用于进程,可以将处于就绪、运行或者阻塞状态的进程挂起,被挂起的进程将不会再接受调度直到被激活(激活操作对应于挂起操作)。

挂起过程

当OS中出现了引发进程挂起的事件时,OS将利用挂起原语suspend将指定进程挂起。suspend除了将进程由活动状态变为对应的静止状态以外,还会把该进程的PCB复制到指定内存区以方便用户或父进程考察该进程的运行情况。

激活过程

当OS中出现了引发进程激活的事件时,OS将利用激活原语active将指定进程激活。active原语先将进程从外存调入内存,检查该进程的状态,根据当前状态来改变为相应状态。


       引入挂起和激活操作后进程的基本状态将被进一步细分,细分后的进程状态转换如下图:

相对于上一个图,引入挂起和激活原语后新增了很多状态转换,其中以下几种状态转换值得注意:

·创建→活动就绪:在当前系统的性能和内存容量容许的情况下,完成对进程的创建后,进程进入活动就绪装填等待分配CPU。

·创建→静止就绪:考虑到当前系统的资源状况和性能要求,OS不分配给新建进程一部分资源(主存等),该进程转为静止就绪状态,驻留在外存中,不参与调度。

·静止阻塞→静止就绪:虽然处于活动阻塞状态的进程被挂起后变成静止阻塞状态不会参与调度,但OS依然会为其准备资源(如I/O请求、申请缓冲区等),一旦资源被准备好,并且系统总体资源状况不允许该进程被激活时,就会由静止阻塞状态变为静止就绪状态。

进程控制块(Process Control Block,PCB)

进程控制块是OS内核中的一种数据结构,是进程的重要组成部分。其主要作用是使一个多道程序处理系统不能独立运行的数据成为一个能独立运行的基本单位(即进程),可以与其它进程并发执行。

PCB中的信息

PCB中记录了OS用于描述进程情况及控制进程运行所需的全部信息。主要包括以下四个方面的信息:
(1)进程标识符

进程标识符唯一标识一个进程,OS通过标识符来找到一个进程对应的PCB。进程标识符又分为两种,一种是外部标识符,用于方便用户(进程)对进程的访问;另一种是内部标识符,方便系统对进程的使用。
(2)处理机(CPU)状态

处理机状态主要由处理机中各种寄存器的内容组成。主要包括通用寄存器、指令寄存器、程序状态字PSW、用户栈指针等。
(3)进程调度信息

进程调度信息主要包括进程状态、进程优先级、事件等。
(4)进程控制信息

用于控制进程所必需的信息,主要包括程序和数据的地址、进程同步和通信机制、资源清单、链接指针等。

PCB的组织方式

(1)线性方式

将OS中的所有PCB都放在一张线性表中。该方式实现简单、开销小,但每次查找都要扫描整张表,因此适合进程不多的系统。
(2)链接方式

把具有相同状态进程的PCB通过链接字链接成一个队列(就绪队列、阻塞队列等)。通常这些队列中的PCB还有优先级的高低之分。
(3)索引方式

系统根据进程状态的不同为进程建立几张索引表(就绪索引、阻塞索引等)。

线程(Threads)

线程,有时候也被称为轻量级进程(Light-weight Process,LWP),为了减少程序在并发执行时所付出的时空开销而被引入。线程是进程中的一个实体,引入线程后,进程将不再是系统调度和分派的基本单位。

线程的引入

要说线程就不得不先提进程,引入进程的目的是为了使多个程序能并发执行,进程是系统调度和分派的基本单位,同时可以拥有资源,这是进程的两个基本属性。

引入进程概念后,为使程序能够并发执行,系统必须重复进行创建进程、撤销进程和进程切换(对进程进行上下文切换时)的操作。由于进程是系统资源的拥有者,因而在这一系列动作中系统必须为之付出较大的时空开销,这就限制了系统中所设置的进程数目,而且进程切换也不宜过于频繁,从而限制了并发程度的提高,影响OS性能的进一步提升。

为解决这个问题,人们想到通过将资源拥有权和调度分派权分离开,让系统对资源拥有者不施以频繁切换、释放,同时可以让调度、分派更为“轻松”,以减少时空开销的做法,线程概念也就因此被提出来了。

线程特点及与进程的对比

调度的基本单位

在传统操作系统中,进程是作为独立调度和分派的基本单位,且拥有系统资源,这样在每次调度的时候都需要进行上下文切换,开销较大。而引入线程后,线程才是调度和分派的基本单位,当线程切换时,还需要保存和设置少量寄存器内容,切换代价远小于传统进程。在同一进程中线程的切换不会引起进程的切换,不同进程中的线程切换则必然引起进程切换。

并发性

引入线程的OS中,不但进程之间可以并发执行,一个进程中的多个线程之间甚至不同进程的线程之间也能并发执行。这样使得OS具有更好的并发性,提高了系统资源的利用率和系统吞吐量。

拥有资源

引入线程后进程依然是拥有资源的基本单位,而线程仅拥有一点必不可少的、能保证独立运行的资源,绝大部分系统资源还在进程中。除此之外,同一进程的多个线程之间还共享该进程所拥有的资源,使得线程之间的同步和通信也比进程的简单。

独立性

同一进程中的不同线程之间的独立性要比进程之间的独立性低得多。每个进程都有一个独立的地址空间和其它资源,除了共享全局变量外不允许其它进程访问。而同一进程的不同线程之间为了提高并发性,共享了进程的内存地址空间和资源。

系统开销

在创建或撤销进程时,系统都要为之分配和回收PCB、分配和回收其它资源。操作系统为之付出的开销远大于线程的创建或撤销。类似的,在进行切换时,线程的切换代价也明显小于进程。

支持多处理机

在多处理机系统中,对于传统进程,不管多少处理机,进程都只能运行在一个处理机上,但对于多线程进程,就可以多个线程都分配到处理机,这将加速进程的完成。

线程控制块(Thread Control Block, TCB)

线程作为进程中的一个实体,也有对应的数据结构——线程控制块,TCB中主要包含了以下几项:
(1)线程标识符

线程标识符唯一标识一个线程,是线程的ID
(2)一组寄存器

包括程序计数器PC、状态寄存器和通用寄存器的内容
(3)线程运行状态

用于描述线程正处于何种运行状态
(4)优先级

描述线程的优先程度
(5)线程专有存储区

用于线程切换时存放相关信息
(6)信号屏蔽

对某些信号加以屏蔽
(7)堆栈指针

在线程运行时,经常会进行过程调用,而过程的调用通常会出现多重嵌套的情况,这样就必须将每次过程调用中所使用的局部变量以及返回地址保存起来。因此应为每个线程设置一个堆栈来存放局部变量和返回地址。TCB中设置有两个指向堆栈的指针:指向用户自己堆栈的指针和指向核心栈的指针。前者是指当线程运行在用户态时,使用用户自己的用户栈来保存,后者是指线程运行在核心态时使用系统的核心栈。

线程的实现方式

内核支持线程(Kernel Supported Threads,KST)

操作系统中的所有进程和线程,无论是系统级还是用户级,都是在操作系统内核的支持下(直接或间接)运行的,KST的创建、撤销、切换等也都是在内核空间实现的。

优缺点

KST在多处理器系统中,内核能够同时调度同一进程的多个线程并发执行;如果进程中的一个线程被阻塞了,内核可以调度该进程中的其它线程运行,也可以运行其它进程中的线程;KST具有很小的数据结构和堆栈,线程切换速度快、开销小;内核本身也可以采用多线程技术(如我们经常听到的Intel处理器所介绍的双核四线程、四核八线程等),可以提高系统的执行速度和效率。

KST的主要缺点是对于用户的线程切换而言,其模式切换的开销较大,在同一个进程中,从线程切换需要从用户态转到核心态进行,因为用户进程的线程在用户态进行,而线程调度和管理是在内核实现的。

用户级线程(User Level Threads,ULT)

ULT是在用户空间中使用的,对线程的创建、撤销、切换、同步与通信等功能都无需内核支持,因此一个操作系统中用户级线程的数量可以达到数千个。由于ULT的TCB都是设置在用户空间,而且线程操作也无需内核帮助,因此内核完全不知道ULT的存在。

需要注意的是,只支持ULT的OS,系统调度仍然是以进程为单位进行的。因此在ULT中若一个进程只有一个用户级线程而另一个进程有多个(假设100)用户级线程,那么前者的运行时间将比后者快100倍,这并不公平。而在KST中,调度则是以线程为单位进行。KST中,内核会根据线程内线程数量的多少分配相应的时间片,使含有不同数目线程的进程的运行时间达到平衡。

优缺点

ULT中线程切换不需要转换到内核空间,对线程的所有操作均在用户空间中进行,不需要内核协助,从而减少了模式切换的开销;在不干扰OS的情况下,调度算法可以是进程专用的,不同的进程可以根据自身需要选择不同的调度算法,对自己的线程进行管理和调度;ULT的实现与OS平台无关,因为对于线程管理的代码是属于用户程序的。因此ULT甚至可以在不支持线程机制的操作系统平台上实现。

ULT的缺点是当一个线程被阻塞时,这个线程所在进程的其它线程都会被阻塞,而KST中则不会出现这种情况;另外,在只支持ULT的OS中,多线程应用不能利用多处理机进行多重处理的优点,因为内核每次分配给一个进程的只有一个处理机,因此一个进程中仅有一个线程能执行,其它线程只能等待。

组合方式

综合KST和ULT的优缺点,一些OS同时提供了对这两种线程实现方式的支持(如Solaris)。在这种系统中,内核支持多个KST的简历、调度和管理,同时也允许应用程序建立、调度和管理ULT。由于ULT和KST的连接方式不同,从而形成了三种不同的组合方式实现模型:

多对一模型

多对一模型将多个ULT映射到一个KST中,这些ULT一般属于同一个进程。这些ULT运行在该进程的用户空间,仅当需要访问内核时才会映射到一个KST上,但每次只允许一个线程进行映射。该模型的主要优点是线程管理开销小、效率高,主要缺点在于如果一个ULT在访问时发生阻塞,则整个进程都会阻塞,此外在同一个进程中,任意时刻只能有一个线程能够访问内核,多个线程不能同时在多个处理机上运行。

一对一模型

一个ULT只映射到一个KST,这也是Linux采用的线程实现方式。这种模型的主要优点是当一个线程阻塞时允许调度另外一个线程运行,因此一对一模型与多对一模型相比较具有更好的并发性能。此外在多处理机系统中,它允许多个线程并发执行。但由于一个ULT只映射一个KST,所以每创建一个ULT就需要创建一个KST,相较于多对一模型,其开销较大,因此限制了整个系统的线程数量。

多对多模型

多对多模型将多个ULT映射到同样数量或更少数量的多个KST上(一般不会相同),综合了以上两种模型的优点,KST数目可以genuine应用进程和系统环境的不同而变化,可以像一对一模型那样使一个进程的多个线程在多处理机系统上并发运行,也可以像多对多模型那样,减少线程管理开销和提高效率。

线程的具体实现

KST实现

KST的实现与进程实现类似,都以控制块为基础进行创建、撤销、切换、通信等。KST的调度和切换过程也分抢占式和非抢占式两种,在线程的调度算法上,同样可采用时间片轮转法、优先权算法等。当线程调度选中一个线程后,便将处理机分配给它。当然,线程在调度和切换上的时空开销要远小于进程的调度和切换。

ULT实现

前面说过所有进程和线程都直接或间接地在OS内核的支持下运行,不论单线程OS还是多线程OS,系统资源都由内核管理,传统OS(单线程)利用OS提供的系统调用来请求资源,而现代OS(多线程)中,ULT是不能利用系统调用的,因此当线程需要系统资源时,需要通过中间系统来完成相应的系统调用。目前主要有运行时系统和内和控制线程两种中间系统实现方式。

运行时系统

所谓“运行时系统”,实质上是用于管理和控制线程的函数(过程)的集合,其中包括线程创建函数、撤销函数、通信函数等。运行时系统中的所有函数都作为ULT与内核之间联系的接口驻留在用户空间中。
传统OS中进程在切换时必须由用户态切换为核心态,而ULT利用运行时系统的函数来执行切换任务。该过程将线程状态保存在该线程的堆栈中,然后按照一定算法选择一个处于就绪状态的新线程运行,将新线程堆栈中的CPU状态装入到相应寄存器中,一旦将栈指针和程序计数器切换后,便开始了新线程的运行,因此ULT的切换无需进入内核,操作简单效率高。
不论单线程OS还是多线程OS,系统资源都由内核管理,传统OS(单线程)利用OS提供的系统调用来请求资源,而现代OS(多线程)中,ULT是不能利用系统调用的,因此当线程需要系统资源时,需要通过中间系统来完成相应的系统调用。

内核控制线程

内核控制线程又称为轻型进程(Light Weight Process,LWP)。像ULT一样,LWP也有类似的数据结构支持,如线程标识符、状态标识等,LWP也可以共享进程所拥有的资源。不同的是,LWP可以通过系统调用来获得内核提供的服务,而ULT不能。因此当一个ULT需要访问内核时,只需要将它连接到一个LWP上,通过LWP就可以访问内核资源。这种线程实现方式就是组合方式。
一个系统中的ULT数量可能很大,为了节省系统开销,不可能设置太多的LWP,而是把LWP做成一个缓冲池,称为“线程池”。用户进程中的任一ULT都可以连接到线程池中的任何一个LWP上。为使每一个ULT在需要时都可以利用LWP与内核进行通信,可以使多个ULT多路复用一个LWP,但只有当前连接到LWP上的ULT才能与内核通信,其它ULT需要等待。因此通过线程池也可以实现ULT与内核之间的间接通信。


如图所示,在内核级线程执行操作时,如果发生阻塞,则与之相连的多个LWP也将随之阻塞,进而使了解到LWP上的所有用户级线程也被阻塞。如果进程中只包含了一个LWP,此时进程也应阻塞。

线程的创建与终止

创建

应用程序在启动时,通常只有一个线程在运行,该线程称为“初始化线程”,它的主要功能是用于创建新线程。在创建新线程时,需要利用一个线程创建函数(或系统调用),并提供相关参数。线程的创建函数执行完后,将返回一个线程标识符供以后使用。

终止

在一个线程需要或被迫终止时,由终止线程调用相应函数(或系统调用)对其执行终止操作。在大多数OS中线程被终止后并不立即释放它所占有的资源,只有当进程中其它线程执行了分离函数后,被终止的线程才与资源分离。被终止但尚未释放资源的线程仍然可以被需要它的线程所调用,以使被终止的线程重新恢复运行。

三者关系

以一个比喻来描述程序、进程、线程三者的关系。

计算机CPU相当于一个工厂,它承担了所有计算任务(工程)。那么进程就是工厂里的一个个车间,负责处理工厂交给车间的任务。工厂电力有限,一次只能供给一个车间的动力(单处理机),因此任一时刻只能有一个车间(进程)处于工作状态,因此只能轮流工作。车间里有很多工人,线程就相当于这些工人,他们协同完成车间(进程)的任务,并且共享这个车间所拥有的资源。那么程序呢?程序就相当于任务,工厂根据任务指示将工作分配给各车间或者更具体到车间里的工人。


 

                                                         本文大部分内容摘自《计算机操作系统(第四版)》,图片来源于百度图片

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值