《操作系统》——进程管理

第一部分:进程和线程
1.1进程描述
1)进程定义:一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程
在这里插入图片描述

  • 将一个文件系统中静态的程序,通过操作系统,在内存中让 CPU 执行起来形成一个动态的执行过程
    2)进程的组成
    一个进程应该包括正在运行的一个程序的所有格状态信息:
  • 程序的代码
  • 程序处理的数据
  • 程序计数器中的值,指向下一条将运行的指令
  • 一组通用的寄存器的当前值,堆、栈
  • 一组系统资源(如打开的文件)
    问:进程与程序的联系?
  • 程序是产生进程的基础,进程执行的所有功能在程序中都有相应描述
  • 程序每次的执行构成不同的进程,程序只有一份放在硬盘中,但可以多次不同输入的执行
  • 进程是程序功能的体现
  • 通过调用关系,一个进程可以包括多个程序
    问:进程与程序的区别?
  • 进程是动态的,程序是静态的:程序是有序代码的集合,进程是程序的执行,且进程有核心态/用户态(平时运行在用户态,系统调用时运行在核心态)
  • 进程是暂时的,而程序是永久的:进程是一个状态变化的过程,程序可长久保存
  • 进程与程序组成不同:进程的组成包括程序、数据和进程控制块(即进程状态信息)
    3)进程的特点
  • 动态性:可动态地创建、结束进程
  • 并发性:进程可以被独立调度并占用处理机运行
  • 独立性:不同进程的工作不相互影响,比如虚拟内存(页表)实现了这一特点,不同的进程有不同的页表
  • 制约性:因访问共享数据/资源或进程间同步而产生制约
    进程控制块(PCB):描述进程的数据结构,操作系统为每个进程维护一个 PCB,用来保存与该进程有关的各种状态信息
    4)进程控制结构
    进程控制块:操作系统管理控制进程运行所用的信息集合,操作系统用 PCB 来描述进程的基本情况和运行变化的过程,PCB是进程存在的唯一标志
  • 进程的创建:为该进程生成一个 PCB
  • 进程的终止:回收它的 PCB
  • 进程的组织管理:通过对 PCB 的组织管理来实现
    PCB 含有以下三大类信息:
  • 进程标识信息:如本进程的标识,本进程的产生者标识(父进程标识),用户标识
  • 处理机状态信息保存区:保存进程的运行现场信息,如用户可见寄存器(用户程序可使用的数据,地址等寄存器)、控制和状态寄存器(程序计数器,程序状态字)、栈指针(过程调用/系统调用/中断处理和返回时需要用到)
  • 进程的控制信息:调度和状态信息(用于操作系统调度进程并占用处理机使用),进程间通信信息(为支持进程间的通信相关的各种标识、信号、信件等,这些信息存在接收方的进程控制块中),存储管理信息(包含有指向本进程映像存储空间的数据结构),进程所用资源(说明由进程打开、使用的系统资源,如打开的文件等),有关数据结构连接信息(进程可以连接到一个进程队列中,或连接到相关的其他进程的 PCB)
    1.2进程状态
    1.2.1进程的生命期管理
    1)进程创建
    引起进程创建的3个主要事件:系统初始化时/用户请求创建一个新进程/正在运行的进程执行了创建进程的系统调用
    系统会在内存中构建出一个PCB,即代表了进程最开始的情况
    2)进程运行:内核选择一个就绪的进程,让它占用处理机并执行,设计CPU调度算法问题
    3)进程等待(阻塞)
    引起进程等待(阻塞)的主要原因:
  • 请求并等待系统服务,无法马上完成
  • 启动某种操作,无法马上完成
  • 需要的数据没有到达
    进程只能自己阻塞自己,因为只有进程自身才知道何时需要等待某种事件的发生
    4)进程唤醒
    唤醒进程的原因:
  • 被阻塞进程需要的资源可被满足
  • 被阻塞进程等待的事件到达
  • 将该进程的PCB插入到就绪队列
    进程只能被别的进程或操作系统唤醒
    5)进程结束
    以下四种情况,进程结束:
  • 正常退出(自愿的)
  • 错误退出(自愿的)
  • 致命错误(强制性的),eg: 访问非法地址空间
  • 被其他进程所杀(强制性的)
    1.2.2进程状态变化模型
    在这里插入图片描述
    1.2.3进程挂起模型
    1)进程挂起的原因:合理且充分地利用系统资源,进程在挂起状态时,意味着进程没有占用内存空间,处在挂起状态的进程映像在磁盘上
    2)挂起状态:
  • 阻塞挂起状态:进程在外存并等待某事件的出现
  • 就绪挂起状态:进程在外存,但只要进入内存,即可运行
    3)与挂起相关的状态转换
    在这里插入图片描述

挂起:把一个进程从内存转到外存
解挂/激活:把一个进程从外存转到内存

  • 阻塞到阻塞挂起:没有进程处于就绪态或就绪进程需要更多内存资源时,会进行这种转换,以提交新进程或运行就绪进程
  • 就绪到就绪挂起:当有搞优先级阻塞(系统认为会很快就绪的)进程和低有限就绪进程时,系统会选择挂起低优先级就绪进程
  • 运行到就绪挂起:对抢先式分时系统,当有高优先级阻塞挂起进程因时间出现而进入就绪挂起时,系统可能会把运行进程转到就绪挂起状态
  • 阻塞挂起到就绪挂起:当有阻塞挂起进程因相关事件出现时,系统会把阻塞挂起进程转换为就绪挂起进程
  • 就绪挂起到就绪:没有就绪进程或挂起就绪进程优先级高于就绪进程时
  • 阻塞挂起到阻塞:当一个进程释放足够内存时,系统会把一个高优先级阻塞挂起进程转换为阻塞进程
    4)状态队列
  • 由操作系统来维护一组队列,用来表示系统中所有进程的当前状态
  • 不同的状态由不同的队列表示
  • 每个进程的 PCB 都根据它的状态加入到相应的队列中,当一个进程的状态发生变化时,它的 PCB 从一个状态队列中脱离出来,加入到另一个队列
    在这里插入图片描述
    注:多个就绪队列是因为不同进程优先级不同
    1.3线程管理
    1.3.1什么是线程
    1)线程:进程当中的一条执行流程
    2)从两个方面重新理解进程 = 资源管理 + 线程
  • 从资源组合的角度:进程把一组相关的资源组合起来,构成了一个资源平台,包括地址空间、打开的文件等资源
  • 从运行的角度:代码在这个资源平台上的一条执行流程(线程)
    在这里插入图片描述
    注:属于一个进程的不同线程共享进程的资源,如堆、代码段、数据段
    3)线程的优点:
  • 一个进程中可以同时存在多个线程
  • 各线程之间可以并发执行
  • 各个线程之间可以共享地址空间和文件等资源
    4)线程的缺点:一个线程崩溃,会导致所属进程的所有线程崩溃
    5)线程所需的资源
    在这里插入图片描述
  • 不同线程要有各自的堆栈和寄存器,以保证个线程的执行流程相互独立互不影响
    6)线程与进程的比较
  • 进程是资源的分配单位,线程是 CPU 调度单位
  • 进程是一个完整的资源平台,而线程只独占必不可少的资源,如寄存器和栈
  • 线程同样具备就绪、阻塞、运行三种基本状态,同样具备状态间的转换关系
  • 线程能减少并发执行的时间和空间开销,eg: 线程的创建时间比进程短、线程的终止时间比较短、同一进程的线程切换时间比进程短(不需要切换页表)、由于同一进程的各线程间共享内存和文件资源,可直接进行不通过内核的通信
    1.3.2线程的实现
    1)用户线程:在用户空间实现,操作系统看不到
    在这里插入图片描述
  • 在用户空间实现的线程机制,不依赖于操作系统的内核,由一组用户级的线程库函数来完成线程的管理,包括线程的创建、终止、同步和调度等
  • 每个进程都需要自己私有的线程控制块(TCB)列表,用来跟踪记录它的各个线程的状态信息(PC、栈指针、寄存器),TCB 由线程库函数维护
  • 用户线程的切换是由线程库函数来完成的,无需用户态/核心态切换,速度快
  • 允许每个进程拥有自定义的线程调度算法
    2)内核线程:在内核中实现,由操作系统内核完成线程的创建、终止和管理
    在这里插入图片描述
  • 在支持内核线程的操作系统中,由内核维护进程和线程的上下文信息(PCB 和 TCB)
  • 线程的创建、终止和切换都是通过系统调用/内核函数的方式来进行,由内核完成,因此系统开销较大
  • 在一个进程中,若某个内核线程发起调用而被阻塞,并不会影响其他内核线程的运行
  • 时间片分配给线程,多线程的进程获得更多 CPU 时间
    3)轻量级进程:在内核中实现,支持用户线程,一个进程可有一个或多个轻量级进程,每个轻量级进程由一个单独的内核线程支持
    在这里插入图片描述
    注:比较复杂,实际效果并不理想
    1.3.3多线程编程接口举例
    1)进程切换(上下文切换):停止当前运行进程(从运行状态改变成其他状态)并且调度其他进程(转变为运行状态)
  • 进程生命周期的信息:寄存器(PC, SP, …);CPU状态;内存地址空间
    在这里插入图片描述
    在这里插入图片描述
    2)进程创建
    在这里插入图片描述
    在这里插入图片描述
    3)加载和执行进程
  • 系统调用 exec() 加载一个完全不同的程序取代当前运行的进程,并从 main 开始执行(即 _start)
  • 代码段、栈(stack)和堆(heap)等完全重写
    执行 fork() 之后
    执行 exec() 之后
  • vfork():virtual fork在这里插入图片描述
    3)等待和终止进程
    wait() 系统调用是父进程用来等待子进程的结束
    wait() 系统调用的作用:
  • 它使父进程去睡眠来等待子进程的结果
  • 当有一个子进程调动 exit() 时,操作系统解锁父进程,并且将通过 exit() 传递得到的返回值作为 wait() 调用的一个结果(连同子进程的 pid 一起)如果这里没有子进程存活,wait() 立刻返回
  • 若这里有为父进程的僵尸等待,wait() 立即返回其中一个值(并且解除僵尸状态)
    补充:
    僵尸态:在 exit() 执行完毕,而 wait() 还没有执行完毕时,子进程所处的一个状态,此时子进程已经无法正常工作了,只是等着被父进程回收
    若父进程已经结束了,而子进程的僵尸态还没解除,操作系统会定时检查进程控制块的链表,并回收该僵尸进程,从而保证系统中不会有太多的僵尸进程
    在这里插入图片描述
  • exec() 可能位于从 Running 态到 Blocked 态的过程
    第二部分:CPU调度
  • 从就绪队列中挑选一个进程/线程作为 CPU 将要运行的下一个进程/线程
  • 调度程序:挑选进程/线程的内核函数(通过一些调度策略)
    在这里插入图片描述
    2.1调度原则
    1)调度算法的评价指标
  • CPU 使用率:CPU 处于忙状态所占时间的百分比
  • 吞吐量:单位时间内完成的进程数量
  • 周转时间:一个进程从初始化到结束,包括所有等待时间所花费的时间
  • 等待时间:进程在就绪队列中(即就绪态)的总时间
  • 响应时间:从一个请求被提及到产生第一次响应所花费的时间,比如点击鼠标
    2)调度算法的目标
  • 减少响应时间:及时处理用户的输出并且尽快将输出提供给用户
  • 减少平均响应时间的波动:在交互系统中,可预测性比高差异低平均更重要
  • 增加吞吐量:减少开销(操作系统开销,上下文切换),系统资源的高效利用(CPU,I/O设备)
  • 减少等待时间:减少每个进程的等待时间
    注:这些指标很难全都顾及,需要进行取舍;吞吐量是操作系统的计算带宽,响应时间是操作系统的计算延迟
    3)公平的定义:保证每个进程都等待相同的时间,但公平通常会增加平均响应时间
    2.2调度算法
    1)FCFS(First Come,First Served):先来先服务
    在这里插入图片描述
    优点:简单
    缺点:
  • 平均等待时间波动较大
  • 花费时间少的任务可能排在花费时间长的任务后面
  • 可能导致I/O和CPU之间的重叠处理:CPU 密集型进程会导致 I/O 设备闲置时,I/O 密集型进程也在等待
    2)SPN(SJF) SRT:短进程优先(短作业优先)短剩余时间优先
    在这里插入图片描述
  • 执行时间越短,优先级越高
  • SPN(SJF):不可抢占,当一个执行时间更短的进程进入队列时,不会影响当前进程的执行
  • SRT:可抢占,当一个执行时间更短的进程进入队列时,会抢占当前的进程,当前运行进程从运行态变到就绪态,进入就绪队列,新来的进程占有 CPU 执行
    优点:最优平均等待时间
    缺点:
  • 可能导致饥饿:连续的短任务流会使长任务饥饿,短任务可用时的任何长任务的 CPU 时间都会增加平均等待时间
  • 要预先知道一个程序的运行时间,很难得到精确值,只能根据之前该进程所花的时间来预测它将要花多少时间
    3)HRRN(Highest Response Ratio Next):最高响应比优先
    在这里插入图片描述
  • 解决了 SPN 存在的饥饿问题,但仍需预先知道一个程序的运行时间
    4)Round Robin:轮询
  • 在叫做量子(或时间切片)的离散单元中分配处理器
  • 时间片结束时,切换到下一个准备好的进程
    在这里插入图片描述
  • 若时间片太大,会导致等待时间过长,极限情况甚至会退化为 FCFS
  • 若时间片太小,反应迅速,但吞吐量(单位时间内完成的进程数量)由于大量的上下文切换的开销而受到影响
    目标:
  • 选择一个合适的时间片,从而维持上下文切换的开销处于 1% 以内
    在这里插入图片描述
  • FCFS 可能会比 RR 高效,因为 FCFS 没有上下文切换带来的消耗
  • 但 RR 的好处是具有公平性且响应快于 FCFS
  • Best FCFS 相当于短进程优先的调度策略,因此其平均等待时间受进程进入队列的顺序影响较大
    5)Mulitlevel Feedback Queues:多级反馈队列
    优先级队列中的轮询
    在这里插入图片描述
    在这里插入图片描述
    6)Fair Share Scheduling:公平共享调度
    在这里插入图片描述
  • 面向服务器、科学计算的计算机,需要多人多用户共享,需要在用户级别实现对资源的公平共享,而非进程级别
    2.3实时调度
    1)实时系统:正确性依赖于其时间和功能两方面的一种操作系统,常用于工业控制,确保某些任务在规定时间内必须完成,时间约束的及时性非常重要,而速度和平均性能相对不重要
  • 强实时系统:需要在保证时间内完成重要的任务,必须完成
    eg:在航空航天、军事、核工业等一些关键领域中,应用时间需求应能够得到完全满足,否则就造成如飞机失事等重大地安全事故,造成重大地生命财产损失和生态破坏
  • 软实时系统:要求重要的进程的优先级更高,尽量完成,并非必须
    eg:如视频点播系统、信息采集与检索系统就是典型的弱实时系统。系统只需保证绝大多数情况下视频数据能够及时传输给用户即可,偶尔的数据传输延迟对用户不会造成很大影响
    2)调度策略
  • 静态优先级调度:在任务执行之前,优先级就已经确定了
    eg:对于通用系统,FCFS 其实就是静态调度的策略
    在这里插入图片描述
  • 动态优先级调度:任务的优先级随着执行在动态的变化
    eg:对于通用系统,RR 其实就是动态调度的策略
    在这里插入图片描述
    第三部分:临界区
    1.进程/线程,计算机/设备需要合作
  • 优点1:共享资源,eg:一台电脑,多个用户
  • 优点2:加速,eg: 多处理器,将程序分成多个部分并行执行
  • 优点3:模块化,将大程序分解成小程序,eg:以编译为例,gcc 会调用cpp,cc1,cc2,as,ld等
    2.一些基础概念
    1)原子操作:指一次不存在任何中断或失败的执行,即该执行成功结束或者根本没有执行,并且不应该发现任何部分执行的状态
  • 有些看上去是原子操作,实际上不是,比如 x++,这样的简单语句,实际上是由3条指令构成的
  • 有时即使是单条机器指令都不是原子的,比如:pipeline,out-of-order,page fault等
    2)临界区:指进程中的一段需要访问共享资源并且当另一个进程处于相应代码区域时便不会被执行的代码区域
    3)互斥:当一个进程处于临界区并访问共享资源时,没有其他进程会处于临界区并且访问任何相同的共享资源
    4)死锁:两个或以上的进程,在相互等待完成特定任务,而最终没法将自身任务进行下去
    5)饥饿:一个可执行的进程,被调度器持续忽略,以至于虽然处于可执行状态却不被执行
    3.临界区的属性
  • 互斥进入:同一时间临界区内最多存在一个线程
  • 有空让进:如果一个线程要进入临界区,那么最终会成功
  • 有限等待:如果一个线程 i 处于入口处,那么再 i 的请求被接受之前,其他线程进入临界区的时间是有限制的
  • 无忙等待(可选):如果一个进程在等待进入临界区,那么在它可以进入之前会被挂起
    4.临界区实现方法一:禁用硬件中断
    1)实现方法:
  • 在进入临界区时,禁用中断
  • 在离开临界区时,开启中断
    2)缺点:
  • 一旦中断被禁用,外设中断无法响应,交互性变差
  • 临界区的长短不确定,可能会导致其他线程饥饿
  • 对多 CPU 无效,因为一个 CPU 执行关中断指令时仅将自己的中断屏蔽,对其他 CPU 无效,其他 CPU 仍可产生中断,从而无法解决互斥问题
    5.临界区实现方法二:基于软件的解决方法
    在这里插入图片描述
    1)尝试1——轮转法
    在这里插入图片描述
  • 满足互斥要求,一个进入临界区,另一个自旋等待
  • 不满足有空让进,若 P0 进程完成后不能再次进入,必须 P0 和 P1 交替执行
    2)尝试二
    在这里插入图片描述
  • 不满足互斥进入,如果进程0完成了while语句而未设置 flag 后切换到了进程1,进程1也可执行while语句后也将 flag 设置为1,则两个进程都已经进入了临界区
    3)尝试三——标记法
    在这里插入图片描述
  • 满足互斥进入
  • 不满足有空让进,两个进程进行切换后,两个进程的 flag 均设为了1,则两个进程都发生了自选等待,即发生了死锁
    4)正确尝试—— Peterson 算法
    在这里插入图片描述
    在这里插入图片描述
  • 满足互斥进入,若两个进程都进入临界区,则 turn 既等于 0 又等于 1,矛盾
  • 满足有空让进,若进程1不在临界区,则 flag[1] = false,或 turn = 0,则进程0肯定能进入
  • 满足有限等待,若进程0要进入,则 flag[0] = true,后面的 P1 不
    5)对于 n 个进程——面包店算法
    在这里插入图片描述
  • 仍然是标记和轮转的结合
  • 轮转:每个进程都获得一个序号,序号最小的进入
  • 标记:进程离开时序号为0,不为0的序号即标记
    在这里插入图片描述
    缺点:
  • 基于纯软件的设计较为复杂
  • 无法进入临界区时,进程忙等,造成CPU浪费
    优点:对硬件需求较低,只需要硬件有 LOAD 和 STORE 指令时原子操作
    6.临界区实现方法三:更高级的抽象(广泛使用)
    1)特殊的原子操作指令
    大多数现代体系结构通过特殊的内存访问电路针对单处理器和多处理器提供了一些特殊原子操作指令,通过这些硬件原语的辅助,使得操作系统提供更高级的编程抽象(锁、信号量)来简化并行编程,主要是 Test-and-Set 和 Exchange
    在这里插入图片描述
    Test-and-Set(测试和置位)
  • 从内存中读取值
  • 测试该值是否为1(然后返回真或假)
  • 内存值设置为1
    Exchange(交换)
  • 交换内存中的两个值
    注:这两个指令语义上都是由多个指令组成的,但是其已经被封装成了一条机器指令,是在制造 CPU 时设计好的,即原子操作,因此在执行是不会被打断
    2)临界区方案设计
    根据特殊原子操作,设计更高级抽象
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    优点:
  • 适用于单处理器或者共享主存的多处理器中任意数量进程
  • 简单并且容易证明
  • 可以用于支持多临界区
    缺点:
  • 当进程离开临界区并且多个进程在等待的时候可能导致饥饿,有的进程可能一直抢不到锁
  • 在一些实时系统中,可能出现死锁,比如一个低优先级进程拥有临界区并且一个高优先级进程也需要,那么高优先级进程会获得处理器并等待临界区

第四部分:互斥与同步
1.信号量
1)抽象数据类型

  • 一个整型(sem),两个原子操作
  • P():sem 减1,如果 sem < 0,等待,否则继续(进入临界区)
  • V():sem 加1,如果 sem <= 0,唤醒一个等待的 P
    2)在早期的操作系统是主要的同步原语,现在很少使用,但还是非常重要的
    2.信号量的使用
  • 信号量是整数,且是被保护的变量,初始化完成后,唯一改变一个信号量值的办法是通过 P() 和 V(),操作必须是原子
  • P() 能阻塞,而 V() 不会阻塞
    1)两种类型信号量
  • 二进制信号量:可以使 0 或 1
  • 一般/计数信号量:可取任何非负值
  • 二者相互表现(给定一个可以实现另一个)
    2)信号量可以用在2个方面
  • 互斥
  • 条件同步(调度约束——一个线程等待另一个线程的事情发生)
    3)用二进制信号量实现互斥
  • 可以用来代替上一讲的 lock 机制
    在这里插入图片描述
    4)用二进制信号量实现调度约束
    在这里插入图片描述
  • 线程A先执行 P() 操作,信号量变为 -1,会导致挂起等待;线程B执行到 V() 操作时,才会唤醒线程A,以继续执行,从而实现了调度约束
    5)生产者-消费者问题
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • mutex 信号量——锁,初值为1,保证临界区中只有一个线程
  • fullBuffers 信号量——初值为0,表示 buffer 为空
  • emptyBuffers 信号量——初值为n,表示 buffer 中可以放多少个数据
    3.信号量的实现
    在这里插入图片描述
    信号量的特点:
  • 信号量的双用途:互斥和条件同步,但等待条件是独立的互斥
  • 读/开发代码比较困难,程序员必须非常精通信号量
  • 容易出错
    4.管程
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    条件变量的具体实现:在这里插入图片描述
    注:信号量中 sem 是信号量的个数,而管程中 numWaiting 是等待队列中等待的个数

用管程实现 生产者-消费者 问题
在这里插入图片描述

  • count 表示当前 buffer 空闲情况,0为空,n为满
  • 与信号量的实现不同,管程的实现中,lock 不是紧紧靠着 buffer 的,而是位于函数的头和尾,这是由管程的定义决定的,线程进入管程的时候只有一个线程能进去,才能执行管程所管理的所有函数
    在这里插入图片描述
  • 左侧方法实现较为容易,signal() 唤醒操作执行后,不立即切换线程,而是等待本线程完全完成后再切换
  • 右侧方法更完美,signal() 唤醒操作执行后立即切换线程,而将本线程阻塞,但实现起来较为复杂在这里插入图片描述
  • 左侧的实现方式,signal() 之后没有立即切换让等待的线程执行,而是继续执行本线程,因此有可能存在多个条件变量上的线程被唤醒,多个被唤醒的线程去抢 count,因此需要 while 来确定 count 是否为 n
  • 右侧的实现方式,signal() 操作之后会马上切换等待线程, 因此 if 就可满足
    在这里插入图片描述

第五部分:死锁

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值