操作系统的基础——进程与线程

目录

一. 概述

1. 进程与线程的由来

2. 进程与线程的区别

二. 进程

1. 进程模型

2. 进程的创建

3. 进程的终止

4. 进程的层次结构

5. 进程的状态

6. 进程的实现

7. 进程的模型分析

8. 进程间通信

<1> 进程间通信需要解决的三个问题

<2> 进程间通信的相关概念

<3> 进程间通信的常见方式

9. 进程的调度

<1> 为什么需要调度

<2> 何时调度

<3> 调度算法分类

<4> 批处理系统中的调度

<5> 交互式系统中的调度

<6> 实时系统中的调度

<7> 调度策略和机制

三. 线程

1. 线程的优点

2. 线程模型

3. POSIX模型

4. 线程实现

<1> 在用户空间实现

<2> 在内核中实现

<3> 混合实现

5. 调度程序激活机制

6. 弹出式线程


本文主要介绍操作系统中最基础的概念——进程与线程,希望通过这篇文章,带大家熟悉和掌握进程与线程的相关知识。

一. 概述

1. 进程与线程的由来

理解进程和线程之前,先明白它的由来是很重要的。

这是因为:人类的发明,绝大数都不是凭空想象出来,而是因为需求的出现。

上面这个规律在绝大多数情况下都是成立的。

下面从操作系统的发展历史谈起。如今的我们可以用计算机做很多事情,上网,办公,交友,游戏等等。而在最初的时候,计算机的出现其实是因为计算的需求:大量的数学计算,通过人工的方式计算,不仅慢,而且繁琐,耗时也长。基于这个需求,人类发明了计算机,把计算公式保存到计算机,然后通过不断往计算机中输入数据,最终得到输出结果。

但是,在计算机使用过程中,人们又有了新的需求:如何进行并行计算?如何提高计算机的计算效率?下面有两个例子:

例1:有两个任务要同时进行,计算机应该怎么做?

例2:假设计算机允许允许两个任务,那么两个任务的顺序怎么安排?

例3:假设其中一个任务里面有多个子任务,那么计算机又要怎么做?

这个时候,进程和线程的概念就因运而生了。进程和线程能够完美的解决上面的三个实例遇到的问题:一个任务代表一个进程,一个子任务代表一个线程,因为一个进程允许包含多个线程。

2. 进程与线程的区别

说完了进程与线程的由来(或者是具体需求)之后,下面介绍下进程与线程,以及它们之前的区别。

进程是程序在执行过程中分配和管理资源的基本单位。线程是进程的一个执行单元,是比进程还要小的独立运行的基本单位。一个程序至少有一个进程,一个进程至少有一个线程。

区别1(根本区别):进程是资源分配的最小单位,线程是程序执行的最小单位。

计算机在执行程序时,会为程序创建相应的进程,进行资源分配时,是以进程为单位进行相应的分配。每个进程都有相应的线程,在执行程序时,实际上是执行相应的一系列线程。

区别2(地址空间):进程有自己独立的地址空间,线程没有独立的地址空间。

每启动一个进程,系统都会为其分配地址空间,通过建立进程表来维护一个进程,每个进程占用一个进程表项;而进程则没有,同一进程的线程共享本进程的地址空间。

区别3(资源):进程之间的资源是独立的,同一进程内的线程共享本进程的资源。

区别4(执行过程):进程有程序运行的入口,进程则不能独立执行。

每个独立的进程程有一个程序运行的入口、顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

区别5(调度):线程是CPU调度的基本单位,但进程不是。

由于程序执行的过程其实是执行具体的线程,那么CPU处理的也是程序相应的线程,所以CPU调度的基本单位是线程。

区别6(系统开销):进程执行开销大,线程执行开销小。

二. 进程

1. 进程模型

操作系统中最核心的概念是进程。进程,是对正在运行程序的一个抽象。操作系统的所有内容都是围绕进程的概念展开的。先引用一个简单的例子来描述下进程,程序,输入数据,CPU。

想象有一位好厨艺的计算机科学家,正在为女儿烘焙生日蛋糕。他有做生日蛋糕的食谱,厨房里有所需的原料,面粉,鸡蛋,糖,香草汁等。在这个比喻中,做蛋糕的食谱就是程序。做蛋糕的各种原料就是输入数据。计算机科学家就是CPU。进程就是厨师阅读食谱,取来各种原料以及烘制蛋糕等一系列动作的总和。

通过上面的例子,我们可以得出进程模型的定义,即:一个进程是某种类型的一个活动,它有程序,输入,输出以及状态。单个处理器可以被若干进程共享,它使用某种调度算法决定何时停止一个进程的工作,并转为另一个进程提供服务。

最后,进程模型在教科书的定义如下:

在进程模型中,计算机上所有可运行的软件(通常也包括操作系统),被组织成若干顺序进程,简称进程。一个进程就是一个正在执行程序的实例,包括程序计数器,寄存器和变量的当前值。

2. 进程的创建

下面介绍进程是如何被创建的。以下4种事件会导致进程的创建:

<1> 系统初始化

启动操作系统时,通常会创建若干个进程。

<2> 正在运行的程序执行了创建进程的系统调用

<3> 用户请求创建一个新进程

<4> 一个批处理作业的初始化

以上的四种事件,从技术上看,新进程都是由于一个已存在的进程执行了一个用于创建进程的系统调用而创建的。

在UNIX系统中,只有一个系统调用可以用来创建新进程:fork。这个系统调用会创建一个与调用进程相同的副本,这两个进程(父进程和子进程)拥有相同的内存映像,同样的环境字符串和同样的打开文件。子进程需要执行execve或一个类似的系统调用以修正其内存映像并运行一个新的程序。

在Windows中,调用createprocess即处理进程创建,也负责把正确的程序装入新的进程,并返回给调用者。

3. 进程的终止

下面介绍一个进程什么情况下会被终止。进程终止,通常由下列条件引起:

<1> 正常退出(自愿)

多数进程是由于完成了它们的工作而终止,在UNIX系统中该调用是exit,而在Windows中,该调用是ExitProcess。

<2> 出错退出(自愿)

进程自己发现了错误,并自愿终止。一般这种情况下,进程退出前会给出提示。

<3> 严重错误(非自愿)

进程在运行过程由于程序的原因发生了异常。比如执行了一条非法指令,引用了不存在的内存。这种情况下进程会受到信号(被中断),然后被终止。

<4> 被其他进程杀死(非自愿)

某个进程通过系统调用通知操作系统杀死某个其他进程。

在UNIX中,这个系统调用是kill,在Windows中,该调用是TerminateProcess。

4. 进程的层次结构

在某些系统中,当进程创建了另一个进程后,父进程和子进程就以某种形式继续保持关联。子进程自身可以创建更多的进程,组成一个进程的层次结构。

在UNIX中,进程和它的所有子进程以及后裔共同组成一个进程组。当用户从键盘发出一个信号时,该信号可以被送给当前与键盘相关的进程组中的所有成员,每个进程可以分别捕获该信号,忽略该信号或采取默认的工作,即被信号杀死。

在Windows中,没有进程层次的概念,所有的进程都是地位相同的。唯一类似于进程层次的暗示是在创建进程的时候,父进程得到一个特别的令牌(称为句柄),用来控制子进程。但是它有权把这个令牌传给某个其他进程,这样子就不存在进程层次了。

5. 进程的状态

一个进程在运行过程中,存在多种状态。下面介绍进程的三种状态。

<1> 运行态

该状态下,进程每时刻都实际占用CPU。

<2> 就绪态

这种状态是一种可运行状态,但是因为其他进程正在运行,所以当前进程暂时中止。

<3> 阻塞态

在阻塞态下,除非某种外部事件发生,否则进程不能运行。

下面是三种状态的状态变化图:

操作系统的基础——进程与线程_第1张图片

 

事件1:进程因为某些事件被阻塞。

事件2:调度程序选择这个线程。

事件3:调度程序选择另一个线程。

事件4:因为某些事件进入就绪状态。

6. 进程的实现

为了实现进程模型,操作系统维护着一张表格(一个结构数组),即进程表。每个进程占用一个进程表项,该表项包括了进程状态的重要信息,包括程序计数器,堆栈指针,内存分配状况,所打开文件的状态,账号和调度信息,以及其他在进程由运行态转换到就绪态或者阻塞态必须保存的信息,从而保证该进程随后能再次启动,就像从未被中断过一样。

下面是进程表中的一些字段:

进程管理:寄存器,程序计数器,堆栈指针,进程状态,进程ID;

存储管理:正文段指针,数据段指针,堆栈端指针;

文件管理:根目录,工作目录,文件描述符;

7. 进程的模型分析

下面对进程模型的CPU利用率进行分析。

假设一个进程等待I/O操作的时间与其停留在内存中的时间比为P,那么当内存中存在n个进程中,则所有进程都在等待I/O操作的概率为P的n次方,所以CPU的利用率为:CPU利用率=1-p的n次方。

由公式可以看出,当等待I/O操作的时间固定时,增加内存中的进程数量,可有效提高CPU的利用率。

严格来说,如果进程用于计算的平均时间是进程在内存中停留时间的20%,且内存中同时存在5个进程,则CPU将一直满负载运行(这个模型过于乐观,因为它假设这5个进程不会同时等待I/O)。

8. 进程间通信

进程经常需要与其他进程通信,下面介绍进程间通信的相关内容。

<1> 进程间通信需要解决的三个问题

1~:进程如何把信息传递给另一个进程;

2~:确保进程间的某些关键活动不会交叉;

3~:保证进程间的正确顺序;

<2> 进程间通信的相关概念

竞争条件

竞争条件是指,两个或多个进程读写某些共享数据,最后运行的结果取决于进程运行的精确时序。

当程序存在竞争条件时,大多数测试结果都很好,但少数情况下会发生一些无法解释的奇怪现象,而多核增长带来的并行使得竞争条件越来越普遍。

临界区

在某些时刻进程也可能需要访问共享内存或共享文件,或执行另外一些会导致竞争的操作。把共享内存进行访问的程序片段称为临界区域或临界区。

如果能够适当地安排,使得两个进程不可能同时处于临界区内,能够避免竞争条件。

<3> 进程间通信的常见方式

方式1:忙等待的互斥

1~:屏蔽中断

在单处理系统,最简单的方法就是使每个进程在刚刚进入临界区后立即屏蔽所有中断,且在离开之前再打开中断。屏蔽中断后,时钟中断也会被屏蔽。CPU只有发生时钟中断或其他中断才会进行线程切换。这样,在屏蔽中断后CPU将不会切换为其他进程。因此,一个进程屏蔽中断之后,它就可以检查和修改共享内存,而担心其他进程介入。

2~:锁变量

一种软件解决方案。设想有一个共享变量。其初始值为0。当一个进程想进入其临界区时,它首先测试这把锁,如果该锁的值为0,则该线程将其设置为1并进入临界区,这把锁的值已经为1,则该进程将等待直到其值变为0。于是0表示临界区内没有进程,1表示某个进程已经进入临界区。

3~:严格轮换法

定义一个整型变量turn,初始值为0,用于记录轮到哪个进程进入临界区,并检查或更新共享内存。开始时,进程0检查turn,发现其值为0,于是进入临界区。进程1也发现其值为0,所以在一个循环中不断测试turn,看其值何时变为1。连续测试一个变量直到某个值出现为止,称为忙等待。用于忙等待的锁,称为自旋锁。

4~:Peterson解法

在使用共享变量之前,各个进程使用进程号0或1作为参数来调用enter_region,在调用在需要的时候需要让进程等待,直到能安全进入临界区。在完成对共享变量的操作之后,进程将调用leave_region,表示操作已完成。若其他的线程希望进入临界区,则现在就可以进入。

5~:TSL指令

一种需要硬件支持的方案。TSL RX,LOCK,称为测试并加锁。

它将一个内存字lock读到寄存器RX中,然后在该内存地址上存一个非零值。读字和写字操作保证是不可分割的,即该指令结束之前,其他处理器均不允许访问该内存字。执行TSL指令的CPU将锁住内存总线,以禁止其他CPU在本指令结束之前访问内存。另外一个可替代TSL的指令是XCHG,它原子性交换两个位置的内容。本质上与TSL的解决方法一样。

方式2:睡眠与唤醒

通过sleep和wakeup实现。sleep是一个引起调用进程阻塞的系统调用,即被挂起,直到另外一个进程将其唤醒。wakeup调用有一个参数,即要被唤醒的进程。

方式3:信号量

信号量是一种实现线程间通信的方法。它引入了一个新的变量类型——信号量,使用整型变量来累计唤醒次数,供以后使用。一个信号量的取值可以为0(表示没有保存下来的唤醒操作)或正值(表示有一个或多个唤醒操作)。

它有down和up操作。

down:检查其值是否大于0。若改值大于0,则将其值减1并继续,若该值为0,则该进程睡眠,此时down操作并未结束。

up:对信号量的值增1,如果一个或多个进程在该信号量上睡眠,无法完成一个先前的down操作,则由系统选择其中的一个并允许该进程完成它的down操作(也就是唤醒一个线程并让它继续执行down)。

注意:

1. 这两个操作都是原子操作。所谓原子操作,是指一组相关联的操作要么都不间断地执行,要么都不执行。

2. 信号量还有一个用途是实现同步。保证某种事件的顺序发生或不发生。

方式4:互斥量

互斥是信号量的一个简化版本,仅仅适用于管理共享资源或小一段代码。互斥量是一个可以处于两态之一的变量:解锁和加锁。只需要一个二进制位表示它。实际上常常使用一个整型量,0表示解锁,其其他所有值表示加锁。

当线程需要访问临界区时,调用mutex_lock,如果当前的互斥量是解锁的,那么调用成功。如果该互斥量已经加锁,那么调用线程被阻塞,直到在临界区中的线程完成并调用mutex_unlock。

1~:快速用户互斥量futex

futex是linux的一个特性,实现了基本的锁(很像互斥锁),但是避免陷入内核,除非它真的不得不这样做,最终通过减少切到内核来减少花销,改善性能。

2~:pthread中的互斥量

提供很多可以用来同步线程的函数,包括:互斥量和条件变量。

互斥量允许或阻塞对临界区的访问。

条件变量则允许线程由于一些未达到的条件而阻塞。

方式5:管程

管程是一种高级同步原语。一个管程是一个由过程,变量及数据结构等组成的一个集合,它组成一个特殊的模块或软件包。进程可以在任何需要的时候调用管程中的过程,但不能在管程之外声明的过程中直接访问管程内的数据结构。

管程是一个编程语言概念,编译器必须要识别管程并对互斥做出安排。

方式6:消息传递

使用两条原语send和receive,它们是系统调用。

前一个调用向一个给定的目标发送一条消息,后一个调用从一个给定的源接收一条消息。如果没有消息可用,则接收者可能被阻塞,直到一条消息到达,或者带着一个错误码立即返回。

方式7:屏障

当一个进程到达屏障时,它就会被屏障阻拦,直到所有进程都达到该屏障为止。屏障可以用于一组进程同步。

方式8:避免锁——读,复制,更新

最快的锁是没有锁。

在某些情况,我们允许写操作更新数据结构,即便还有其他的进程正在使用它。窍门在于确保每个读操作要么读取旧的数据版本,要么读取新的数据版本,但是绝不是新旧数据的一些奇怪组合。

9. 进程的调度

<1> 为什么需要调度

当计算机系统是多道程序设计系统时,通常会有多个进程或者线程同时竞争CPU。只要有两个或更多的进程处于就绪状态,这种情况就会发生,如果此时只有一个CPU可用,那么就必须选择下一个要运行的进程。在操作系统中,完成选择工作的这一部分称为调度程序,该程序使用的算法称为调度算法。适用于进程调度的处理方法也适用于线程调度。

操作系统的基础——进程与线程_第2张图片

<2> 何时调度

在遇到以下情形时,需要调度处理:

1~:在创建一个新进程后,需要决定是运行父进程还是子进程;

2~:在一个进程退出时必须做出调度决策;

3~:当一个进程阻塞在I/O和信号量上或由于其他原因阻塞时,必须选择另一个进程运行;

4~:在一个I/O中断发生时,必须做出调度决策;

<3> 调度算法分类

1~:抢占式调度算法

挑选一个进程,并且让该进程运行某个固定时段的最大值,如果在该时段结束时,该进程仍在运行,它就被挂起,而调度程序挑选另一个进程运行(如果存在一个就绪进程)。

2~:非抢占式调度算法

挑选一个进程,然后让该进程运行直至被阻塞(阻塞在I/O上或等待另一个进程),或者直到该进程自动释放CPU,即使该进程运行了若干个小时,它也不会被强迫挂起。

注意:不同的环境需要不同的调度算法。因为不同的应用领域有不同的目标。环境分别有批处理,交互式和实时。

批处理:用于处理薪水册,存货清单,账目收入,账目支出,利息计算等周期性工作。因为不会有用户不耐烦地在终端等待响应,因此可以使用非抢占式。

交互式:由于需要交互,因此需要避免一个进程霸占CPU拒绝为其他进程服务。因此,抢占是必要的。

实时:特点是或多或少必须满足截止时间。因此调度程序必须是高度可预测和有规律的。

<4> 批处理系统中的调度

1~:先来先服务(非抢占式)

内容:使用该算法,进程按照它们请求CPU的顺序使用CPU。

优点:易于理解并且容易在程序中运用,公平。

缺点:没有考虑到系统中各种资源的综合使用情况,往往使短作业的用户不满意,因为短作业等待处理的时间可能比实际运行时间长得多。

2~:最短作业优先(非抢占式)

内容:适用于运行时间可以预知的调度算法,谁短谁先运行。

3~:最短剩余时间优先(抢占式)

内容:调度程序总是选择剩余运行时间最短的那个进程运行。当一个新的作业到达时,其整个时间同当前进程的剩余时间做比较。如果新的进程比当前运行进程需要更少的时间,则当前进程被挂起,而运行新的进程。

优点:使短作业获得良好的服务。

<5> 交互式系统中的调度

1~:轮转调度

最古老,最公平,使用最广。每个进程被分配一个时间片,即允许该进程在该时间段中运行。如果在时间片结束时该进程还在运行,则剥夺CPU并分配给另一个进程。如果该进程在时间片结束前阻塞或结束,则CPU立即进行切换。

时间片的设置

时间片设置太短会导致过多的进程切换,降低CPU效率。

时间片设置太长又可能引起对短的交互请求的响应时间变长。

将时间片设置成20-50ms通常是一个比较合理的折中。

2~:优先级调度

每个进程被赋予一个优先级,允许优先级最高的可运行进程先运行。

为了防止高优先级进程无休止地运行下去,调度程序可能在每个时钟中断降低当前进程的优先级。另一种方法是,给每个进程赋予一个允许允许的最大时间片,当用完这个时间片后次高优先级的进程便获得运行机会。

优先级可以是静态赋予或者动态赋予。

缺点:如果不偶尔对优先级进行调整,则低优先级进程很可能出现饥饿现象(即得不到运行机会)。

3~:多级队列

通过设立优先级类,属于最高优先级类的进程运行1个时间片,属于次优先级类的进程运行2个时间片,再次一级运行4个时间片,以此类推。当一个进程用完分配的时间片后,它被移到下一类。

4~:最短进程优先

由于最短作业常常伴随着最短响应时间,所以能够把它用于交互进程,那将是非常好的,这种方式需要从当前可运行进程中找出最短的那一个进程。

5~:保证调度

该算法是向用户作出明确的性能保证,然后去实现它。该保证内容是:若用户工作时有n个用户登录,则用户将获得CPU处理能力的1/n。

为了实现所做的保证,系统必须跟踪各个进程自创建以来已使用了多少CPU时间。然后它计算各个进程应获得的CPU时间。根据该算法不断调整各个进程分配的时间,保证各个进程间相对公平。

6~:彩票调度

彩票调度的基本思想是,为进程提供各种系统资源(如CPU时间)的彩票。一旦需要做出一项调度决策时,就随机抽出一张彩票,拥有该彩票的进程获得该资源。

彩票调度是反应迅速的,如果有一个新的进程出现并得到一些彩票,那么在下一次的抽奖中,该进程会有同它持有彩票数量成比例的机会赢得奖励。

7~:公平分享调度

上面的调度方法被调度的都是各个进程本身,并不关注其所有者是谁。而公平分享调度中被调度的是各个用户。每个用户都被分配到CPU时间的一部分,而调度程序以一种强制的方式选择进程。

<6> 实时系统中的调度

实时系统,是一种时间起着主导作用的系统。当一种或多种外部物理设备发给计算机一个服务请求,而计算机必须在一个确定的时间范围内恰当地做出反应。

实时系统的调度算法,可以是静态或动态的。前者在系统开始运行之前作出调度决策,后者在运行过程中进行调度决策。只有在可以提前掌握所完成的工作以及必须满足的截止时间等全部信息时,静态调度才能工作,而动态调度算法不需要这些限制。

<7> 调度策略和机制

存在的问题

到目前为止,我们都假设系统中所有进程分属不同的用户,并且进程间相互竞争CPU。但有时候也有其他情况:一个进程有许多子进程并在其控制下运行。各个子进程间的重要性,只要主进程最清楚。

上述的调度算法并没有一个算法从用户进程接收有关的调度决策信息,这导致了调度程序很少能做出最优的选择。

解决方案

解决问题的方法就是将调度机制和调度策略分离。通过将调度算法以某种形式参数化,而参数可以由用户进程填写。假设内核使用优先级调度算法,并提供一条可供进程设置(并改变)优先级的系统调用。这样子,尽管父进程本身并不参与调度,但是它可以控制如何调度子进程的细节。

调度机制位于内核,而调度策略由用户进程决定。策略与机制分离是一种关键性思路。

三. 线程

1. 线程的优点

<1> 同一个进程的所有并行线程拥有共享同一个地址空间和所有可用数据的能力。

<2> 线程比进程更轻量级,更容易创建和销毁。

<3> 对于大量I/O处理,多个线程并行执行能加快应用程序的速度。

<4> 对于多CPU系统中,多线程能够实现真正并行。

2. 线程模型

<1> 线程模型 = 程序计数器 + 寄存器 + 堆栈

程序计数器:用来记录要执行哪一条指令;

寄存器:用来保存线程当前的工作变量;

堆栈:用来记录执行历史;

操作系统的基础——进程与线程_第3张图片

<2> 线程是被CPU调度执行的实体。

<3> 在同一个进程环境中,允许彼此之间有较大独立性的多个线程执行。与多个进程共享物理内存,磁盘,打印机和其他资源一样,多个线程共享同一地址空间和其他资源。

<4> 线程的状态与进程一样,都有就绪,运行,阻塞,终止状态。

<5> 线程的转换与进程的转换是一样的。

3. POSIX模型

为了实现可移植的线程程序,IEEE在标准1003.1c中定义了线程的标准,它定义的线程包叫做pthread。大部分UNIX系统都支持该标准。这个标准定义了超过60个函数调用。以下描述一些主要的函数:

<1> pthread_create:创建

创建一个新线程。调用该方法后,新创建的线程的线程标识符会作为函数值返回,用于标识线程。

<2> pthread_exit:结束

结束调用的线程。当一个线程完成分配给它的工作,可以通过该方法来终止,这个调用终止该线程并释放它的栈。

<3> pthread_join:等待

等待一个特定的线程退出。一般一个线程在继续运行前需要等待另一个线程完成它的工作并退出,可以通过pthread_join线程调用来等待别的特定线程的终止。

<4> pthread_yield:主动释放

释放CPU来运行另外一个线程。在某些情况下,可以调用该方法,比如:一个线程逻辑上没有阻塞,但感觉上它已经运行了足够长时间并且希望给另外一个线程机会去运行。此时可以调用该方法去实现该目标。另外补充一点,在进程中是没有这种调用的。

<5> pthread_attr_init:初始化属性结构

创建并初始化一个线程的属性结构。通过该调用建立关联一个线程的属性结构并初始化成默认值,这些值可以通过修改属性结构的域值来改变。

<6> pthread_attr_destroy:删除属性结构

删除一个线程的属性结构。该调用会释放它占用的内存,它不会影响调用它的线程,这些线程会继续存在。

4. 线程实现

<1> 在用户空间实现

内容

把整个线程包放在用户空间中,内核对线程包一无所知。在用户空间管理线程时,每个进程需要有其专用的线程表,用来跟踪该进程中的线程。这些表和内核中的进程表类似,不过它仅仅记录各个线程的属性,如每个线程的程序计数器,堆栈指针,寄存器和状态等该线程表由运行时系统管理。当一个线程转换到就绪状态或阻塞状态时,在该线程表中存放重新启动该线程所需的信息,与内核在进程表中存放进程的信息完全一样。

操作系统的基础——进程与线程_第4张图片

优点

1~:用户级线程包可以在不支持线程的操作系统上实现;

2~:线程切换比陷入内核至少快一个数量级;

3~:线程调度非常快捷,保存线程状态和调度程序都只是本地过程;

缺点

1~:如何实现阻塞的系统调用;

2~:缺页中断问题;

3~:如果一个线程开始运行,在进程中的其他线程就不能运行;

<2> 在内核中实现

内容

内核中有用来记录系统中所有线程的线程表,当某个线程希望创建一个新线程或撤销一个已有线程时,它进行一个系统调用,这个调用通过对线程表的更新完成线程创建或撤销工作。

内核的线程表保存了每个线程的寄存器,状态和其他信息,这些信息和在用户空间的线程表是一样的,只是保存在内核中。

由于在内核中创建或者撤销线程的代价比较大,某些系统采取"环保"的处理方式,回收其线程。当某个线程被撤销时,就把它标记为不可运行的,但其数据结构没有销毁。稍后在必须创建的时候,就重新启动某个旧线程,从而节省一些开销。

操作系统的基础——进程与线程_第5张图片

优点

所有能够阻塞线程的调用都以系统调用的形式实现。

缺点

多线程进程创建问题:创建进程时是否创建和原进程相同数量的线程?

信号问题:信号应该交给进程中的哪一个线程?

<3> 混合实现

内容

通过使用内核级线程,将用户级线程和某些或者全部内核线程复用起来。采用这种方法,编程人员可以决定有多少个内核级线程和多少个用户级线程彼此多路复用。

采用这种方法,内核只识别内核级线程,并对其进行调度。其中一些内核级线程会被多个用户级线程多路复用。

优点

灵活。

5. 调度程序激活机制

调度程序激活机制是一种保持内核级线程优良特性的前提下,改进其速度的机制。调度程序激活机制的目标是模拟内核线程的功能,但是为线程包提供通常在用户空间中才能实现的更多的性能和更大的灵活性。通过避免在用户空间和内核空间的不必要转换,提高效率。

6. 弹出式线程

一个消息的到达导致系统创建一个处理该消息的线程,这种线程称为弹出式线程。这种线程相当新,没有历史数据,因此创建这种线程非常快,并可以对该新线程指定所要处理的消息。

最终,使用弹出式线程,消息到达和处理开始之间的时间非常短。

 

 

你可能感兴趣的:(操作系统,操作系统)