四、进程管理
小林coding 《图解系统:进程管理》笔记
参考:
张杰:上下文切换开销
Chris Terman:L17: Virtualizing the Processor
进程
当进程要从硬盘读取数据时,CPU 不需要阻塞等待数据的返回,而是去执行另外的进程。当硬盘数据返回时,CPU 会收到个中断,于是 CPU 再继续运行这个进程。
虽然单核的 CPU 同时只能运行一个进程。但在 1 秒钟期间,它可能会交替运行多个进程,这样就产生并行的错觉,实际上这是并发。
进程状态的变迁如下图:
- 就绪态 -> 运行状态:处于就绪状态的进程被操作系统的进程调度器选中后,就分配给 CPU 正式运行该进程;
- 运行状态 -> 阻塞状态:当进程请求某个事件且必须等待时,例如请求 I/O 事件;
- 阻塞状态 -> 就绪状态:当进程要等待的事件完成时,由另一个进程唤醒,它从阻塞状态变到就绪状态;
物理内存空间是有限的,被阻塞状态的进程占用着物理内存就一种浪费物理内存的行为。
所以,在虚拟内存管理的操作系统中,通常会把阻塞状态的进程的物理内存空间换出到硬盘,等需要再次运行的时候,再从硬盘换入到物理内存。
所以新加入一个挂起状态:来描述被从物理内存换出到磁盘的进程
另外,挂起状态可以分为两种:
- 阻塞挂起状态:进程在阻塞状态时被换出到硬盘,等待事件唤醒后回到内存,回来后是就绪状态,等待被 CPU 调度
- 就绪挂起状态:进程在就绪状态时被换出到硬盘,等待被调度后回到内存,回来后可以立即运行
进程的控制结构PCB(linux 中的 task_struct)
PCB 是进程存在的唯一标识(linux 中的 pid),这意味着一个进程的存在,必然会有一个 PCB,如果进程消失了,那么 PCB 也会随之消失。
进程描述信息:
- 进程标识符:标识各个进程,每个进程都有一个并且唯一的标识符;
- 用户标识符:进程归属的用户,用户标识符主要为共享和保护服务;
进程控制和管理信息:
- 进程当前状态,如 new、ready、running、waiting 或 blocked 等;
- 进程优先级:进程抢占 CPU 时的优先级;
资源分配清单:
- 有关内存地址空间或虚拟地址空间 (linux 中的 mm_struct 结构体) 的信息,所打开文件的列表和所使用的 I/O 设备信息。
CPU 相关信息:
- CPU 中各个寄存器的值,当进程被切换时,CPU 的状态信息都会被保存在相应的 PCB 中,以便进程重新执行时,能从断点处继续执行。
PCB 通常是通过链表的方式进行组织,把具有相同状态的进程链在一起,组成各种队列。比如:就绪队列,阻塞队列,运行队列
- 另外,对于运行队列在单核 CPU 系统中则只有一个链表了,因为单核 CPU 在某个时间,只能运行一个程序。
除了链接的组织方式,还有索引方式,不同状态对应不同的索引表,索引项指向 PCB。
一般会选择链表,因为可能面临进程创建,销毁等调度导致进程状态发生变化,所以链表能够更加灵活的插入和删除。
进程的控制
创建进程:申请空白PCB;填写控制管理信息如进程唯一标志;将PCB插入就绪队列等待被调度
终止进程:查找要终止的进程的PCB;如果是运行态立即终止执行将CPU资源分配给其它进程;如果还有子进程则把这个孤儿进程交给1号进程接管;将进程拥有的全部资源归还系统;将其PCB在队列删除
阻塞进程:找到要阻塞的进程的PCB;如果是运行态则保护现场将其转为阻塞状态停止运行;将该PCB插到阻塞队列;
唤醒进程:在事件的相应阻塞队列找到PCB;将其在阻塞队列移出并置为就绪状态;将该PCB移到就绪队列;
进程的上下文切换
CPU 上下文
各个进程之间是共享 CPU 资源的,那么这个一个进程切换到另一个进程运行,称为进程的上下文切换。
在操作系统将任务交给CPU运行前,操作系统需要事先帮 CPU 设置好 CPU 寄存器和程序计数器,让 CPU 知道任务从哪里加载,又从哪里开始运行。所以说,CPU 寄存器和程序计数是 CPU 在运行任何任务前,所必须依赖的环境,这些环境就叫做 CPU 上下文。
- CPU 寄存器: CPU 内部一个容量小,但是速度极快的内存(缓存)。
- 程序计数器:用来存储 CPU 正在执行的指令位置、或者即将执行的下一条指令位置。
CPU 上下文切换就是先把前一个任务的 CPU 上下文(CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
CPU 的上下文切换分三种:进程上下文切换、线程上下文切换、中断上下文切换
。
进程上下文切换
进程上下文切换主要涉及到两部分主要过程:
- CPU 上下文切换:由进程 P1 切换到 P2 时,需要把当前 CPU 寄存器和程序计数器保存到 P1 中,然后 CPU 加载 P2 保存的 CPU寄存器和程序计数器
- CPU TLB 页表失效:进程都有自己的虚拟地址空间,把虚拟地址转换为物理地址需要查找页表,一般采用的多级页表查找是一个很慢的过程,因此 CPU 芯片中,加入了一个专门存放程序最常访问的页表项的 Cache,这个 Cache 就是 TLB ,通常称为页表缓存、快表等。由于每个进程都有自己的虚拟地址空间,那么显然每个进程都有自己的页表,那么当进程切换后页表也要进行切换,页表切换后 TLB 就失效了,Cache 失效导致命中率降低,那么虚拟地址转换为物理地址就要重新查询多级页表,表现出来的就是程序运行会变慢,而线程切换则不会导致 TLB 失效,因为线程无需切换地址空间,因此我们通常说线程切换要比较进程切换块,原因就在这里。
- CPU L1~L3 Cache 失效:CPU本身的L1、L2、L3级缓存,本身是为了加速数据读写、减少对内存的访问所引入的,如果任务切换后,任务需要读写的数据在CPU cache中没有找到,就要再去读写内存并同步到cache中去,这个是比较耗时间的。
在 CPU 芯片里面,封装了内存管理单元(Memory Management Unit)芯片,它的作用是讲 虚拟内存地址 转换为 物理内存地址,以及和 TLB 进行交互。
下图是 MMU 转换 虚拟内存地址的过程:
- 首先,我们检查 TLB 中是否缓存了所需的 虚拟内存地址 到 物理内存地址 的映射。
- 如果没有,我们必须访问多级页表以查看该页面是否已分配了物理内存,如果是,则查找其物理页号。
- 如果我们发现该页面未分配物理内存,则会向 CPU 发出缺页中断,以便它可以运行处理程序从物理内存分配该页面
线程
线程与进程最大的区别在于:线程是调度的基本单位,而进程则是资源拥有的基本单位。
线程与进程的其它比较如下:
- 进程拥有一个完整的资源平台,而线程只独享必不可少的资源,如寄存器和栈;
- 线程同样具有就绪、阻塞、执行三种基本状态,同样具有状态之间的转换关系;
- 线程能减少并发执行的时间和空间开销;
对于,线程相比进程能减少开销,体现在:
- 线程的创建时间比进程快,因为进程在创建的过程中,还需要资源管理信息,比如内存管理信息、文件管理信息,而线程在创建的过程中,不会涉及这些资源管理信息,而是共享它们;
- 线程的终止时间比进程快,因为线程释放的资源相比进程少很多;
- 同一个进程内的线程切换比进程切换快,原因在后面。
- 由于同一进程的各线程间共享内存和文件资源,那么在同一进程的线程之间数据传递的时候,就不需要经过内核了,这就使得线程之间的数据交互效率更高了;
所以,不管是时间效率,还是空间效率线程比进程都要高。
线程的上下文切换
这还得看线程是不是属于同一个进程:
- 当两个线程不是属于同一个进程,则切换的过程就跟进程上下文切换一样;
- 当两个线程是属于同一个进程,因为虚拟内存是共享的,所以在切换时,TLB 缓存不会失效,只需要进行 CPU寄存器、程序计数器 的硬件切换。
所以,线程的上下文切换相比进程,开销要小很多。
线程的实现
主要有三种线程的实现方式:
- 用户线程(User Thread):在用户空间实现的线程,不是由内核管理的线程,是由用户态的线程库来完成线程的管理;
- 内核线程(Kernel Thread):在内核中实现的线程,是由内核管理的线程;
- 轻量级进程(LightWeight Process):在内核中来支持用户线程;