进程控制块(Process Control Block, PCB):
定义:
操作系统管理控制进程运行所用的信息集合。
操作系统用PCB来描述进程的基本情况以及运行变化的过程
PCB是进程存在的唯一标志,每个进程都在操作系统中有一个对应的PCB
内容:
- 进程标识信息
PID:进程号
UID等 - 处理机现场保存
堆栈指针:记录堆栈现场
指令指针:记录进程运行到哪 - 进程控制信息
调度和状态信息:进程和处理机使用情况调度
进程间通信信息:进程间通信相关的各种标识
存储管理信息:指向进程映像存储空间数据结构
进程所用资源:进程使用的系统资源,如打开文件等
有关数据结构连接信息:通过与PCB相关的进程队列实现
组织: - 链表
同一状态的进程的PCB组织成一链表,多个状态对应多个不同的链表。比如就绪链表、阻塞链表 - 索引表
同一状态的进程归入一个索引表(由索引表指向PCB),多个状态对应多个不同的索引表。
进程切换:
三状态进程模型:
创建与退出只是两个短暂的状态,除此之外简化为三个状态:就绪、运行、等待。装填变迁如下图。
挂起进程模型:
处在挂起状态的进程映像在磁盘上,目的是减少进程占用内存。也就是说挂起就是把一个处于就绪或者等待状态的进程从内存转存到外存,通常选择低优先级进程进行挂起。
由操作系统来维护一组队列,表示系统中所有进程的当前状态。不同队列表示不同状态。根据进程状态不同,进程PCB进入对应的队列。进程状态的变迁变成队列之间进程的搬迁
线程=进程-共享资源
定义:
线程是进程的一部分,描述指令流执行状态,它是进程的指令执行流的最小单元,是CPU调度的基本单位。
进程的资源分配角色:进程由一组相关资源构成,包括地址空间(代码段、数据段)、打开的文件等各种资源。
线程的处理机调度角色:线程描述在进程资源环境中的指令流的执行状态。每个线程有自己独立的指令指针等。
线程和进程的关系
进程时资源分配单位,线程是cpu调度单位
线程三种实现方式:
用户线程:
由一组用户级的线程库函数来完成线程的管理。
优点:
不依赖于操作系统内核,可以用于不支持线程的多进程操作系统
在用户空间实现的线程机制
同一进程内的用户线程切换速度快,无需在用户态与核心态之间切换
允许每个进程拥有自己的线程调度算法
缺点:
线程发起系统调用而阻塞时,整个进程进入等待
不支持基于线程的处理机抢占,除非当前运行线程主动放弃,它所在的线程的其他线程无法抢占CPU
只能按进程分配CPU时间,多个线程进程中,每个线程的时间片较少。
内核线程:
由内核通过系统调用实现的线程机制,由内核完成线程的创建、终止和管理。如Windows、Linux等
特点:
由内核维护PCB和TCB
线程执行系统调用而被阻塞不影响其他线程
线程的创建、终止和切换开销相对较大,因为通过系统调用/内核函数,在内核空间实现
以线程为单位进行CPU时间分配,多线程的进程可以获得更多CPU时间。
轻量级进程:
内核支持的用户线程,一个进程可以有一个或多个轻量级进程,每个轻权进程由一个单独的内核线程来支持。
用户线程与内核线程的对应关系:
可以有如下图多种方式,最后证明一对一是最优的。
进程控制
进程切换(也叫上下文切换):
定义:
暂停当前运行进程,从运行状态变成其他状态
调度另一个进程从就绪状态变成运行状态
要求:
切换前,保存进程上下文
切换后,恢复进程上下文
快速切换
进程存储的生命周期的信息:
寄存器(PC,SP,…)
CPU状态
内存地址空间
进程控制块PCB:内核的进程状态记录
内核为每个进程维护了对应的进程控制块PCB
内核将相同状态的进程的PCB放置在同一队列
进程的切换流程
进程切换的函数调用关系图
进程创建:
Windows进程创建API:CreateProcess
Unix进程创建系统调用:fork/exec。fork()把一个进程复制成两个进程,父子进程有各自的PID;接下来的exec()用新程序来重写当前进程,PID不改变。
创建进程
fork的地址空间复制
exec进行程序加载和执行
空闲进程创建:
当用户代码执行完之后,系统将在空闲进程中执行。
第一个内核线程的创建:
fork()的开销:(fork和exec都是系统调用)
对子进程分配内存
复制父进程的内存和CPU寄存器到子进程里
开销昂贵
在99%的情况里,调用fork()之后调用exec(),会将刚复制的地址空间给覆盖掉,考虑到fork()操作中内存复制是没有作用的,因此考虑将其合并到一个调用中,于是出现了vfork()。
vfork():
创建进程时,不再创建一个同样的内存映像,而是当调用exec()时才开始复制,而要被覆盖或者关闭的资源不再进行复制
一些时候称为轻量级fork()
子进程应该几乎立刻调用exec()
现在都使用写时复制Copy on Write(COW)技术
Copy on Write(COW):写时复制,进程创建后,等到要用的时候才延迟过来进行复制,如果要直接覆盖新地址,那么这个复制就不进行了
进程加载
用exec()这个系统调用就可以加载一个可执行文件到内存当中覆盖原来进程的地址空间,然后开始执行
进程等待与退出:
wait()系统调用用于父进程等待子进程的结束
子进程结束时通过exit()向父进程返回一个值
父进程通过wait()接受并处理返回值
wait系统调用的功能
wait在前,exit在后:有子进程存活时,父进程进入等待状态,等待子进程的返回结果。当某子进程调用exit()时,唤醒父进程,将exit()返回值作为父进程中wait的返回值
exit在前,wait在后:有子进程调用exit()而父进程还未调用wait()处理时,子进程被称为僵尸进程。如果此时父进程调用wait()则直接处理并返回——僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出 ,子进程就变成了孤儿进程,就会被init接管,子进程退出后init会回收其占用的相关资源
无子进程或无子进程存活时,wait()也是立刻返回
进程的有序终止exit()
进程结束执行时调用exit(),完成进程资源回收。
exit()系统调用的功能
将调用参数作为进程的结果返回
关闭所有打开的文件等占用资源
释放内存
释放大部分进程相关的内核数据结构
检查是否父进程是存活的:如果父进程存活,保留结果的值直到父进程用wait()处理,进入僵尸状态;如果父进程不存活,则子进程为孤儿进程,直接释放所有数据结果,进程结束——父进程调用wait时检查子进程状态,子进程调用exit时检查父进程状态
清理所有等待的僵尸进程
进程终止是最终的垃圾回收