进程:运行中的程序
一次执行一个程序效率慢,比如有一个会读取硬盘文件数据的程序被执行了,那么当运行到读取文件的指令时,就会去从硬盘读取数据,但是硬盘的读写速度是非常慢的,这时如果 CPU 等着硬盘返回数据的话, CPU 的利用率非常低。
故引入多个程序、交替执行的思想,CPU 会从一个进程快速切换至另一个进程,其间每个进程各运行几十或几百个毫秒。这会产生并行的错觉,其实这是并发。
并发是一个处理器交替执行多个任务。
并行是两个处理器分别执行两个任务。
进程的状态
- 运行状态(Runing):该时刻进程占用 CPU;
- 就绪状态(Ready):可运行,但因为其他进程正在运行而暂停停止;
- 阻塞状态(Blocked):该进程正在等待某一事件发生(如等待输入/输出操作的完成)而暂时停止运行,这时,即使给它CPU控制权,它也无法运行;
- 创建状态(new):进程正在被创建时的状态;
- 结束状态(Exit):进程正在从系统中消失时的状态
上面的状态都好理解,而挂起状态是什么?为什么会有挂起状态?
假设有大量处于阻塞状态的进程,进程可能会占用大量物理内存空间,被阻塞状态的进程占用着物理内存就一种浪费物理内存的行为。故在虚拟内存管理的操作系统中,通常会把阻塞状态的进程的物理内存空间换出到硬盘,等需要再次运行的时候,再从硬盘换入到物理内存。故这种描述进程没有占用实际的物理内存空间的情况,这个状态就是挂起状态
挂起状态可以分为两种:
- 阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现;
- 就绪挂起状态:进程在外存(硬盘),但只要进入内存,即刻立刻运行
进程的控制结构
操作系统用进程控制块(process control block,PCB)数据结构来描述进程。
PCB 包含的主要信息如下:
- 进程标识符(Process ID, PID):用于唯一标识一个进程。
- 进程状态(Process State):表示当前进程的状态,例如就绪、运行、等待、挂起等。
- 程序计数器(Program Counter, PC):用于记录程序当前执行的位置,即下一条指令的地址。
- CPU 寄存器(CPU Register):包含了 CPU 中各个寄存器的值,包括通用寄存器、程序计数器和栈指针等。
- 进程优先级(Process Priority):用于记录进程的优先级,以便操作系统进行进程调度。
- 进程拥有者(Process Owner):表示进程的拥有者,通常是创建该进程的用户。
- 进程所属组(Process Group):表示进程所属的组,通常是创建该进程的用户所在的组。
- 内存管理信息(Memory Management Information):用于记录进程占用的内存地址空间信息,包括进程的代码段、数据段、堆栈等。
- 文件描述符(File Descriptor):用于记录进程打开的文件,包括文件的类型、位置和权限等信息。
- 进程间通信信息(Interprocess Communication, IPC):用于记录进程与其他进程进行通信的方式,例如共享内存、消息队列、管道等。
相同状态的进程通常是以链表方式组成各种队列。因为可能会面临进程创建,销毁等调度导致进程状态发生变化,链表能够更加灵活的插入和删除。
进程的控制
进程的创建
分配进程唯一标识符;申请一个空白的 PCB;为进程分配资源;初始化 PCB;将进程插入到就绪队列,等待被调度运行。
其中可能会遇到申请PCB失败、分配资源失败等问题。
进程的终止
查找要终止 进程的PCB;若该进程处于执行状态则终止进程执行并将CPU资源分配给其他进程;终止其所有子进程;归还所有资源给父进程或操作系统;将PCB从所在队列删除。
进程的阻塞
找到PCB;若该进程为运行状态则保护其现场,将其状态转为阻塞状态,停止运行;将该 PCB 插入的阻塞队列中去
进程的唤醒
找到PCB;将该进程从阻塞队列中移去并将状态转为就绪状态 ;将PCB插入到就绪队列
进程的上下文切换
各个进程之间共享 CPU 资源,在不同的时候进程之间需要切换,一个进程切换到另一个进程运行,称为进程的上下文切换。
CPU 上下文:CPU在执行程序时,保存当前状态信息的一组寄存器值和其他相关信息的集合。它是实现任务切换和中断处理的关键机制之一。CPU上下文信息包括:
- 程序计数器的值,用于存储下一条指令的地址。
- 栈指针的位置,用于指向堆栈中的当前位置。
- 寄存器的值,例如累加器和指针寄存器等。
- 状态寄存器的值,用于表示CPU的状态信息,例如运算结果是否为零,是否有进位等。
- 其他处理器特定的状态信息,例如标志寄存器等。
CPU 上下文切换就是先把前一个任务的 CPU 上下文保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
由于进程是由内核管理和调度的,所以进程的切换只能发生在内核态。进程的上下文切换不仅包含了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的资源。
进程上下文切换过程:
- 操作系统决定要切换到哪个进程。操作系统使用进程调度算法来选择下一个要执行的进程。
- 保存当前进程的上下文信息。进程切换前需要将当前进程的CPU上下文信息保存到内存中。
- 加载下一个进程的上下文信息。在进行进程切换时,需要从内存中加载下一个进程的CPU上下文信息,以恢复该进程的执行状态。
- 更新进程控制块(PCB)。更新当前进程的状态,同时将下一个进程的PCB加载到内存中。
- 恢复下一个进程的执行。在完成上述步骤后,操作系统将CPU控制权转移到下一个进程,从下一个进程的程序计数器处开始执行。
- 进程执行。当下一个进程被执行时,操作系统会根据进程调度算法的规则在各个进程之间进行切换。上述步骤将重复执行,直到所有进程都执行完毕。
通常,会把交换的信息保存在进程的 PCB,当要运行另外一个进程的时候,我们需要从这个进程的 PCB 取出上下文,然后恢复到 CPU 中,这使得这个进程可以继续执行。
线程
线程是进程当中的一条执行流程。
线程与进程最大的区别在于:线程是调度的基本单位,而进程则是资源拥有的基本单位。
为什么引入线程:
由于进程是资源的拥有者,所以在创建、撤销、切换操作中需要较大的时空开销,限制了并发程度的进一步提高。为减少进程切换的开销,把进程作为资源分配单位和调度单位这两个属性分开处理,即进程还是作为资源分配的基本单位,但是不作为调度的基本单位(很少调度或切换),把调度执行与切换的责任交给“线程”。这样做的好处不但可以提高系统的并发度,还能适应新的对称多处理机(SMP)环境的运行,充分发挥其性能。
每个线程都有独立一套的寄存器和栈,这样可以确保线程的控制流是相对独立的。
线程与进程的比较:
- 进程是资源(包括内存、打开的文件等)分配的单位,线程是 CPU 调度的单位;
- 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈;
- 线程同样具有就绪、阻塞、执行三种基本状态,同样具有状态之间的转换关系;
- 线程能减少并发执行的时间和空间开销;
线程相比进程能减少开销,体现在:
线程的创建时间比进程快,因为进程在创建的过程中,还需要资源管理信息,比如内存管理信息、文件管理信息,而线程在创建的过程中,不会涉及这些资源管理信息,而是共享它们;
线程的终止时间比进程快,因为线程释放的资源相比进程少很多;
同一个进程内的线程切换比进程切换快,因为线程具有相同的地址空间(虚拟内存共享),这意味着同一个进程的线程都具有同一个页表,那么在切换的时候不需要切换页表。而对于进程之间的切换,切换的时候要把页表给切换掉,而页表的切换过程开销是比较大的;
由于同一进程的各线程间共享内存和文件资源,那么在线程之间数据传递的时候,就不需要经过内核了,这就使得线程之间的数据交互效率更高了;
所以,线程比进程不管是时间效率,还是空间效率都要高。
线程的实现
图1. 用户级线程和内核级线程
用户级线程(User-Level Thread, ULT):
多对一模型。将多个用户级线程映射到一个内核级线程。所有工作都由应用程序在用户空间中完成,内核意识不到线程的存在。应用程序可以通过使用线程库设计多线程程序。如上图(a)。对于设置了用户级线程的系统,其调度仍以进程为单位进行。
用户级线程的优点:
1. 线程切换不需要切换到内核空间,而是由线程库函数来完成,节省了模式切换的开销。
2. 调度算法是进程专用的,不同的进程可根据需要对自己的线程选择不同的调度算法。
3. 用户级线程的实现与操作系统平台无关,对线程管理的代码是属于用户程序的一部分。
用户级线程缺点:
1. 系统调用的阻塞问题。当线程执行一个系统调用时,不仅该线程被阻塞,进程内所有线程都被阻塞。
2. 不能发挥多处理机优势。内核每次分配给一个进程的仅有一个CPU,因为进程中仅有一个线程能执行。
内核级线程(Kernel-Level Thread, KLT):
一对一模型。将每个用户级线程映射到一个内核级线程。内核级线程管理的所有工作在内核空间内实现。内核空间为每个内核级线程设置一个线程控制块,内核根据该控制块感知某线程的存在并加以控制。如图1(b)。
内核级线程优点:
1. 能发挥多处理机优势。内核能同时调度同一进程中的多个线程并行执行。
2. 如果进程中的一个线程被阻塞,内核可以调度该进程中的其他线程占用处理机,也可运行其他进程中的线程。
3. 内核支持线程具有很小的数据结构和堆栈,线程切换比较快,开销小。
4. 内核本身也可采用多线程技术,提高系统的执行速度和效率。
内核级线程缺点:
同一进程中的线程切换,需要从用户态转到核心态进行,系统开销大。这是因为用户进程的线程在用户态运行,而线程调度和管理是在内核实现。
组合模式
如图1(c)。综合了前两种优点,大部分的线程上下文发生在用户空间,且多个线程又可以充分利用多核 CPU 的资源。