操作系统之进程管理

826 阅读15分钟

「这是我参与2022首次更文挑战的第34天,活动详情查看:2022首次更文挑战

进程与线程

进程

进程是资源分配的基本单位

进程控制块(Process Control Block,PCB)是操作系统核心中一种数据结构,主要表示进程状态,包括进程状态、程序计数器、CPU寄存器、CPU排版法、存储器管理、会计信息、输入输出状态。创建进程的时候会创建PCB、销毁进程的时候会销毁PCB。

虚拟地址空间

进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,页表查找是一个很慢的过程,因此通常使用Cache来缓存常用的地址映射,这样可以加速页表的查找,这个Cache就是TLB(transaction Lookaside Buffer)。

由于每个进程都有自己的虚拟地址空间,那么显然每个进程都有自己的页表,那么当进程切换后页表也需要切换,页表切换后TLB就失效了,Cache失效导致命中率降低,那么虚拟地址转换为物理地址就会变慢,表现出来的就是程序运行会变慢。线程共享进程资源,切换不会。

进程状态

进程在执行时,状态会改变。所谓状态,就是指进程目前的动作:

  • 新生(new):进程新产生中。
  • 执行(runing):正在执行。
  • 等待(waiting):等待某事发生,例如等待用户输入完成。亦称“阻塞”(blocked)
  • 就绪(ready):排班中,等待CPU。
  • 结束(terminated):完成执行。

线程

线程是操作系统能够进行运算调度的最小单位。大部分情况下,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

线程是独立调度和分派的基本单位。

同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但是同一进程中的多个线程有各自的调用栈,自己的寄存环境,自己的线程本地存储。

线程状态

  • 产生(spawn)
  • 阻塞(block)
  • 非阻塞(unblock)
  • 结束(finish)

线程的分类

从线程的运行空间来说,分为用户线程(user-level thread,ULT)和内核级线程(kernel-level,KLT)

内核级线程:这类线程依赖于内核,又称为内核支持的线程或轻量级进程。无论是在用户程序中的线程还是系统进程中的线程,它们的创建、撤销和切换都由内核实现。 比如英特尔i5-8250U是4核8线程,这里的线程就是内核级线程。

用户级线程:它仅存在于用户级中,这些线程是不依赖于操作系统核心的。应用进程利用线程库来完成其创建和管理,速度比较快,操作系统内核无法感知用户级线程的存在。

区别

拥有资源

进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。

调度

线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。

系统开销

由于创建和撤销进程时,系统都需要为之分配和回收资源,如内存空间、I/O设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行CPU环境的保存及新调度进程CPU环境的设置,而线程的切换时只需保存和设置少量寄存器内容,开销很小。

通信方面

线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助IPC(Inter-Process Communication,进程间传送数据或信号的一些技术或方法,如本地可以使用文件、共享内存等,远程可以通过http等。)。

进程状态的切换

image.png

注意

  • 只有就绪状态和运行状态可以互相转换,其他的都是单向转换。就绪状态的进程通过调度算法从而获得CPU时间,转为运行状态;而运行状态的进程,在分配给它的CPU时间片用完之后就会转为就绪状态,等待下一次调度。
  • 阻塞状态是缺少需要的资源从而由运行状态转换而来,但该资源不包括CPU时间,缺少CPU时间会从运行态转为就绪态。

进程调度算法

不同环境的调度算法目标不同,因此需要针对不同环境来讨论调度算法。

批处理系统

批处理系统没有太多的用户操作,在该系统中,调度算法目标时保证吞吐量和周转时间(从提交到终止的时间)。

先来先服务first-come-first-serverd(FCFS)

非抢占式的调度算法,按照请求的顺序进行调度。

有利于长作业,但不利于短作业,因为短作业必须等待前面的长作业执行完毕才能执行,而长作业需要执行很长时间,造成了短作业等待时间过长。也不利于I/O密集型进程,因为这种进程每次进行I/O操作之后又得重新排队。

短作业优先 shortest job first(SJF)

非抢占式的调度算法,按估计运行时间最短的顺序进行调度。

长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。

最短剩余时间优先 shrotest remaining time next(SRTN)

抢占式的调度算法,按剩余运行时间的顺序进行调度。

当一个新的作业到达时,其整个运行时间与当前进程的剩余时间作比较。如果新的进行需要的时间更少,则挂起当前进程,运行新的进程。否则新的进程就等待。

笔者思考

和SJF有点类似,但是不一样的是,SRTN本质上是FCFS但是新的作业进来时SRTN会根据剩余时间进行抢占。应该是FCFS+新进来的根据剩余时间抢占。是比较综合的算法。

交互系统

交互系统有大量的用户交互操作,在该系统中调度算法的目标是快速地进行响应。

时间片轮转

image.png

将所有就绪进程按FCFS的原则排列成一个队列,每次调度时,把CPU时间分配给对首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把CPU时间分配给对首的进程。

时间片轮转算法的效率和时间片的大小有很大关系:

  • 因为进程切换都要保存进程的信息并且载入新的进程的信息,如果时间片的太小,会导致进程切换得太频繁,在进程上切换就花费太多的时间。
  • 而如果时间片过长,那么实时性就不能得到保证。

笔者思考

所以时间片的选择很重要,可以根据所有执行的任务所花费的时间进行分析,尽量保证大多数任务能及时响应同时CPU的利用率也较高。但是任务是动态的,最好能动态每隔一段时间进行调整一次时间片。

优先调度

为每个进程分配一个优先级,按优先级进行调度。

为了防止优先级低的进程永远无法等不到调度,可以随着时间的推移增加等待进程的优先级。

笔者思考

调高等待时间优先级比较合理。但是肯定有相关的监视器或者相关的策略去判断等待是否达到了需要调整优先级的要求,需要消耗额外资源。

多级反馈队列

image.png

一个进程执行100个时间片,如果采用时间片轮转调度算法,那么需要交换100次。

多级队列时间是为了这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如1,2,4,8,...。进程在第一个队列没执行完,就会被移动到下一个队列。(避免cpu来会切换,增加cpu的利用率)这种方式下,之前的进程只需要交换7次。

每个队列的优先级也不同,最上面的优先级最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。

可以将这种调度算法看成是时间片轮转调度算法和优先级调度算法的结合。

笔者思考

注意上一个进程如果一直有任务根据优先级调度算法长时间没有被执行会进行优先级调整的,这个算法是比较综合的,一般会选择它。

实时系统

实时系统要求一个请求时间在一个确定时间内得到响应。

分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。

进程同步

临界区

当多个线程访问一个独占性资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,直到临界区的线程放弃临界区为止。

优点:保证在某一时刻只有一个线程能访问数据的简便办法。

缺点:只能用来同步进程内的线程,而不可用来同步多个进程中的线程。(线程共享进程内的资源,所以需要实现进程间的互斥需要一个进程间共享的资源)

互斥量

为协调共同对一个共享资源的单独访问而设计的。互斥量跟临界区很类似,比临界区复杂,互斥对象只有一个(进程间共享的),只有拥有互斥对象的线程才具有访问资源的权限。

优点:使用互斥不仅仅能够在同一应用不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现资源的共享。

缺点:互斥量是可以命名的,也就是说它可以跨进程使用,所以创建互斥量需要的资源更多,所以如果只是为了进程内部使用的话使用临界区会带来速度上的优势并能够减少资源占用。

笔者思考

进程可以理解为资源的容器,线程为执行单元,进程同步的本质还是线程同步,只不过所在的环境不一样(拥有的资源不一样)。互斥量像是临界区的进阶,关键点在于互斥对象,互斥对象是进程间共享的。同一台机器内可以做到共享互斥对象,比如共享内核内存,对于java开发中常用的就是分布式锁,可以说是降维的。

信号量

信号量(semaphore),是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaaphore对象的等待(wait)时,该值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态。

使用: 为控制一个具有有限数量用户资源而设计。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。互斥量是信号量的一种特殊情况,当信号量的最大资源数=1就是互斥量了。

优点:适用于对Socket(套接字)程序中线程的同步。

缺点:信号量机制必须有公共内存,不能用于分布式操作系统,这是它最大的弱点。

PV操作

计数信号量具备两种操作动作,常称为V(signal())与P(wait())。V操作会增加信号标S的数值,P操作会减少它。

运作方式:

  1. 初始化,给一个非负数的整数值。
  2. 执行P(wait()),信号标S的值将被减少。企图进入临界区段的进程,需要先执行P(wait())。当信号标S减为负值时,进程会被挡住,不能继续;当信号标S不为负值时,进程可以获准进入临界区段。
  3. 执行V(signal()),信号标S的值会被增加。结束离开临界区段的进程,将会执行V(signal())。当信号标S不为负值时,先前被遮挡的其他进程,将可获准进入临界区段。

事件

用来通知线程有一些事情已发生,从而启动后继任务的开始。

优点:事件对象通过通知操作的方式来保持线程的同步,并且可以实现不同进程中的线程同步操作。

进程通信

进程间通信(IPC,Inter-Process Communication),指至少两个进程或者线程间传送数据或信号的一些技术和方法。

进程是计算机系统分配资源的最小单位(严格来说是线程)。每个进程都是自己的一部分独立的洗头膏资源,彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作,才有了进程间通信。通常,使用进程间通信的两个应用可以被分为客户端和服务器(见[主从式架构]),客户端进程请求数据,服务端响应客户端的数据请求。有些应用本身即使服务器又是客户端,这是在分布式计算中,时常可以见到的。这些进程可以运行在同一计算机上或者网络连接的不同计算机上。

IPC对微内核和nano内核的设计过程非常重要。微内核减少了内核提供的功能数量。然后通过IPC与服务器通信获得这些功能,与普通的宏内核相比,IPC的数量大幅增加。

使用IPC的理由:

  • 信息共享:web服务器,通过网页浏览器使用进程间通信来共享web文件(网页等)和多媒体
  • 加速:维基百科使用通过进程间通信交流的多服务器来满足用户的请求
  • 模块化
  • 私有权分离

管道

管道是通过调用pipe函数创建的,fd[0]用于读,fd[1]用于写。

#include <unistd.h>
int pipe(int fd[2]);

它具有以下限制:

  • 只支持半双工通信(单向交替传输);
  • 只能在父子进程或者兄弟进程中使用。

image.png

FIFO

也成为命名管道,去除了只能在父子进程中使用的限制。

#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);

FIFO常用于客户-服务应用程序中,FIFO用作汇聚点,在客户进程和服务进程之间传递数据。

image.png

消息队列

相比于FIFO,消息队列具有以下优点:

  • 消息队列可以独立于读写进程存在,从而避免了FIFO中同步管道的打开和关闭时可能产生的困难。
  • 避免了FIFO的同步阻塞问题,不需要进程自己提供同步方法。
  • 读进程可以根据消息类型有选择地接收消息,而不像FIFO那样只能默认地接收。

信号量

它是一个计数器,用于为多个进程提供对象数据对象的访问。

共享存储

允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种IPC。

需要使用信号量来同步共享存储的访问。

多个进程可以将同一个文件映射到它们的地址空间从而实现共享内存。另外XSI共享内存不是使用文件,而是内存的匿名段。

套接字

与其它通信机制不同的是,它可用于不同机器间的进程通信。

信号

信号是一种比较复杂的通信方式,信号可以在任何时候发给某一个进程,而无需知道该进程的状态。用于程序终止、推出、定时等。

笔者思考

IPC最开始应该是单机内的概念,进程中的通信逐渐演变成分布式架构。单机的思想和策略在分布式同样适用比如消息队列。

参考

维基百科

进程控制块

线程

进程间的通信

调度 (计算机)

java并发编程——临界区,互斥量,信号量

信号量

信号 ...