进程管理And线程实现
进程的描述
进程的定义
一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程.
进程的组成
包括 :
- 程序的代码
- 程序处理的数据
- 程序计数器中的值, 指示下一条将运行的指令
- 一组通用的寄存器的当前值, 堆, 栈
- 一组系统资源(如打开的文件)
程序和进程的联系:
- 程序是产生进程的基础
- 程序的每次运行构成不同的进程
- 进程是程序功能的体现
- 通过多次执行, 一个程序可以对应多个进程, 通过调用关系, 一个进程可包括多个程序.
进程和程序的区别
- 进程是动态的, 程序是静态的 : 程序是有序代码的集合. 进程是程序的执行, 进程有核心态 / 用户态.
- 进程是暂时的, 程序是永久的. 进程是一个状态变化的过程, 程序可以长久保存.
- 进程和程序的组成不同 : 进程的组成包括程序, 数据和进程控制块(进程状态信息)
进程的特点
动态性 : 可动态地创建, 结果进程;
并发性 : 进程可以被独立调度并占用处理机运行; (并发:一段, 并行:一时刻)
独立性 : 不同进程的工作不相互影响;(页表是保障措施之一)
制约性 : 因访问共享数据, 资源或进程间同步而产生制约.
**问题 **:
如果你要设计一个OS, 怎么样来实现其中的进程管理机制?
进程控制结构
进程也可以说是一个程序 , 我们都知道程序 = 数据结构 + 算法
所以我们进程也会需要这些。
在进程中 , 我们用 进程控制块(Process Control Block)[PCB]来作为进行的数据结构 。
操作系统为每个进程都维护了一个PCB, 用来保存与该进程有关的各种状态信息.
进程控制块 :
进程控制块 : 操作系统管理控制进程运行所用的信息集合.
- 进程的创建 : 为该进程生成一个PCB
- 进程的终止 : 回收它的PCB
- 进程的组织管理 : 通过对PCB的组织管理来实现
(PCB具体包含什么信息? 如何组织的? 进程的状态转换?)
PCB有以下三大类信息 :
- 进程标志信息. 如本进程的标志, 本进程的产生者标志(父进程标志). 用户标志
- **处理机状态信息保存区 **: 保存进程的运行现场信息 :
- 用户可见寄存器. 用户程序可以使用的数据, 地址等寄存器
- 控制和状态寄存器. 如程序计数器(PC), 程序状态字(PSW)
- 栈指针. 过程调用, 系统调用, 中断处理和返回时需要用到它
- 进程控制信息
- 调度和状态信息. 用于操作系统调度进程并占用处理机使用.
- 进程间通信信息. 为支持进程间与通信相关的各种标志, 信号, 信件等, 这些信息都存在接收方的进程控制块中.
- 存储管理信息. 包含有指向本进程映像存储空间的数据结构.
- 进程所用资源. 说明由进程打开, 使用的系统资源. 如打开的文件等.
- 有关数据结构的链接信息. 进程可以连接到一个进程队列中, 或连接到相关的其他进程的PCB.
进程的状态(进程的动态特点)
进程的生命期管理
进程从产生到结束
- 进程创建
- 进程运行
- 进程等待
- 进程唤醒
- 进程结束
进程创建
引起进程创建的3个主要事件 :
- 系统初始化;
- 用户请求创建一个新进程;
- 正在运行的进程执行了创建进程的系统调用.
进程运行
内核选择一个就绪的进程, 让它占用处理机并执行
(为何选择? && 如何选择?)
进程等待
在以下情况下, 进程等待(阻塞):
- 请求并等待系统服务, 无法马上完成
- 启动某种操作, 无法马上完成
- 需要的数据没有到达
进程只能自己阻塞自己, 因为只有进程自身才能知道何时需要等待某种事件的发生.
进程唤醒
唤醒进程的原因 :
- 被阻塞进程需要的资源可被满足
- 被阻塞进程等待的事件到达
- 将该进程的PCB插入到就绪队列
进程只能被别的进程或操作系统唤醒
进程结束
四种情况下, 进程结束 :
- 正常退出(自愿)
- 错误退出(自愿)
- 致命错误(强制性)
- 被其他进程杀死**(强制性)**
进程状态变化模型
进程的三种基本状态 : 进程在生命结束前处于三种基本状态之一.
不同系统设置的进程状态数目不同.
三种基本状态
- 运行状态(Running) : 当一个进程正在处理机上运行时
- 就绪状态(Ready) : 一个进程获得了除处理机之外的一切所需资源, 一旦得到处理机即可运行
- 等待状态(阻塞状态 Blocked) : 一个进程正在等待某一时间而暂停运行时. 如等待某资源, 等待输入/输出完成.
还可以有创建状态 && 结束状态
状态变化图
- NULL → New : 一个新进程被产生出来执行一个程序
- New → Ready: 当进程创建完成并初始化后, 一切就绪准备运行时, 变为就绪状态
- Ready → Running : 处于就绪态的进程被进程调度程序选中后, 就分配到处理机上来运行
- Running → Exit : 当进程表示它已经完成或者因出错, 当前运行进程会由操作系统作结束处理
- Running → Ready : 处于运行状态的进程在其运行过程中, 由于分配它的处理机时间片用完而让出处理机
- Running → Blocked: 当进程请求某样东西且必须等待时
- Blocked → Ready : 当进程要等待某事件到来时, 它从阻塞状态变到就绪状态
进程挂起模型
**挂起 : **意味着进程没有占用内存空间。 处于挂起的进程映像在磁盘上
两种挂起状态 :
- 阻塞挂起状态 : 进程在外存并等待某事件的出现;
- 就绪挂起状态 : 进程在外存, 但只要进入内存, 即可运行.
挂起的状态之间的转换【把一个进程从内存到外存】
- 阻塞到阻塞挂起 : 没有进程处于就绪状态或就绪进程要求更多内存资源时, 会进行这种转换, 以提交新进程或运行时就绪进程.
- **就绪到就绪挂起 : **当有高优先级阻塞(系统认为会很快就绪的)进程和低优先级就绪进程时, 系统会选择挂起低优先级就绪进程.
- **运行到就绪挂起 : **对抢先式分时系统, 当有高优先级阻塞挂起进程因事件出现而进入就绪挂起时, 系统可能会把运行进程转导就绪挂起状态.
在外存时的状态转换 :
- 阻塞挂起到就绪挂起 : 当有阻塞挂起因相关事件出现时, 系统会把阻塞挂起进程转换为就绪挂起进程
**解挂/ 激活【 把一个进程从内存到外存】 : **
- 就绪挂起到就绪 : 没有就绪进程或挂起就绪进程优先级高于就绪进程时, 会进行这种转换.
- 阻塞挂起到阻塞 : 当一个进程释放足够内 存时, 系统会把一个高优先级阻塞挂起(系统认为会很快出现所等待的事件)进程转换为阻塞进程.
状态队列:
- 由操作系统来维护一组队列, 用来表示系统当中所有进程的当前状态;
- 不同的状态分别用不同的队列来表示(就绪队列, 各种类型的阻塞队列);
- 每个进程的PCB都根据它的状态加入到相应的队列当中, 当一个进程的状态发生变化时, 它的PCB从一个状态中脱离出来, 加入到另外一个队列.
线程
什么是线程 && 为什么使用 ?
需要提出一种新的实体, 满足以下特征:
- 实体之间可以并发执行;
- 实体之间共享相同的地址空间.
这实体就是线程.
线程是进程当中的一条执行流程.
从两个方面重新理解进程:
- 从资源组合的角度: 进程把一组相关的资源组合起来,构成了一个资源平台(环境),包括地址空间(代码段,数据段),打开的文件等各种资源;
- 从运行的角度: 代码在这个资源平台上的一条执行流程(线程).
线程的优缺点:
线程 = 进程 - 共享资源
线程的优点:
- 一个进程中可以同时存在多个线程;
- 各个线程之间可以并发地执行;
- 各个线程之间可以共享地址空间和文件等资源.
线程的缺点: (他的优点 也是 他的缺点 )
- 一个线程崩溃, 会导致其所属进程的所有线程崩溃.(给它了”权限”就得有更高的”责任”)
- 线程所需的资源不同的线程需要独立的寄存器和堆栈, 共享代码,数据和文件等.
★★进程和线程的比较★★★
- 进程是资源分配单位, 线程是CPU调度单位;
- 进程拥有一个完整的资源平台, 而线程只独享必不可少的资源, 如寄存器和栈;
- 线程同样具有就绪,阻塞和执行三种基本状态,同样具有状态之间的转换关系;
- 线程能减少并发执行的时间和空间开销:
- 线程的创建时间比进程短;(直接利用所属进程的一些状态信息)
- 线程的终止时间比进程短;(不需要考虑把这些状态信息给释放)
- 同一进程内的线程切换时间比进程短;(同一进程不同线程的切换不需要切换页表)
- 由于同一进程的各线程之间共享内存和文件资源, 可直接进行不通过内核的通信.(直接通过内存地址读写资源)
线程的实现
线程的实现之 用户线程
在用户空间实现
在用户空间实现的线程机制, 它不依赖于操作系统的内核, 由一组用户级的线程库来完成线程的管理, 包括进程的创建,终止,同步和调度等.
操作系统只能看到进程, 但是却看不到线程 ,线程的TCB在线程库中实现;
特点 :
- 由于用户线程的维护由相应的进程来完成(通过线程库函数),不需要操作系统内核了解用户进程的存在,可用于不支持线程技术的多进程操作系统;
- 每个进程都需要它自己私有的线程控制块(TCB)列表,用来跟踪记录它的各个线程的状态信息(PC,栈指针,寄存器),TCB由线程库函数来维护;
- 用户线程的切换也是由线程库函数来完成,无需用户态/核心态切换,所以速度特别快;
- 允许每个进程拥有自定义的线程调度算法.
缺点:
- 阻塞性的系统调用如何实现?如果一个线程发起系统调用而阻塞,则整个进程在等待;
- 当一个线程开始运行时,除非它主动地交出CPU的使用权,否则它所在的进程当中的其他线程将无法运行;
- 由于时间片分配给进程,所以与其他进程比,在多线程执行时,每个线程得到的时间片较少,执行会较慢.
线程的实现之 内核线程
在内核空间实现
内核线程是在操作系统的内核当中实现的一种线程机制,由操作系统的内核来完成线程的创建,终止和管理.
特点
- 在支持内核线程的操作系统中,由内核来维护进程和线程的上下文信息(PCB和TCB);
- 线程的创建,终止和切换都是通过系统调用,内核函数的方式来进行,由内核来完成,因此系统开销较大;
- 在一个进程当中,如果某个内核线程发起系统调用而被阻塞,并不会影响其他内核线程的运行;
- 时间片分配给线程,多线程的进程获得更多CPU时间;
- Windows NT 和 Windows 2000/XP 支持内核线程.
线程的实现之 轻量级进程
内核支持的用户线程(Solaris/ Linux)
一个进程可以有一个或多个轻量化进程,每个量级进程由一个单独的内核线程来支持.(Solaris,Linux)
上下文切换
程序以进程的形式 在操作系统中运行。
停止当前运行进程(从运行状态变成其他状态),并且调度其他进程(转变为运行状态)
- 必须在切换之前存储许多部分的进程上下文
- 必须能够在之后恢复他们,所以进程不能显示它曾经被暂停过
- 必须快速(上下文切换时非常频繁)
需要存储什么上下文?
- 寄存器(PC,SP…),CPU状态等信息
- 一些时候可能会费时,所以我们应该尽可能避免
操作系统为活跃进程准备了进程控制块(PCB)
操作系统将进程控制块(PCB)放置在一个合适的队列中
有三个队列:
- 就绪队列
- 等待IO队列(每个设备的队列)
- 僵尸队列
进程控制
进程控制 之 创建进程
创建进程的简单实现(fork() )
- 对子进程分配内存
- 复制父进程的内存和CPU寄存器到子进程
- 开销昂贵
在大多数情况下, 我们在调用fork() 之后要调用exec()
- 在fork( ) 操作中内存复制是没有作用的
- 子进程将可能关闭打开的文件和链接
- 开销因此是最高的
- 为什么不能结合他们在一个调用中 ?
vfork()
- 一个创建进程的系统调用,不需要创建一个同样的内存映射
- 一些适合称为轻量级fork()
- 子进程应该几乎立即调用exec()
- 现在不再使用 copy on write 技术
进程控制 之 加载和执行进程
系统调用exec()加载程序取代当前运行的进程
exec()调用允许一个进程”加载”一个不同的程序并且在main开始执行(事实上 _start)
它允许一个进程指定参数的数量(argc)和它字符串参数数组(argv)
如果调用成功(相同的进程,不同的程序)
代码,stack,heap重写
进程控制 之 等待和终止进程
wait()系统调用是被父进程用来等待子进程的结束
一个子进程向父进程返回一个值,所以父进程必须接受这个值并处理
wait()系统调用担任这个要求
- 它使父进程去睡眠来等待子进程的结束
- 当一个子进程调用exit()的时候,操作系统解锁父进程,并且将通过exit()传递得到的返回值作为wait调用的一个结果(连同子进程的pid一起)如果这里没有子进程存活,wait()立刻返回
- 当然,如果这里有为父进程的僵尸等待,wait()立即返回其中一个值(并且解除僵尸状态)
进程结束执行之后,它调用exit()
这个系统调用:
- 将这程序的”结果”作为一个参数
- 关闭所有打开的文件,连接等等
- 释放内存
- 释放大部分支持进程的操作系统结构
- 检查是否父进程是存活着的:
- 如果是的话,它保留结果的值直到父进程需要它;在这种情况里,进程没有真正死亡,但是它进入了僵尸状态
- 如果没有,它释放所有的数据结构,这个进程死亡
- 清理所有等待的僵尸进程
进程终止是最终的垃圾收集(资源回收)