=====B102第二章进程管理==== (4)

简介: 2.5 进程通信  进程通信,是指进程之间的信息交换,其所交换的信息量少者是一个状态或数值,多者则是成千上万个字节。进程之间的互斥和同步,由于其所交换的信息量少而被归结为低级通信。在进程互斥中,进程通过只修改信号量来向其他进程表明临界资源是否可用。在生产者—消费者问题中,生产者通过缓冲池将所生产的产品传送给消费者。

=====B102第二章进程管理==== (3)https://developer.aliyun.com/article/1415748

2.5 进程通信

进程通信,是指进程之间的信息交换,其所交换的信息量少者是一个状态或数值,多者则是成千上万个字节。进程之间的互斥和同步,由于其所交换的信息量少而被归结为低级通信。在进程互斥中,进程通过只修改信号量来向其他进程表明临界资源是否可用。在生产者—消费者问题中,生产者通过缓冲池将所生产的产品传送给消费者。

 应当指出,信号量机制作为同步工具是卓有成效的,但作为通信工具,则不够理想, 主要表现在下述两方面:

 (1) 效率低,生产者每次只能向缓冲池投放一个产品(消息),消费者每次只能从缓冲区中取得一个消息;

 (2) 通信对用户不透明。 可见,用户要利用低级通信工具实现进程通信是非常不方便的。因为共享数据结构的设置、数据的传送、进程的互斥与同步等,都必须由程序员去实现,操作系统只能提供共享存储器。

 本节所要介绍的是高级进程通信,是指用户可直接利用操作系统所提供的一组通信命 令高效地传送大量数据的一种通信方式。操作系统隐藏了进程通信的实现细节。或者说, 通信过程对用户是透明的。这样就大大减少了通信程序编制上的复杂性。


###2.5.1 共享存储器系统

 随着 OS 的发展,用于进程之间实现通信的机制也在发展,并已由早期的低级进程通信 机制发展为能传送大量数据的高级通信工具机制。目前,高级通信机制可归结为三大类:共享存储器系统、消息传递系统以及管道通信系统。


共享器存储系统

 在共享存储器系统中,相互通信的进程共享某些数据结构或共享存储区,进程之间能够通过这些空间进行通信。据此,又可把它们分成以下两种类型:   (1) 基于共享数据结构的通信方式。在这种通信方式中,要求诸进程公用某些数据结构, 借以实现诸进程间的信息交换。这里,公用数据结构的设置及对进程间同步的处理,都是程序员的职 责。这无疑增加了程序员的负担,而操作系统却只须提供共享存储器。因此,这种通信方式是低效的,只适于传递相对少量的数据。

  (2) 基于共享存储区的通信方式。为了传输大量数据,在存储器中划出了一块共享存储 区,诸进程可通过对共享存储区中数据的读或写来实现通信。这种通信方式属于高级通信。进程在通信前,先向系统申请获得共享存储区中的一个分区,并指定该分区的关键字;若系统已经给其他进程分配了这样的分区,则将该分区的描述符返回给申请者,继之,由申请者把获得的共享存储分区连接到本进程上;此后,便可像读、写普通存储器一样地读、写该公用存储分区。


消息传递系统

 消息传递系统是当前应用最为广泛的一种进程间的通信机制。 在该机制中,进程间的数据交换是以格式化的消息(message)为单位的;在计算机网络中,又把 message 称为报文。程序员直接利用操作系统提供的一组通信命令(原语),不仅能实现大量数据的传递,而且还隐藏了通信的实现细节,使通信过程对用户是透明的,从而大大减化了通信程序编制的复杂性,因而获得了广泛的应用。特别值得一提的是,在当今最为流行的微内核操作系统中,微内核与服务器之间的通信,无一例外地都采用了消息传递机制。又由于它能很好地支持多处理机系统、分布式系统和计算机网络,因此它也成为这些领域最主要的通信工具。消息传递系统的通信方式属 于高级通信方式。又因其实现方式的不同而进一步分成直接通信方式和间接通信方式两种。


管道通信

 所谓“管道”,是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名 pipe 文件。向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入管道;而接受管道输出的接收进程(即读进程),则从管道中接收(读)数据。 由于发送进程和接收进程是利用管道进行通信的,故又称为管道通信。这种方式首创于 UNIX 系统,由于它能有效地传送大量数据,因而又被引入到许多其它的操作系统中。为了协调双方的通信,管道机制必须提供以下三方面的协调能力:

 (1) 互斥,即当一个进程正在对 pipe 执行读/写操作时,其它(另一)进程必须等待。

 (2) 同步,指当写(输入)进程把一定数量(如 4 KB)的数据写入 pipe,便去睡眠等待,直到读(输出)进程取走数据后,再把它唤醒。当读进程读一空 pipe 时,也应睡眠等待,直至写进程将数据写入管道后,才将之唤醒。

 (3) 确定对方是否存在,只有确定了对方已存在时,才能进行通信。

2.5.2 消息传递通信的实现方法

在进程之间通信时,源进程可以直接或间接地将消息传送给目标进程,由此可将进程通信分为直接通信和间接通信两种通信方式。


直接通信方式

 这是指发送进程利用 OS 所提供的发送命令,直接把消息发送给目标进程。此时,要求 发送进程和接收进程都以显式方式提供对方的标识符。通常,系统提供下述两条通信命令(原语):

   Send(Receiver,message); 发送一个消息给接收进程;

   Receive(Sender,message); 接收 Sender 发来的消息;

 例如,原语 Send(P2,m1)表示将消息 m1 发送给接收进程 P2;而原语 Receive(P1,m1) 则表示接收由 P1 发来的消息 m1。

 在某些情况下,接收进程可与多个发送进程通信,因此,它不可能事先指定发送进程。例如,用于提供打印服务的进程,它可以接收来自任何一个进程的“打印请求”消息。对于这样的应用,在接收进程接收消息的原语中,表示源进程的参数,也是完成通信后的返回值,接收原语可表示为:

   Receive (id,message);

 我们还可以利用直接通信原语来解决生产者—消费者问题。当生产者生产出一个产品 (消息)后,便用 Send 原语将消息发送给消费者进程;而消费者进程则利用 Receive 原语来得 到一个消息。如果消息尚未生产出来,消费者必须等待,直至生产者进程将消息发送过来。生产者—消费者的通信过程可分别描述如下:

20210701114222822.png

间接通信方式

 间接通信方式指进程之间的通信需要通过作为共享数据结构的实体。该实体用来暂存 发送进程发送给目标进程的消息;接收进程则从该实体中取出对方发送给自己的消息。通常把这种中间实体称为信箱。消息在信箱中可以安全地保存,只允许核准的目标用户随时 读取。因此,利用信箱通信方式,既可实现实时通信,又可实现非实时通信。

 系统为信箱通信提供了若干条原语,分别用于信箱的创建、撤消和消息的发送、接收等。

 (1) 信箱的创建和撤消。进程可利用信箱创建原语来建立一个新信箱。创建者进程应给出信箱名字、信箱属性(公用、私用或共享);对于共享信箱,还应给出共享者的名字。当进程不再需要读信箱时,可用信箱撤消原语将之撤消。

 (2) 消息的发送和接收。当进程之间要利用信箱进行通信时,必须使用共享信箱,并利用系统提供的下述通信原语进行通信:

   Send(mailbox,message); 将一个消息发送到指定信箱;

   Receive(mailbox,message); 从指定信箱中接收一个消息;

 信箱可由操作系统创建,也可由用户进程创建,创建者是信箱的拥有者。据此,可把信箱分为以下三类。

 1) 私用信箱

   用户进程可为自己建立一个新信箱,并作为该进程的一部分。信箱的拥有者有权从信 箱中读取消息,其他用户则只能将自己构成的消息发送到该信箱中。这种私用信箱可采用 单向通信链路的信箱来实现。当拥有该信箱的进程结束时,信箱也随之消失。

 2) 公用信箱

   它由操作系统创建,并提供给系统中的所有核准进程使用。核准进程既可把消息发送到该信箱中,也可从信箱中读取发送给自己的消息。显然,公用信箱应采用双向通信链路 的信箱来实现。通常,公用信箱在系统运行期间始终存在。

 3) 共享信箱

   它由某进程创建,在创建时或创建后指明它是可共享的,同时须指出共享进程(用户) 的名字。信箱的拥有者和共享者都有权从信箱中取走发送给自己的消息。 在利用信箱通信时,在发送进程和接收进程之间存在以下四种关系:

   (1) 一对一关系。这时可为发送进程和接收进程建立一条两者专用的通信链路,使两者之间的交互不受其他进程的干扰。

   (2) 多对一关系。允许提供服务的进程与多个用户进程之间进行交互,也称为客户/服 务器交互。

   (3) 一对多关系。允许一个发送进程与多个接收进程进行交互,使发送进程可用广播方 式向接收者(多个)发送消息。

   (4) 多对多关系。允许建立一个公用信箱,让多个进程都能向信箱中投递消息;也可从 信箱中取走属于自己的消息。

2.5.3 消息传递系统实现中的若干问题

在单机和计算机网络环境下,高级进程通信广泛采用消息传递系统。故本小节将对这种通信中的几个主要问题做扼要的阐述。


通信链路

 为使在发送进程和接收进程之间能进行通信,必须在两者之间建立一条通信链路。有两种方式建立通信链路。

   第一种方式是由发送进程在通信之前用显式的“建立连接”命令(原语)请求系统为之建立一条通信链路;在链路使用完后,也用显式方式拆除链路。这种方式主要用于计算机网络中。

   第二种方式是发送进程无须明确提出建立链路的请求,只须利用系统提供的发送命令(原语),系统会自动地为之建立一条链路。这种方式主要用于单机系统中。

 根据通信链路的连接方法,又可把通信链路分为两类:

   (1) 点——点连接通信链路,这时的一条链路只连接两个结点(进程);

   (2) 多点连接链路,指用一条链路连接多个(n>2)结点(进程)。

 而根据通信方式的不同,则又可把链路分成两种:

   (1) 单向通信链路,只允许发送进程向接收进程发送消息,或者相反;

   (2) 双向链路,既允许由进程 A 向进程 B 发送消息,也允许进程 B 同时向进程 A 发送 消息。

 还可根据通信链路容量的不同而把链路分成两类:

   一是无容量通信链路,在这种通信链路上没有缓冲区,因而不能暂存任何消息;

   二是有容量通信链路,指在通信链路中 设置了缓冲区,因而能暂存消息。缓冲区数目愈多,通信链路的容量愈大。


消息的格式

 在消息传递系统中所传递的消息,必须具有一定的消息格式。在单机系统环境中,由于发送进程和接收进程处于同一台机器中,有着相同的环境,故其消息格式比较简单;但在计算机网络环境下,不仅源和目标进程所处的环境不同,而且信息的传输距离很远,可能要跨越若干个完全不同的网络,致使所用的消息格式比较复杂。通常,可把一个消息分成消息头和消息正文两部分。消息头包括消息在传输时所需的控制信息,如源进程名、目 标进程名、消息长度、消息类型、消息编号及发送的日期和时间;而消息正文则是发送进 程实际上所发送的数据。

 在某些 OS 中,消息采用比较短的定长消息格式,这便减少了对消息的处理和存储开销。这种方式可用于办公自动化系统中,为用户提供快速的便笺式通信;但这对要发送较长消息的用户是不方便的。在有的 OS 中,采用变长的消息格式,即进程所发送消息的长度是可变的。系统无论在处理还是在存储变长消息时,都可能会付出更多的开销,但这方便了用户。这两种消息格式各有其优缺点,故在很多系统(包括计算机网络)中,是同时都用的。


进程同步方式

 在进程之间进行通信时,同样需要有进程同步机制,以使诸进程间能协调通信。不论是发送进程,还是接收进程,在完成消息的发送或接收后,都存在两种可能性,即进程或者继续发送(接收),或者阻塞。由此,我们可得到以下三种情况:   (1) 发送进程阻塞,接收进程阻塞。这种情况主要用于进程之间紧密同步,发送进程和接收进程之间无缓冲时。这两个进程平时都处于阻塞状态,直到有消息传递时。这种同步方式称为汇合。

 (2) 发送进程不阻塞,接收进程阻塞。这是一种应用最广的进程同步方式。平时,发送进程不阻塞,因而它可以尽快地把一个或多个消息发送给多个目标; 而接收进程平时则处 于阻塞状态,直到发送进程发来消息时才被唤醒。例如,在服务器上通常都设置了多个服务进程,它们分别用于提供不同的服务,如打印服务。平时,这些服务进程都处于阻塞状 态,一旦有请求服务的消息到达时,系统便唤醒相应的服务进程,去完成用户所要求的服 务。处理完后,若无新的服务请求,服务进程又阻塞。

 (3) 发送进程和接收进程均不阻塞。这也是一种较常见的进程同步形式。平时,发送进程和接收进程都在忙于自己的事情,仅当发生某事件使它无法继续运行时,才把自己阻塞起来等待。例如,在发送进程和接收进程之间联系着一个消息队列时,该消息队列最多能 接纳 n 个消息,这样,发送进程便可以连续地向消息队列中发送消息而不必等待;接收进程也可以连续地从消息队列中取得消息,也不必等待。只有当消息队列中的消息数已达到 n个时,即消息队列已满,发送进程无法向消息队列中发送消息时才会阻塞;类似地,只有 当消息队列中的消息数为 0,接收进程已无法从消息队列中取得消息时才会阻塞。

2.5.4 消息缓冲队列通信机制

消息缓冲队列通信机制首先由美国的 Hansan 提出,并在 RC 4000 系统上实现,后来被广泛应用于本地进程之间的通信中。在这种通信机制中,发送进程利用 Send 原语将消息直接发送给接收进程;接收进程则利用 Receive 原语接收消息。


消息缓冲队列通信机制中的数据结构

1)消息缓冲区

 在消息缓冲队列通信方式中,主要利用的数据结构是消息缓冲区。它可描述如下:

20210701121150542.png

2)PCB 中有关通信的数据项

 在操作系统中采用了消息缓冲队列通信机制时,除了需要为进程设置消息缓冲队列外, 还应在进程的 PCB 中增加消息队列队首指针,用于对消息队列进行操作,以及用于实现同 步的互斥信号量 mutex 和资源信号量 sm。在 PCB 中应增加的数据项可描述如下:

20210701121244575.png

发送原语

 发送进程在利用发送原语发送消息之前,应先在自己的内存空间设置一发送区 a,见图 2-14 所示。把待发送的消息正文、发送进程标识符、消息长度等信息填入其中,然后调用发送原语,把消息发送给目标(接收)进程。发送原语首先根据发送区 a 中所设置的消息长度 a.size 来申请一缓冲区 i,接着把发送区 a 中的信息复制到缓冲区 i 中。为了能将 i 挂在接收进程的消息队列 mq 上,应先获得接收进程的内部标识符 j,然后将 i 挂在 j.mq 上。由于该队列属于临界资源,故在执行 insert 操作的前后,都要执行 wait 和 signal 操作。

 发送原语可描述如下:

20210701121624775.png

20210701121635723.png

  1. 接收原语
      接收进程调用接收原语 receive(b),从自己的消息缓冲队列 mq 中摘下第一个消息缓冲区 i,并将其中的数据复制到以 b 为首址的指定消息接收区内。接收原语描述如下:

2021070112173165.png

2.6 线程

自从在 20 世纪 60 年代人们提出了进程的概念后,在 OS 中一直都是以进程作为能拥有资源和独立运行的基本单位的。直到 20 世纪 80 年代中期,人们又提出了比进程更小的能 独立运行的基本单位——线程(Threads),试图用它来提高系统内程序并发执行的程度,从而可进一步提高系统的吞吐量。特别是在进入 20 世纪 90 年代后,多处理机系统得到迅速发 展,线程能比进程更好地提高程序的并行执行程度,充分地发挥多处理机的优越性,因而 在近几年所推出的多处理机 OS 中也都引入了线程,以改善 OS 的性能。

2.6.1 线程的基本概念

线程的引入

 在操作系统中再引入线程,是为了减少程序在并发执行时所付出的时空开销,使 OS 具有更好的并发性。为了说明这一点,我们首先来回顾进程的两

个基本属性:

    ① 进程是一个可拥有资源的独立单位;

    ② 进程同时又是一个可独立调度和分派的基本单位。

 正是由于进程有这两个基本属性,才使之成为一个能独立运行的基本单位, 从而也就构成了进程并发执行的基础。然而,为使程序能并发执行,系统还必须进行以下的一系列操作。

 1) 创建进程

 2) 撤消进程

 3) 进程切换


 换言之,由于进程是一个资源的拥有者,因而在创建、撤消和切换中,系统必须为之付出较大的时空开销。正因如此,在系统中所设置的进程,其数目不宜过多,进程切换的频率也不宜过高,这也就限制了并发程度的进一步提高。 如何能使多个程序更好地并发执行同时又尽量减少系统的开销,已成为近年来设计操作系统时所追求的重要目标。有不少研究操作系统的学者们想到,若能将进程的上述两个

属性分开,由操作系统分开处理,亦即对于作为调度和分派的基本单位,不同时作为拥有资源的单位,以做到“轻装上阵”;而对于拥有资源的基本单位,又不对之进行频繁的切换。正是在这种思想的指导下,形成了线程的概念。

 因为进程“太重”,致使实现多处理机环境下的进程调度、分派和切换时,都 需花费较大的时间和空间开销。如果在 OS 中引入线程,以线程作为调度和分派的基本单位,则可以有效地改善多处理机系统的性能。因此,一些主要的 OS(UNIX、OS/2、Windows)厂 家都又进一步对线程技术做了开发,使之适用于 SMP 的计算机系统。


线程与进程的比较

 线程具有许多传统进程所具有的特征,所以又称为轻型进程或进程元,相应地把传统进程称为重型进程,传统进程相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都拥有若干个线程,至少也有一个线程。

 下面我们从调度性、并发性、系统开销和拥有资源等方面对线程和进程进行比较。

调度

 在传统的操作系统中,作为拥有资源的基本单位和独立调度、分派的基本单位都是进程。而在引入线程的操作系统中,则把线程作为调度和分派的基本单位,而进程作为资源拥有的基本单位,把传统进程的两个属性分开,使线程基本上不拥有资源,这样线程便能轻装前进,从而可显著地提高系统的并发程度。在同一进程中,线程的切换不会引起进程的切换,但从一个进程中的线程切换到另一个进程中的线程时,将会引起进程的切换。

并发性

 在引入线程的操作系统中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间亦可并发执行,使得操作系统具有更好的并发性,从而能更加有效地提高系统资源的利用率和系统的吞吐量。例如,在一个未引入线程的单 CPU 操作系统中,若仅设置一个文件服务进程,当该进程由于某种原因而被阻塞时,便没有其它的文件服务进程来提供服务。在引入线程的操作系统中,则可以在一个文件服务进程中设置多个服务线程。当第一 个线程等待时,文件服务进程中的第二个线程可以继续运行,以提供文件服务;当第二个 线程阻塞时,则可由第三个继续执行,提供服务。显然,这样的方法可以显著地提高文件 服务的质量和系统的吞吐量。

拥有资源

 不论是传统的操作系统,还是引入了线程的操作系统,进程都可以拥有资源,是系统中拥有资源的一个基本单位。一般而言,线程自己不拥有系统资源(也有一点必不可少的资 源),但它可以访问其隶属进程的资源,即一个进程的代码段、数据段及所拥有的系统资源, 如已打开的文件、I/O 设备等,可以供该进程中的所有线程所共享。

系统开销

 在创建或撤消进程时,系统都要为之创建和回收进程控制块,分配或回收资源,如内存空间和 I/O 设备等,操作系统所付出的开销明显大于线程创建或撤消时的开销。类似地,在进程切换时,涉及到当前进程 CPU 环境的保存及新被调度运行进程的 CPU 环境的设置,而线程的切换则仅需保存和设置少量寄存器内容,不涉及存储器管理方面的操作,所以就 切换代价而言,进程也是远高于线程的。此外,由于一个进程中的多个线程具有相同的地址空间,在同步和通信的实现方面线程也比进程容易。在一些操作系统中,线程的切换、 同步和通信都无须操作系统内核的干预。

线程的属性

 在多线程 OS 中,通常是在一个进程中包括多个线程,每个线程都是作为利用 CPU 的 基本单位,是花费最小开销的实体。线程具有下述属性。

 (1) 轻型实体。线程中的实体基本上不拥有系统资源,只是有一点必不可少的、 能保 证其独立运行的资源,比如,在每个线程中都应具有一个用于控制线程运行的线程控制块 TCB,用于指示被执行指令序列的程序计数器,保留局部变量、少数状态参数和返回地址 等的一组寄存器和堆栈。

 (2) 独立调度和分派的基本单位。在多线程 OS 中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很“轻”,故线程的切换非常迅速且开销小。

 (3) 可并发执行。在一个进程中的多个线程之间可以并发执行,甚至允许在一个进程中的所有线程都能并发执行;同样,不同进程中的线程也能并发执行。

 (4) 共享进程资源。在同一进程中的各个线程都可以共享该进程所拥有的资源,这首先表现在所有线程都具有相同的地址空间(进程的地址空间)。这意味着线程可以访问该地址空间中的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构 等。


线程的状态

 (1) 状态参数。在 OS 中的每一个线程都可以利用线程标识符和一组状态参数进行描述。状态参数通常有这样几项:

   ① 寄存器状态,它包括程序计数器 PC 和堆栈指针中的内容;

   ② 堆栈,在堆栈中通常保存有局部变量和返回地址;

   ③ 线程运行状态,用于描述线程正处于何种运行状态;

   ④ 优先级,描述线程执行的优先程度;

   ⑤ 线程专有存储器,用于保 存线程自己的局部变量拷贝;

   ⑥ 信号屏蔽,即对某些信号加以屏蔽。

 (2) 线程运行状态。如同传统的进程一样,在各线程之间也存在着共享资源和相互合作 的制约关系,致使线程在运行时也具有间断性。相应地,线程在运行时也具有下述三种基本状态:

   ① 执行状态,表示线程正获得处理机而运行;

   ② 就绪状态,指线程已具备了各种执行条件,一旦获得 CPU 便可执行的状态;

   ③ 阻塞状态,指线程在执行中因某事件而受 阻,处于暂停执行时的状态。


线程的创建和终止

 在多线程 OS 环境下,应用程序在启动时,通常仅有一个线程在执行,该线程被人们称 为“初始化线程”。它可根据需要再去创建若干个线程。在创建新线程时,需要利用一个线程创建函数(或系统调用),并提供相应的参数,如指向线程主程序的入口指针、堆栈的大 小,以及用于调度的优先级等。在线程创建函数执行完后,将返回一个线程标识符供以后使用。

 如同进程一样,线程也是具有生命期的。终止线程的方式有两种:

   一种是在线程完成了自己的工作后自愿退出;

   一种是线程在运行中出现错误或由于某种原因而被其它线程 强行终止。

 但有些线程(主要是系统线程),在它们一旦被建立起来之后,便一直运行下去而不再被终止。在大多数的 OS 中,线程被中止后并不立即释放它所占有的资源,只有当进程中的其它线程执行了分离函数后,被终止的线程才与资源分离,此时的资源才能被其它线 程利用。虽已被终止但尚未释放资源的线程,仍可以被需要它的线程所调用,以使被终止线程重新恢复运行。

 为此,调用者线程须调用一条被称为“等待线程终止”的连接命令,来与 该线程进行连接。如果在一个调用者线程调用“等待线程终止”的连接命令试图与指定线程相连接时,若指定线程尚未被终止,则调用连接命令的线程将会阻塞,直至指定线程被终止后才能实现它与调用者线程的连接并继续执行;若指定线程已被终止,则调用者线程不会被阻塞而是继续执行。


多线程 OS 中的进程

 在多线程 OS 中,进程是作为拥有系统资源的基本单位,通常的进程都包含多个线程并为它们提供资源,但此时的进程就不再作为一个执行的实体。多线程 OS 中的进程有以下属性:

(1) 作为系统资源分配的单位。在多线程 OS 中,仍是将进程作为系统资源分配的基本 单位,在任一进程中所拥有的资源包括受到分别保护的用户地址空间、用于实现进程间和线程间同步和通信的机制、已打开的文件和已申请到的 I/O 设备,以及一张由核心进程维护 的地址映射表,该表用于实现用户程序的逻辑地址到其内存物理地址的映射。

 (2) 可包括多个线程。通常,一个进程都含有多个相对独立的线程,其数目可多可少,但至少也要有一个线程,由进程为这些(个)线程提供资源及运行环境,使这些线程可并发执行。在 OS 中的所有线程都只能属于某一个特定进程。

 (3) 进程不是一个可执行的实体。在多线程 OS 中,是把线程作为独立运行的基本单位, 所以此时的进程已不再是一个可执行的实体。虽然如此,进程仍具有与执行相关的状态。例如,所谓进程处于“执行”状态,实际上是指该进程中的某线程正在执行。此外,对进程所施加的与进程状态有关的操作,也对其线程起作用。例如,在把某个进程挂起时,该进程中的所有线程也都将被挂起;又如,在把某进激活时,属于该进程的所有线程也都 将被激活。

2.6.2 线程间的同步和通信

为使系统中的多线程能有条不紊地运行,在系统中必须提供用于实现线程间同步和通信的机制。为了支持不同频率的交互操作和不同程度的并行性,在多线程 OS 中通常提供多种同步机制,如互斥锁、条件变量、计数信号量以及多读、单写锁等。


互斥锁

 互斥锁是一种比较简单的、用于实现线程间对资源互斥访问的机制。由于操作互斥锁的时间和空间开销都较低,因而较适合于高频度使用的关键共享数据和程序段。互斥锁可以有两种状态,即开锁(unlock)和关锁(lock)状态。相应地,可用两条命令(函数)对互斥锁进行操作。其中的关锁 lock 操作用于将 mutex 关上,开锁操作 unlock 则用于打开 mutex。

 当一个线程需要读/写一个共享数据段时,线程首先应为该数据段所设置的 mutex 执行关锁命令。命令首先判别 mutex 的状态,如果它已处于关锁状态,则试图访问该数据段的线程将被阻塞;而如果 mutex 是处于开锁状态,则将 mutex 关上后便去读/写该数据段。

 在线程完成对数据的读/写后,必须再发出开锁命令将 mutex 打开,同时还须将阻塞在该互斥锁上的一个线程唤醒,其它的线程仍被阻塞在等待 mutex 打开的队列上。 另外,为了减少线程被阻塞的机会,在有的系统中还提供了一种用于 mutex 上的操作命令 Trylock。当一个线程在利用 Trylock 命令去访问 mutex 时,若 mutex 处于开锁状态, Trylock 将返回一个指示成功的状态码;反之,若 mutex 处于关锁状态,则 Trylock 并不会阻塞该线程,而只是返回一个指示操作失败的状态码。


条件变量

 在许多情况下,只利用 mutex 来实现互斥访问可能会引起死锁,我们通过一个例子来 说明这一点。有一个线程在对 mutex 1 执行关锁操作成功后,便进入一临界区 C,若在临界 区内该线程又须访问某个临界资源 R,同样也为 R 设置另一互斥锁 mutex 2。假如资源 R 此时正处于忙碌状态,线程在对 mutex 2 执行关锁操作后必将被阻塞,这样将使 mutex 1 一直保持关锁状态;如果保持了资源 R 的线程也要求进入临界区 C,但由于 mutex 1 一直保持关 锁状态而无法进入临界区,这样便形成了死锁。为了解决这个问题便引入了条件变量。

 每一个条件变量通常都与一个互斥锁一起使用,亦即,在创建一个互斥锁时便联系着一个条件变量。单纯的互斥锁用于短期锁定,主要是用来保证对临界区的互斥进入。而条件变量则用于线程的长期等待,直至所等待的资源成为可用的资源。

 现在,我们看看如何利用互斥锁和条件变量来实现对资源 R 的访问。线程首先对 mutex执行关锁操作,若成功便进入临界区,然后查找用于描述该资源状态的数据结构,以了解资源的情况。只要发现所需资源 R 正处于忙碌状态,线程便转为等待状态,并对 mutex 执 行开锁操作后,等待该资源被释放;若资源处于空闲状态,表明线程可以使用该资源,于是将该资源设置为忙碌状态,再对 mutex 执行开锁操作。下面给出了对上述资源的申请(左 半部分)和释放(右半部分)操作的描述。

20210701133850635.png

 原来占有资源 R 的线程在使用完该资源后,便按照右半部分的描述释放该资源,其中 的 wakeup表示去唤醒在指定条件变量上等待的一个或多个线程。在大多数情况下,由于所释放的是临界资源,此时所唤醒的只能是在条件变量上等待的某一个线程,其它线程仍继续在该队列上等待。但如果线程所释放的是一个数据文件,该文件允许多个线程同时对它执行读操作。在这种情况下,当一个写线程完成写操作并释放该文件后,如果此时在该条件变量上还有多个读线程在等待,则该线程可以唤醒所有的等待线程。


信号量机制

 前面所介绍的用于实现进程同步的最常用工具——信号量机制,也可用于多线程 OS中,实现诸线程或进程之间的同步。为了提高效率,可为线程和进程分别设置相应的信号量。


私用信号量(private samephore)

 当某线程需利用信号量来实现同一进程中各线程之间的同步时,可调用创建信号量的命令来创建一私用信号量,其数据结构存放在应用程序的地址空间中。私用信号量属于特定的进程所有,OS 并不知道私用信号量的存在,因此,一旦发生私用信号量的占用者异常结束或正常结束,但并未释放该信号量所占有空间的情况时,系统将无法使它恢复为 0(空),也不能将它传送给下一个请求它的线程。

公用信号量(public semaphort)

 公用信号量是为实现不同进程间或不同进程中各线程之间的同步而设置的。由于它有着一个公开的名字供所有的进程使用,故而把它称为公用信号量。其数据结构是存放在受保护的系统存储区中,由 OS 为它分配空间并进行管理,故也称为系统信号量。如果信号量的占有者在结束时未释放该公用信号量,则 OS 会自动将该信号量空间回收,并通知下一进程。可见,公用信号量是一种比较安全的同步机制。

2.6.3 线程的实现方式

内核支持线程

 对于通常的进程,无论是系统进程还是用户进程,进程的创建、 撤消,以及要求由系统设备完成的 I/O 操作,都是利用系统调用而进入内核,再由内核中的相应处理程序予以完成的。进程的切换同样是在内核的支持下实现的。因此我们说,不论什么进程,它们都是在操作系统内核的支持下运行的,是与内核紧密相关的。

这里所谓的内核支持线程 KST,也都同样是在内核的支持下 运行的,即无论是用户进程中的线程,还是系统进程中的线程,他们的创建、撤消和切换 等也是依靠内核,在内核空间实现的。此外,在内核空间还为每一个内核支持线程设置了一个线程控制块,内核是根据该控制块而感知某线程的存在,并对其加以控制。

 这种线程实现方式主要有如下四个优点:


 (1) 在多处理器系统中,内核能够同时调度同一进程中多个线程并行执行;

 (2) 如果进程中的一个线程被阻塞了,内核可以调度该进程中的其它线程占有处理器运行,也可以运行其它进程中的线程;

 (3) 内核支持线程具有很小的数据结构和堆栈,线程的切换比较快,切换开销小;

 (4) 内核本身也可以采用多线程技术,可以提高系统的执行速度和效率。


内核支持线程的主要缺点是:


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


用户级线程

 用户级线程 ULT仅存在于用户空间中。对于这种线程的创建、撤 消、线程之间的同步与通信等功能,都无须利用系统调用来实现。对于用户级线程的切换,

通常发生在一个应用进程的诸多线程之间,这时,也同样无须内核的支持。由于切换的规则远比进程调度和切换的规则简单,因而使线程的切换速度特别快。可见,这种线程是与内核无关的。我们可以为一个应用程序建立多个用户级线程。在一个系统中的用户级线程 的数目可以达到数百个至数千个。由于这些线程的任务控制块都是设置在用户空间,而线 程所执行的操作也无须内核的帮助,因而内核完全不知道用户级线程的存在。

 值得说明的是,对于设置了用户级线程的系统,其调度仍是以进程为单位进行的。在采用轮转调度算法时,各个进程轮流执行一个时间片,这对诸进程而言似乎是公平的。但假如在进程 A 中包含了一个用户级线程,而在另一个进程 B 中含有 100 个用户级线程,这样,进程 A 中线程的运行时间将是进程 B 中各线程运行时间的 100 倍;相应地,其速度要快上 100 倍。

 假如系统中设置的是内核支持线程,则调度便是以线程为单位进行的。在采用轮转法调度时,是各个线程轮流执行一个时间片。同样假定进程 A 中只有一个内核支持线程,而在进程 B 中有 100 个内核支持线程。此时进程 B 可以获得的 CPU 时间是进程 A 的 100 倍, 且进程 B 可使 100 个系统调用并发工作。

 使用用户级线程方式有许多优点,主要表现在如下三个方面:

 (1) 线程切换不需要转换到内核空间,对一个进程而言,其所有线程的管理数据结构均 在该进程的用户空间中,管理线程切换的线程库也在用户地址空间运行。因此,进程不必切换到内核方式来做线程管理,从而节省了模式切换的开销,也节省了内核的宝贵资源。

 (2) 调度算法可以是进程专用的。在不干扰操作系统调度的情况下,不同的进程可以根 据自身需要,选择不同的调度算法对自己的线程进行管理和调度,而与操作系统的低级调 度算法是无关的。

 (3) 用户级线程的实现与操作系统平台无关,因为对于线程管理的代码是在用户程序内的,属于用户程序的一部分,所有的应用程序都可以对之进行共享。因此,用户级线程甚至可以在不支持线程机制的操作系统平台上实现。


用户级线程实现方式的主要缺点在于如下两个方面:


(1) 系统调用的阻塞问题。在基于进程机制的操作系统中,大多数系统调用将阻塞进程,因此,当线程执行一个系统调用时,不仅该线程被阻塞,而且进程内的所有线程都会被阻塞。而在内核支持线程方式中,则进程中的其它线程仍然可以运行。

 (2) 在单纯的用户级线程实现方式中,多线程应用不能利用多处理机进行多重处理的优点。内核每次分配给一个进程的仅有一个 CPU,因此进程中仅有一个线程能执行,在该线程放弃 CPU 之前,其它线程只能等待。


组合方式

 有些操作系统把用户级线程和内核支持线程两种方式进行组合,提供了组合方式ULT/KST 线程。在组合方式线程系统中,内核支持多 KST 线程的建立、调度和管理,同时,也允许用户应用程序建立、调度和管理用户级线程。一些内核支持线程对应多个用户级线程,程序员可按应用需要和机器配置对内核支持线程数目进行调整,以达到较好的效果。 组合方式线程中,同一个进程内的多个线程可以同时在多处理器上并行执行,而且在阻塞一个线程时,并不需要将整个进程阻塞。所以,组合方式多线程机制能够结合 KST 和 ULT两者的优点,并克服了其各自的不足。

2.6.4 线程的实现

内核支持线程的实现

 在仅设置了内核支持线程的 OS 中,一种可能的线程控制方法是,系统在创建一个新进程时,便为它分配一个任务数据区 PTDA,其中包括若干个线程控制块 TCB 空间,如图 2-15 所示。在每一个 TCB 中可保存线程标识符、优先级、线程运行的 CPU状态等信息。虽然这些信息与用户级线程 TCB 中的信息相同,但现在却是被保存在内核空间中。

20210701145941941.png

每当进程要创建一个线程时,便为新线程分配一个TCB,将有关信息填入该 TCB 中,并为之分配必要的资 源,如为线程分配数百至数千个字节的栈空间和局部存储区,于是新创建的线程便有条件立即执行。当 PTDA中的所有 TCB 空间已用完,而进程又要创建新的线程时,只要其所创建的线程数目未超过系统的允许值(通常为数十至数百个),系统可再为之分配新的 TCB 空间;在撤消一个线程时,也应回收该线程的所有资源和 TCB。

 可见,内核支持线程的创建、 撤消均与进程的相类似。在有的系统中为了减少创建和撤消一个线程时的开销,在撤消一个线程时,并不立即回收该线程的资源和 TCB,当以后再要创建一个新线程时,便可直接利用已被撤消但仍保持有资源和 TCB 的线程作为新线程。

 内核支持线程的调度和切换与进程的调度和切换十分相似,也分抢占式方式和非抢占方式两种。在线程的调度算法上,同样可采用时间片轮转法、优先权算法等。当线程调度选中一个线程后,便将处理机分配给它。当然,线程在调度和切换上所花费的开销,要比进程的小得多。


用户级线程的实现

 用户级线程是在用户空间实现的。所有的用户级线程都具有相同的结构,它们都运行在一个中间系统的上面。当前有两种方式实现的中间系统,即运行时系统和内核控制线程。1)运行时系统(Runtime System)

 所谓“运行时系统”,实质上是用于管理和控制线程的函数(过程)的集合,其中包括用于创建和撤消线程的函数、 线程同步和通信的函数以及实现线程调度的函数等。正因为有这些函数,才能使用户级线程与内核无关。运行时系统中的所有函数都驻留在用户空间,并作为用户级线程与内核之间的接口。

 在传统的 OS 中,进程在切换时必须先由用户态转为核心态,再由核心来执行切换任务; 而用户级线程在切换时则不需转入核心态,而是由运行时系统中的线程切换过程来执行切换任务。该过程将线程的 CPU 状态保存在该线程的堆栈中,然后按照一定的算法选择一个处于就绪状态的新线程运行,将新线程堆栈中的 CPU 状态装入到 CPU 相应的寄存器中,一旦将栈指针和程序计数器切换后,便开始了新线程的运行。

 由于用户级线程的切换无需进 入内核,且切换操作简单,因而使用户级线程的切换速度非常快。 不论在传统的 OS 中,还是在多线程 OS 中,系统资源都是由内核管理的。在传统的OS 中,进程是利用 OS 提供的系统调用来请求系统资源的,系统调用通过软中断(如 trap)机制进入 OS 内核,由内核来完成相应资源的分配。用户级线程是不能利用系统调用的。当线程需要系统资源时,是将该要求传送给运行时系统,由后者通过相应的系统调用来获得系统资源的。



2)内核控制线程

 这种线程又称为轻型进程 LWP。每一个进程都可拥有多个 LWP,同用户级线程一样,每个 LWP 都有自己的数据结构(如 TCB),其中包括线程标识符、优先级、状态,另外还有栈和局部存储区等。它们也可以共享进程所拥有的资源。LWP 可通过系统调用来获得内核提供的服务,这样,当一个用户级线程运行时,只要将它连接到一个LWP 上,此时它便具有了内核支持线程的所有属性。这种线程实现方式就是组合方式。

 在一个系统中的用户级线程数量可能很大,为了节省系统开销,不可能设置太多的LWP,而把这些 LWP 做成一个缓冲池,称为“线程池”。用户进程中的任一用户线程都可 以连接到 LWP 池中的任何一个 LWP 上。为使每一用户级线程都能利用 LWP 与内核通信,可以使多个用户级线程多路复用一个 LWP,但只有当前连接到 LWP 上的线程才能与内核通 信,其余进程或者阻塞,或者等待 LWP。而每一个 LWP 都要连接到一个内核级线程上,这样,通过 LWP 可把用户级线程与内核线程连接起来,用户级线程可通过 LWP 来访问内核, 但内核所看到的总是多个 LWP 而看不到用户级线程。亦即,由 LWP 实现了在内核与用户 级线程之间的隔离,从而使用户级线程与内核无关。


图 2-16 示出了利用轻型进程作为中间系统时用户级线程的实现方法。

20210701150546847.png

则有多个线程并行执行。该模型并行能力较强,但每创建一个用户线程相应地就需要创建一个内核线程,开销 较大,因此需要限制整个系统的线程数。Windows 2000、Windows NT、OS/2 等系统上都实 现了该模型。

多对一模型

 该模型是将多个用户线程映射到一个内核控制线程,为了管理方便,这些用户线程一般属于一个进程,运行在该进程的用户空间,对这些线程的调度和管理也是在该进程的用户空间中完成。当用户线程需要访问内核时,才将其映射到一个内核控制线程上,但每次 只允许一个线程进行映射。 该模型的主要优点是线程管理的开销小,效率高,但当一个线程在访问内核时发生阻 塞,则整个进程都会被阻塞,而且在多处理机系统中,一个进程的多个线程无法实现并行。

多对多模型

 该模型结合上述两种模型的优点,将多个用户线程映射到多个内核控制线程,内核控制线程的数目可以根据应用进程和系统的不同而变化,可以比用户线程少,也可以与之相同。


目录
相关文章
|
10月前
|
消息中间件 算法 Unix
Linux系统编程(进程基础知识讲解)
Linux系统编程(进程基础知识讲解)
105 3
|
3月前
|
Linux 调度 C语言
|
调度
操作系统概论学习(进程管理)
操作系统概论学习(进程管理)
46 0
|
Linux 测试技术
Linux操作系统实验十一 进程管理(下)
Linux操作系统实验十一 进程管理(下)
86 0
|
3月前
|
调度 UED
=====B102第二章进程管理==== (1)
2.1 进程的基本概念   在未配置OS的系统中,程序的执行方式是顺序执行,也就是说必须在一个程序执行完后,才允许另一个程序执行;在多道程序环境下,则允许多个程序并发执行。程序的这两种执行方式间有着显著的不同。也正是程序并发执行的这种特征,才导致了在操作系统中引入了进程的概念。
44 0
|
3月前
|
消息中间件 移动开发 算法
=====B102第二章进程管理==== (3)
2.3.4 管程机制   虽然信号量机制是一种既方便、又有效的进程同步机制,但每个要访问临界资源的进程都必须自备同步操作 wait(S)和 signal(S)。这就使大量的同步操作分散在各个进程中。这不仅给系统的管理带来了麻烦,而且还会因同步操作的使用不当而导致系统死锁。这样, 在解决上述问题的过程中,便产生了一种新的进程同步工具——管程(Monitors)。
25 0
|
3月前
|
消息中间件 存储 算法
=====B102第二章进程管理==== (2)
2.1.5 进程控制块 进程控制块的作用   为了描述和控制进程的运行,系统为每个进程定义了一个数据结构——进程控制块 PCB,PCB是进程实体的一部分,是操作系统中最重要的记录型数据结
56 0
|
Linux 调度 C语言
Linux操作系统实验十一 进程管理(上)
Linux操作系统实验十一 进程管理
379 0
|
Linux 调度 SDN
Linux操作系统实验十一 进程管理(中)
Linux操作系统实验十一 进程管理(中)
130 0
|
安全 Linux
嵌入式Linux C多进程编程(二)——进程概述
嵌入式Linux C多进程编程(二)——进程概述
嵌入式Linux C多进程编程(二)——进程概述