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