操作系统中的进程和线程模型是核心概念,它们在操作系统的设计和实现中起着至关重要的作用。

第一节 进程的基本概念

进程

1. 什么是进程

具有一定独立功能的程序在某个是数据集合上的运动活动。

进程是操作系统中资源分配和调度的基本单位。它是一个正在执行的程序的实例,包含了程序的代码、数据、堆栈以及程序计数器、寄存器等信息。

进程与程序的联系与区别:

联系:程序是进程的组成成分之一,一个进程的运动目标是执行它所对应的程序;如果没有程序,进程就失去了存在的意义。

从静态角度进程由程序、数据、进程控制块(PCB)组成。

区别:程序是静态的,进程是动态的。

进程是程序的一个执行过程。进程有生命周期,一个进程可以包含若干程序的执行,一个程序也可以产生多个进程。

可再入程序

一个程序不是任何条件下都可以产生多个进程的,一个能被多个用户同时调用的程序,在执行中自身不能改变。这样的能被多个用户同时调用的程序称作"可再入程序"。它必须是"纯"代码的程序且执行中不会修改自身的代码。

进程的特征

OS的并发性和共享性正是通过进程的活动体现出来的;进程的两个基本属性:

1.是一个可拥有资源的独立单位。

2.是一个可独立调度和分派的基本单位。

这也是并发执行的基础。

特性:

  1. 并发性:与其他进程一起推进。
  2. 动态性:有生命周期,周期内状态不断变化。
  3. 独立性:是一个相对完整的资源分配单位。
  4. 交互性:可能会与其他进程发生直接或间接的作用。
  5. 异步性:各自独立,以不可预知的速度推进。
  6. 结构性:有程序、数据和进程控制块组成。
2.进程控制块(PCB)

在OS内核中定义了一个专门的数据结构,称为进程控制块(PCB)。

PCB是操作系统用于管理进程的重要数据结构,用来描述进程的基本情况及进程的运行变化过程。它包含了进程的各种信息:

  • 进程ID(PID):唯一
  • 进程名
  • 程序计数器
  • CPU寄存器
  • 内存管理信息(如基址寄存器、界限寄存器)
  • I/O状态信息
  • 调度信息(如优先级、调度队列指针)

PCB是进程存在的唯一标志,是进程的灵魂,程序和数据是进程的躯体,当系统创建一个进程时,为进程设置一个PCB,再用它对进程进行控制和管理;在撤销进程时,系统会收回它的PCB,进程也就随之消亡。

PCB的内容可以分为调度信息现场信息两大类。

现场信息:进程的运行情况,由于每个进程都有自己专用的工作存储区,其它进程运行时不会改变它的内容;因此,PCB中的现场信息只记录那些可能会被其他进程改变的寄存器,如程序状态字、时钟、界限寄存器等。一旦中断进程的运行,必须把中断时刻的上述内容记入PCB的现场信息。

进程PCB的所有部分均存放在内存中;PCB的内容和大小随系统不同而异。

PCB的组织方式:

(1)线性方式:将所有PCB不分状态地组织在一个连续表(PCB表)中;简单,适合进程数目不多;缺点是需要扫描整个PCB表,才能找到需要的PCB。

(2)索引方式:对具有相同状态的进程,分别设置各自的PCB索引表,表目为每个PCB在该PCB表中的地址。

(3)链式方式:对具有相同状态的进程,通过PCB中的链接字构成一个队列;链接字指出本队列下一个PCB在PCB表中的编号(或地址);编号为0表示队尾;队首由内存固定单元中相应的队列指针指示。如此,便形成了就绪队列或等待队列(可有多个等待队列,对应不同的原因)。

3.进程调度

进程调度是操作系统选择就绪队列中的一个进程,将其分配到CPU上运行的过程。常见的调度算法包括:

  • 先来先服务(FCFS)
  • 短作业优先(SJF)
  • 轮转法(RR)
  • 优先级调度(Priority Scheduling)
进程模型
三状态模型

这是最简单的进程状态模型,描述了进程的三个基本状态。

1. 就绪(Ready)

  • 进程已经分配了所需的资源,并等待CPU的调度。
  • 已具备运行条件,但由于没有获得CPU而不能运行时所处的状态。
  • 就绪状态的进程保存在就绪队列中。

2. 运行(Running)

  • 进程正在使用CPU执行其指令。
  • 在单处理器系统中,同一时刻只能有一个进程处于运行状态。

3. 阻塞/等待(Blocked/Waiting)

  • 也叫等待或者封锁状态。
  • 进程等待某个事件的发生(如I/O操作完成),暂时无法执行。
  • 进程在等待状态时,不占用CPU资源。
  • 当多个进程竞争同一资源时,没有占用资源的进程就处于阻塞状态。

【操作系统】第三章 进程/线程模型_实现



五状态模型

在三状态的基础上增加创建和退出两种状态。

【操作系统】第三章 进程/线程模型_控制_02

OS中多个进程的并发执行是通过进程交替进入运行状态来实现的。

交替的策略:

  • 调度+超时
  • 调度+等待事件+事件出现



七状态模型

OS引入虚拟存储技术后,由于进程优先级,一些低优先级的进程可能等待较长时间,为了保证内存充足,从而将它们从内存交换至外存的特定区域(交换分区);好处:

1)提高处理机效率

2)为运行进程提供足够的内存

3)有利于调试

将五状态模型的就绪和阻塞状态进行细分,增加"就绪挂起"和"阻塞挂起;且原本的就绪和挂起状态的含义也有变化。

【操作系统】第三章 进程/线程模型_实现_03

1)就绪:进程在内存且可立即进入运行状态。

2)就绪挂起:进程在外存,需要进入内存即可进入运行状态。

3)阻塞:进程在内存并等待某事件出现。

4)阻塞挂起:进程在外存并等待某事件出现。

挂起(Suspend):把一个进程从内存转到外存。

激活(Activate):把一个进程从外存转到内存。

进程队列

通常系统中的进程队列分为三类:

1.就绪队列

2.等待队列

3.运行队列:在单处理器中只有一个运行队列且其中只有一个进程。

进程的队列有PCB的链接组成;方式有单向链接双向链接


第二节 进程控制

一 进程控制的基本概念

进程生命周期中各种状态之间的转换进行有效控制;通过进程控制原语实现。

进程控制原语是由若干条指令组成的一个不可分割的指令序列;用来实现某个特定的操作功能,是OS内核的一个组成部分;必须在管态下执行且常驻内存。

原语和系统调用均可被进程调用;区别在于原语有不可中断性,这是通过在其执行过程中关闭中断实现的,且原语往往是被系统进程调用的

许多系统调用的功能都可用目态下运行的系统过程完成。

用于进程控制原语:

1.创建原语

一个进程通过它创建一个新进程(主要任务是建立PCB),前者称为父进程,后者称为子进程。

过程:先申请一个空闲的PCB区域,然后将有关信息写入PCB;置该进程为就绪状态,最后将他插入就绪队列中即可。

2.撤销原语

进程完成任务后就应该撤销它,释放资源;实质就是撤销PCB。

过程:找到要撤的PCB,将进程从所在队列中消去;撤销所有子孙进程,释放资源并撤销他们的PCB。

3.阻塞原语

若进程执行中需要执行I/O操作,则该进程调用阻塞原语将其从运行状态转换为阻塞状态。

过程:首先中断CPU的执行,将CPU的当前状态保存在PCB现场信息中;然后将进程状态置为阻塞,将其插入该事件的等待/阻塞队列。

4.唤醒原语

进程因等待某事件发生而转入阻塞;当该事件发生后,唤醒原语将其转为就绪状态。

过程:在等待/阻塞队列中找到进程,将该进程状态置为就绪;然后将其从等待/阻塞队列中撤出并插入到就绪队列,等待系统调度。

第三节 线程的引入及基本概念

一 线程的引入

进程是一个资源的拥有者,在进程的创建、撤销和切换中,系统必须为之付出事件和空间方面的资源开销。所以系统中所设置的进程数量不宜过多;进程切换的频率也不宜过高。

这些限制了并发度,如何使多个程序更好地并发且减少系统开销?

进程是调度的基本单位,而且还是独立分配资源的单位;若把这两个基本单位的功能分开就产生了线程的概念。

1.什么是线程

在引入线程的操作系统中,线程是进程的一个实体,是CPU调度和分派的基本单位。线程自己基本上不拥有系统资源,只拥有少量在运行中必不可少的资源(如程序计数器、一组寄存器和栈等),但它可与同属同一进程的其他线程共享进程拥有的全部资源。

线程是进程中的一个执行路径。每个线程都有自己的程序计数器、寄存器集合和栈,但它们共享同一个进程的代码段、数据段和其他资源。

2.线程的属性

(1)每个线程有唯一标识符和一张线程描述表(记录线程执行的寄存器以及栈等现场信息)。

(2)不同线程可以执行相同的程序(如同一服务被不同用户调用时);OS可以为它们创建不同的线程。

(3)同一进程中各线程共享该进程的内存。

(4)线程是CPU的独立调度单位,多个线程可并发;在单处理器的计算机系统中,各线程交替占用CPU;多CPU时各线程可同时占用不同的CPU。

(5)线程在创建后即开始了它的生命周期(就绪、运行、阻塞)。

3. 线程的优点(引入线程的好处)
  • 并发执行:在多处理器系统中,线程可以并发执行,提高系统性能。
  • 资源共享:线程共享进程的资源(如内存),上下文切换开销小。
  • 响应性提高:多线程使得应用程序可以同时执行多个任务,提高响应性。
  • 创建线程花费时间少,无需再划分额外资源;速度快、开销少。
  • 线程切换花费时间少。
  • 同一进程内的线程共享内存,因此,线程间通讯无需调用内核也无需额外的机制。
  • 线程能独立执行并发挥并行能力。
4. 线程的实现方式
  • 用户级线程(User-Level Threads):线程管理在用户空间实现,调度由应用程序控制。优点是创建和切换开销小,缺点是缺乏内核支持,无法充分利用多处理器。
  • 内核级线程(Kernel-Level Threads):线程管理由操作系统内核完成,内核负责调度。优点是可以利用多处理器并行执行,缺点是创建和切换开销大。
  • 混合实现(Hybrid Approach):结合用户级线程和内核级线程的优点,通过多对多或两级模型实现。
5. 线程库

常见的线程库包括:

  • POSIX线程(Pthreads):一种标准的线程API,广泛应用于Unix和Linux系统。
  • Windows线程:Windows提供的线程API,如CreateThreadExitThread

二 线程的组成

每个线程都有一个Thread结构,即线程控制块;用于保存自己的私有信息;主要有:

(1)唯一的线程标识符

(2)描述处理器工作的一组寄存器(如:程序计数器、状态寄存器、通用寄存器等)的内容。

(3)每个Thread都有两个栈指针:一个指向内核栈(内核态运行时),另一个指向用户栈(用户态运行时)。

(4)一个私有存储区:存储现场保护信息和其他有关该线程的统计信息等。

【操作系统】第三章 进程/线程模型_实现_04

线程必须在某个进程内执行。

【操作系统】第三章 进程/线程模型_控制_05


【操作系统】第三章 进程/线程模型_实现_06

三 线程与进程的关系

线程又叫轻量级进程或者进程元。

进程与线程的对比
  • 内存使用:进程有独立的地址空间,线程共享同一地址空间。
  • 系统开销:线程调度的开销远小于进程调度的开销。进程创建开销大(分配独立资源),线程创建开销小(共享资源)。
  • 通信开销:进程间通信(IPC)开销大,线程间通信开销小(共享内存)。
  • 调度和切换:线程是CPU调度的基本单位,进程是资源分配的基本单位。进程切换开销大(切换独立资源),线程切换开销小(共享资源)。
  • 并发性:引入线程后OS的并发性更强。

在创建和撤销进程时,系统都要为进程分配或回收资源,如内存、I/O设备等。

切换进程时涉及整个CPU环境的保存,而线程只涉及少量寄存器的内容。

进程的应用场景
  • 独立任务:需要独立的资源和独立运行的任务,如不同的应用程序。
  • 安全隔离:需要进程间完全隔离,以确保安全和稳定性。
线程的应用场景
  • 并行计算:需要同时执行多个计算任务的场景,如多线程服务器处理多个客户端请求。
  • 高响应性:需要快速响应用户交互的场景,如图形用户界面应用程序。

第四节 线程的实现与实例

线程的实现方式有多种,主要分为用户级线程内核级线程。此外,还有一种混合级实现,结合了两者的优点。

一、线程的实现方式

1. 用户级线程(User-Level Threads)

用户级线程由用户空间的线程库管理,操作系统内核对用户级线程的调度不可见。所有的线程管理操作(如创建、调度、同步)都在用户空间完成。

优点

  • 线程操作开销低,不需要内核的参与
  • 上下文切换速度快,因为不涉及系统调用。

缺点

  • 一个线程的阻塞会导致整个进程阻塞,因为内核不感知用户级线程的存在。
  • 无法利用多处理器并行计算能力,因为内核只看到单个进程。
2. 内核级线程(Kernel-Level Threads)

内核级线程由操作系统内核管理,内核负责所有线程的创建、调度和管理。每个内核级线程都有独立的调度实体,可以并行执行。

优点

  • 内核感知线程的存在,可以充分利用多处理器并行计算能力。
  • 一个线程的阻塞不会影响其他线程,内核可以调度其他线程运行

缺点

  • 线程操作开销大,涉及系统调用和内核态的切换
  • 上下文切换速度慢于用户级线程。
3. 混合实现(Hybrid Approach)

混合实现结合了用户级线程和内核级线程的优点,通过多对多或两级模型实现。用户级线程库将用户线程映射到多个内核级线程上,实现高效的并发执行。

优点

  • 兼具用户级线程的低开销和内核级线程的并行计算能力。
  • 提供更高的灵活性和扩展性。

缺点

  • 实现复杂度高。

对于通常的进程,无论是用户进程还是系统进程,在切换时都依赖于内核调度;即无论什么进程都与内核有关

二、Pthreads线程库

多线程应用程序需要用一组用户级程序库来编写,最著名的是Pthreads(POSIX threads)库。

可移植操作系统接口POSIX(Protable Operating System Interface)是IEEE 1003.1c定义的线程标准。

创建新线程:pthread_create

终止进程:pthread_exit

等待其他进程终止:pthread_join

三、协程

当线程的数量非常大时,系统线程会占用非常多的内存空间,过多的线程切换会占用大量系统时间。为了解决这两个问题,许多编程语言提出协程机制。

携程时运行在线程之上的轻量级线程。

附录A 栈

一、操作系统中的栈

在操作系统和计算机体系结构中,栈(Stack)是一个用于临时存储数据的内存区域,遵循后进先出(LIFO, Last In First Out)原则。栈在程序执行过程中起到非常重要的作用,尤其是在函数调用、局部变量存储和控制流管理等方面。

二、栈的创建

栈是在程序运行时由操作系统和编译器共同管理的。具体创建过程如下:

  1. 进程创建时
  • 当操作系统创建一个新进程时,会为该进程分配栈内存。这通常在进程的地址空间中预留一块内存区域。
  • 对于主线程,栈在进程创建时分配。
  • 对于新创建的线程,其栈在线程创建时分配。
  1. 线程创建时
  • 每个线程都有自己的栈,所以在线程创建时,操作系统会为该线程分配独立的栈空间。

三、栈的物理存储位置

栈保存在计算机的主存(RAM)中。具体来说,进程的地址空间通常划分为不同的段(如代码段、数据段、堆、栈等),栈段是其中之一。

四、栈的大小

栈的大小取决于操作系统和编译器的设置,可以通过编译器选项或操作系统配置进行调整:

  1. 操作系统默认设置
  • 不同的操作系统和体系结构对栈大小有不同的默认限制。例如,在Linux系统中,默认栈大小通常为8MB,但可以通过ulimit命令修改。
  1. 编译器选项
  • 在编译时可以通过特定编译器选项(如GCC的-Wl,-stack_size)设置栈大小。
  1. 程序显式设置
  • 在线程编程中,使用线程库(如Pthreads)的接口可以显式设置线程栈的大小。

五、栈的使用及限制

1. 使用场景

  • 函数调用:每次函数调用时,都会在栈上分配一个栈帧,用于存储函数的局部变量、参数和返回地址。
  • 递归调用:递归函数调用时,每一层调用都会在栈上分配一个新的栈帧。
  • 中断处理:在处理硬件中断时,系统会将当前CPU寄存器状态保存到栈中。

2. 限制和注意事项

  • 栈溢出(Stack Overflow)
  • 当栈使用超过分配的栈空间时,会发生栈溢出错误。常见原因包括无限递归调用或分配过多的局部变量。
  • 栈溢出通常会导致程序崩溃或不确定的行为。
  • 栈大小有限
  • 栈空间是有限的,尤其是在嵌入式系统或某些资源受限的环境中,这个限制更加明显。
  • 不可显式释放
  • 栈上的内存是由编译器和操作系统自动管理的,程序员无法显式地释放栈内存。
  • 线程独立栈
  • 多线程程序中,每个线程有自己的栈,线程之间的栈是独立的,不共享。

六、栈与堆的对比

  • 栈(Stack)
  • 存储内容:函数调用信息、局部变量、返回地址等。
  • 管理方式:由操作系统和编译器自动管理,遵循LIFO原则。
  • 分配速度:分配和释放速度快。
  • 大小:固定或有限大小,由操作系统或编译器设定。
  • 生命周期:随着函数调用和返回自动管理。
  • 堆(Heap)
  • 存储内容:动态分配的内存,如通过mallocnew分配的内存。
  • 管理方式:由程序员显式管理,需要手动分配和释放。
  • 分配速度:相对较慢,因为需要搜索空闲块。
  • 大小:相对较大,但受限于系统可用内存。
  • 生命周期:由程序员控制,可以跨函数调用存在。

栈是操作系统和编译器管理的一个重要内存区域,主要用于存储函数调用信息和局部变量。它在程序运行时自动创建,并保存在计算机的主存(RAM)中。栈的大小有限,超出分配空间会导致栈溢出错误。理解栈的结构和使用,对于编写高效、安全的程序具有重要意义。

附录B 进程线程信息的保存

因为进程并发执行共享CPU资源,通过时间片轮转进程调度;当时间片用完就需要保存现场并调入新的进程,那么,现场数据保存在哪个硬件部件上呢?那这种部件的容量和数量与词条这中进程数量的关系又是什么呢?

进程的现场数据保存在寄存器和主存中。现场数据的容量与进程数量的关系取决于可用内存的大小。为了提高系统的性能和资源利用率,操作系统通常会采用虚拟内存和多级反馈队列调度等策略来管理进程的现场数据。

进程现场数据的存储与容量关系

在时间片轮转进程调度算法中,当一个进程的时间片用完时,其现场数据会被保存到寄存器主存中。

  • 寄存器: 寄存器是CPU内部的快速存储器,用于保存当前正在执行的进程的关键信息,例如程序计数器 (PC)、指令寄存器 (IR)、通用寄存器等。这些信息对于恢复进程的执行状态至关重要。
  • 主存: 主存是计算机系统的主要存储器,用于存储程序代码、数据和进程的大部分现场数据,例如进程的堆栈、环境变量、全局变量等。

现场数据的容量与进程数量的关系

现场数据的大小取决于进程的复杂程度和所使用的编程语言。一般来说,进程的现场数据越大,其运行所需的资源就越多。因此,在系统中同时运行的进程数量受到可用内存的限制。

具体来说,可用内存的大小决定了可以同时存储多少个进程的现场数据。如果可用内存不足以容纳所有进程的现场数据,则操作系统需要将一些进程的现场数据换出外存中,例如硬盘。这会导致进程的上下文切换开销增加,降低系统的性能。

为了提高系统的性能和资源利用率,操作系统通常会采用以下策略来管理进程的现场数据:

  • 虚拟内存: 虚拟内存技术允许将进程的现场数据的一部分换出到外存中,同时在主存中保留必要的关键信息。这可以提高系统的多进程处理能力。
  • 多级反馈队列调度: 多级反馈队列调度算法根据进程的运行特点来调整调度算法,可以提高进程的平均响应时间和周转率。

寄存器是CPU内部的快速存储器,用于保存当前正在执行的进程的关键信息。 既然这样,假如这些寄存器已经保存了上一个进程的线程信息,那么接下来的进程现场数据需要保存会覆盖上一个进程的现场吗?

不会,下一个进程的现场数据不会覆盖上一个进程的现场数据。这是因为:

  1. 每个进程都有自己的寄存器组: CPU内部有多个寄存器,每个进程都有自己的专用寄存器组。当一个进程切换到运行状态时,CPU会加载该进程的专用寄存器组,并保存上一个进程的寄存器组。因此,下一个进程的现场数据不会覆盖上一个进程的现场数据。
  2. 进程切换时会保存现场数据: 当一个进程的时间片用完或发生其他事件导致其需要切换到其他状态时,操作系统会保存该进程的现场数据。现场数据包括寄存器值、堆栈指针、程序计数器等信息。这些信息会被保存到主存中,以便在该进程下次运行时恢复其执行状态。
  3. 主存空间隔离: 主存被划分为多个区域,每个进程都有自己的地址空间。当进程切换时,操作系统会切换到该进程的地址空间,并加载该进程的现场数据。因此,下一个进程的现场数据不会覆盖上一个进程的现场数据。

总而言之,寄存器和主存中的地址空间隔离机制确保了每个进程都能拥有自己的独立现场数据,不会相互覆盖。

寄存器是高速缓存,用于存储当前正在执行的进程的关键信息。主存是主要存储器,用于存储所有进程的现场数据以及程序代码和数据。

进程切换时,操作系统需要保存上一个进程的现场数据,并加载下一个进程的现场数据。这会导致一定的开销,但这是为了确保每个进程都能独立运行所必需的。

现代操作系统通常使用虚拟内存技术来管理进程的现场数据。虚拟内存允许将部分进程的现场数据换出到外存 (例如硬盘) 中,从而提高系统的多进程处理能力。

CPU中寄存器数量有多少?如果CPU中寄存器这种数量有上限,是不是代表能保存现场的进程个数是有限制的?

CPU中寄存器数量是有限的,但它并不直接限制能保存现场的进程个数。这是因为操作系统使用多级存储机制和虚拟内存技术来管理进程的现场数据,使得可以同时运行的进程数量远远超过CPU中寄存器数量的限制。

CPU中寄存器数量是有限的,但它并不直接限制能保存现场的进程个数。这是因为:

1. 寄存器用于保存关键信息

CPU中的寄存器主要用于保存当前正在执行的进程的关键信息,例如:

  • 程序计数器 (PC):指示CPU要执行的下一条指令的地址
  • 指令寄存器 (IR):保存当前正在执行的指令
  • 通用寄存器:用于存储操作数和临时结果

这些信息对于恢复进程的执行状态至关重要。

2. 进程现场数据保存在多处

进程的现场数据不仅保存在寄存器中,还保存在主存中。主存是计算机系统的主要存储器,用于存储程序代码、数据和进程的大部分现场数据,例如进程的堆栈、环境变量、全局变量等。

3. 进程切换时会保存现场数据

当一个进程的时间片用完或发生其他事件导致其需要切换到其他状态时,操作系统会保存该进程的现场数据。现场数据包括寄存器值、堆栈指针、程序计数器等信息。这些信息会被保存到主存中,以便在该进程下次运行时恢复其执行状态。

4. 多级存储机制

现代操作系统通常使用多级存储机制来管理进程的现场数据。多级存储机制包括:

  • 寄存器: 存储关键信息,访问速度最快
  • 高速缓存: 存储部分经常访问的数据,访问速度比主存快
  • 主存: 存储所有进程的现场数据以及程序代码和数据,访问速度较慢
  • 外存 (例如硬盘): 存储部分不经常访问的数据,访问速度最慢

多级存储机制可以有效利用内存空间,提高系统的性能。

5. 虚拟内存技术

虚拟内存技术允许将部分进程的现场数据换出到外存中,从而提高系统的多进程处理能力。虚拟内存技术使得可以同时运行的进程数量远远超过CPU中寄存器数量的限制。