当前位置: 首页 > news >正文

操作系统:进程调度,创建和终止

进程的调度

调度的概念

进程调度是操作系统在就绪队列中,按照某种算法(策略)动态地选择一个进程,并将CPU的控制权分配给它,使其执行的过程。

调度的类型

特性维度长程调度 (Long-Term Scheduling)中程调度 (Medium-Term Scheduling)短程调度 (Short-Term Scheduling)
中文别名作业调度、高级调度交换调度、中级调度CPU调度、低级调度
核心职责控制入口:决定哪些程序可被调入内存,成为就绪进程控制负载:将进程在内存和磁盘间换出/换入,缓解内存压力分配CPU:从就绪队列中选择下一个要执行的进程
调度对象磁盘上的程序(作业)内存中的进程内存中的就绪进程
发生频率低(秒、分钟级)中等极高(毫秒级)
决策速度慢,可进行复杂权衡中等极快,开销必须最小化
主要目标控制多道程序度
平衡I/O型CPU型进程的混合比例(控制多道程序的度)
提高内存利用率
改善系统总体吞吐量和响应速度
最大化CPU利用率
保证公平性、高吞吐量、低响应时间
关键操作接纳(Admit) 或创建(Create)换出(Swap Out) / 挂起(Suspend)
换入(Swap In) / 激活(Resume)
分发(Dispatch)
(分配CPU)
影响状态无 → 就绪/挂起就绪就绪/阻塞 ↔ 挂起就绪/挂起阻塞就绪 → 运行

类比理解调度的类型:


1. 长程调度 (Long-Term Scheduling) - “招聘经理”

  • 中文别名:作业调度、高级调度

  • 核心职责:决定是否允许一个新的程序进入内存,成为一个有资格竞争CPU的进程。它控制着系统的多道程序度(Degree of Multiprogramming),即内存中同时存在的进程数量。

  • 好比:HR招聘经理决定从大量简历中录用哪些人成为公司正式员工。他需要控制公司总人数,并考虑员工的技能组合(IO型还是计算型),以保证公司整体效率。

  • 特点

    • 执行频率极低:几分钟甚至更久才执行一次。

    • 决策速度可以慢:因为不频繁,可以做复杂的权衡。

    • 平衡负载混合:会混合接纳I/O密集型CPU密集型进程,确保系统资源(CPU和I/O设备)都能得到充分利用。


2. 中程调度 (Medium-Term Scheduling) - “部门经理”

  • 中文别名:交换调度、中级调度

  • 核心职责:当系统内存紧张时,负责将暂时不运行的进程从内存换出(Swap out) 到磁盘挂起,以释放内存空间。当内存有空闲时,再将其换入(Swap in),重新激活。

  • 好比:部门经理发现项目太多,资源紧张,决定让一些暂时不紧急的项目(员工)暂时休假(换出),等资源充足时再叫他们回来继续工作(换入)。这并没有解雇他们,只是暂时挂起。

  • 特点

    • 引入了“挂起”状态:这是中程调度存在的标志。

    • 目的是平衡系统负载:通过换出换入来缓解内存压力,提高系统整体吞吐量和响应速度。


3. 短程调度 (Short-Term Scheduling) - “项目经理”

  • 中文别名:CPU调度、低级调度

  • 核心职责:当CPU空闲时,立刻从内存的就绪队列中选择一个进程,并将CPU分配给它执行。这是最频繁、最核心的调度。

  • 好比:项目经理手下有多个已就绪的员工(进程),每当一个员工完成一项任务或时间到了,经理立刻决定下一个任务派给谁。这个决策需要非常快。

  • 特点

    • 执行频率极高:几十毫秒(ms)就会发生一次。

    • 决策必须极快:因为频繁,调度算法本身不能太复杂,否则开销太大。

    • 是通常所说的“进程调度”:我们讨论的各种调度算法(如先来先服务、短作业优先、时间片轮转、优先级调度)都是指短程调度。


也就是说:

  • 长程调度管 “进不进” 内存(控制数量和质量)。

  • 中程调度管 “待不待” 在内存(灵活调整内存负载)。

  • 短程调度管 “谁先跑” CPU(微观上实现并发执行)。

进程的两种类型

1. I/O密集型进程 (I/O-Bound Process)

  • 特点:这类进程的大多数时间都花在等待输入/输出(I/O)操作的完成上,而不是进行大量的计算。

  • 影响CPU underutilized (导致CPU利用率不足)。因为进程大部分时间在等待,如果把CPU全给它,CPU就闲着了。

  • 行为模式:通常会发起一个I/O请求(如读取文件、等待网络数据、响应鼠标点击),然后因为需要等待慢速的I/O设备而主动放弃CPU,进入阻塞状态。当I/O操作完成后,它被唤醒,进行短暂的计算处理,然后很快又发起下一个I/O请求。

  • 典型例子

    • 大多数交互式程序:文本编辑器、浏览器、Office软件(用户思考或打字时就是在“等待I/O”)

    • 需要频繁读写磁盘或网络的应用

  • 对调度的需求

    • 响应速度:当I/O操作完成(如用户按下一个键),进程需要被快速调度到CPU上运行,以便给用户即时反馈。否则系统会显得“卡顿”。

    • 不需要很大的CPU时间片,因为每次它只做一点计算就去等待I/O了。


2. CPU密集型进程 (CPU-Bound Process)

  • 特点:这类进程的大多数时间都花在执行计算上,会长时间占用CPU而不被打断。

  • 影响I/O underutilized (导致I/O设备利用率不足)。因为进程一直霸占CPU进行计算,很少去请求I/O,磁盘、网络等设备就闲着了。

  • 行为模式:进程包含大量需要CPU进行处理的指令(如循环计算、数学运算、图像渲染)。它们可以连续运行很多个时间片而几乎不产生I/O请求。

  • 典型例子

    • 科学计算、数值模拟

    • 视频编码/解码、图形渲染

    • 编译大型项目(如编译Linux内核)

    • 复杂数据分析

  • 对调度的需求

    • 吞吐量:更关注在单位时间内完成的计算总量。

    • 需要长时间、连续的CPU时间片。如果给它的时间片太短,会导致频繁的上下文切换,反而降低整体效率(因为大部分时间都花在切换进程上了,而不是真正计算)。


  • 理想状态:CPU和I/O都保持“忙碌”的工作状态。当CPU密集型进程在疯狂计算时,I/O密集型进程可以排队进行I/O操作;当I/O密集型进程等待I/O时,CPU可以立刻切换到CPU密集型进程上工作。

  • 这就是“正确的组合”:系统中同时存在这两种类型的进程,可以相互填补对方造成的资源空闲期,像一个配合默契的团队,使得系统的总体效率达到最高。

对比总结

特征I/O密集型进程 (I/O-Bound)CPU密集型进程 (CPU-Bound)
主要时间花费等待I/O操作完成(磁盘、网络、用户输入)执行CPU计算
CPU使用率高(甚至渴望100%占用)
行为特点频繁放弃CPU,进入阻塞长期占用CPU,不主动放弃
核心需求低响应延迟(快速被调度)高吞吐量(大块CPU时间)
典型例子浏览器、文本编辑器、Web服务器视频编码、科学计算、编译器

进程的创建

进程的创建是依靠原语(Primitive)来实现的

原语是一种特殊的程序,它的执行具有原子性,也就是说这段程序的执行必须一气呵成,不可中断。(一个原语操作要么全部执行完成,要么一点都不执行,在执行过程中不允许被任何中断(包括时钟中断))

为什么需要原语?
想象一下,如果没有原语的原子性保证,在创建进程做到一半(比如刚分配了PID,但还没建好页表)时,突然发生时钟中断,CPU转去执行另一个进程了。这会导致系统处于一个极其混乱和不一致的状态,可能引发致命的错误。原语通过“关中断执行-开中断”等方式,确保了这些关键操作的完整性。

进程创建的过程:

  1. 申请并初始化PCB:为新进程分配一个唯一的进程标识符(PID),并在内核中创建一个空的进程控制块(PCB)。

  2. 分配资源:为新进程分配必要的资源,最重要的是建立地址空间。这包括:

    • 创建页表:建立虚拟内存到物理内存的映射关系。

    • 继承或设置资源:处理父进程资源的继承问题(如打开的文件描述符)。

  3. 初始化PCB:将新进程的详细信息填入PCB,包括:

    • 程序计数器(PC)指向程序的入口点(对于 fork() 则是继承父进程的断点)。

    • 分配系统堆栈。

    • 设置进程状态为“就绪”或“就绪/挂起”。

  4. 放入就绪队列:将初始化好的新进程的PCB插入到系统的就绪队列中,等待调度器在合适的时机分配CPU给它运行。

这一切操作,作为一个整体,要么全部成功,要么全部失败(回滚),对外界来说是不可见的、瞬间完成的原子操作。 这就是原语的威力。

进程的终止

触发终止

首先,需要一个事件来触发进程的终止。通常有四种情况:

  1. 正常退出(自愿):进程完成了它的工作,主动调用退出系统调用(如 exit())。这是最常见的情况。

  2. 错误退出(自愿):进程发现了错误并决定自行终止(例如,Python 脚本遇到 SyntaxError)。

  3. 致命错误(非自愿):进程在执行过程中遇到了不可恢复的错误,如执行了非法指令、访问了非法内存地址(段错误)、除以零等。通常由硬件触发异常,再由操作系统处理并终止该进程。

  4. 被其他进程杀死(非自愿):另一个进程(通常拥有权限,如用户通过 kill 命令或父进程)通过系统调用(如 kill())强制终止该进程。


核心操作:执行终止原语

进程终止同样是依靠原语(Primitive)来实现的,例如 exit() 系统调用。这个原语会原子性地完成一系列清理工作,确保系统资源被完整回收,系统状态保持一致。

操作系统内核会执行终止原语,其主要步骤包括:

  1. 传递退出状态:获取进程的退出状态码(例如在 exit(0) 中的 0),并将其保存在该进程的PCB中。父进程可以通过 wait() 等系统调用来获取这个状态码,以了解子进程是如何终止的。

  2. 资源回收与归还:这是终止过程最核心的工作,操作系统会遍历该进程拥有的所有资源,并逐一释放:

    • 释放内存空间:释放该进程占用的所有物理内存页和页表、代码段、数据段、堆栈段等。

    • 关闭打开文件:遍历进程的打开文件表,关闭所有已打开的文件描述符。这一步至关重要,因为它确保了文件数据被正确写回磁盘,并释放了文件锁,避免资源泄漏。

    • 释放其他内核资源:释放该进程占用的所有其他内核对象,如管道、信号量、共享内存段、网络连接等。

  3. 管理进程关系

    • 处理子进程:将该进程的所有子进程移交给了另一个进程(在许多现代系统中,如Linux,会移交给 init 或 systemd 进程),防止子进程成为“孤儿进程”。

    • 通知父进程:向该进程的父进程发送一个 SIGCHLD 信号,通知它有一个子进程已经终止。

  4. 销毁进程控制块(PCB):在确保所有资源都被释放、所有后续事宜都已安排妥当后,操作系统最终会销毁该进程的PCB,并回收其PID。从此,这个进程在系统中就彻底消失了。


需要注意的是,进程的终止并不是单单一个进程的结束,更会引发一系列父子进程间的通信和状态同步行为。其核心在于 “父进程需要知道子进程的死活和结局”

我们来简单说一下这一过程:

场景一:子进程终止 (Child Process Terminates)

当子进程通过 exit() 系统调用(或其它方式)终止时,会发生以下一系列原子性的操作:

  1. 子进程变为“僵尸”(Zombie State)

    • 子进程的执行停止了,资源(内存、CPU等)也被操作系统回收了。

    • 但是,它的进程控制块(PCB)并没有被立即销毁。这个保留的PCB就像一个“墓碑”,里面记录了该进程的进程ID(PID) 和 退出状态(Exit Status)(比如是正常退出还是因为某个信号被杀掉,以及返回值是什么)。

    • 处于这种状态的进程被称为 “僵尸进程”(Zombie or Defunct Process)

  2. 子进程向父进程发送信号(SIGCHLD)

    • 操作系统会向该子进程的父进程发送一个 SIGCHLD 信号,异步地通知它:“你的一个子进程已经终止了”。

  3. 父进程的职责:等待(Wait)

    • 父进程在收到 SIGCHLD 信号后,应该调用 wait() 或 waitpid() 系统调用来回收(reap) 那个子进程。

    • wait() 的作用是:从僵尸子进程中获取退出状态信息,并彻底销毁它的PCB,让这个子进程真正从系统里消失。

    • 如果父进程一直不调用 wait(),那么子进程就会一直保持僵尸状态。


场景二:父进程终止 (Parent Process Terminates)

当父进程先于子进程终止时,情况则有所不同。操作系统不会允许子进程变成“孤儿”无人照管,因此有特殊机制:

  1. 子进程变为“孤儿”(Orphaned Process)

    • 父进程终止后,它的子进程就失去了父进程。

  2. 操作系统介入:重新指定父进程(Reparenting)

    • 为了解决孤儿问题,操作系统内核(具体是 init 进程,在现代系统中是 systemd)会接管所有这些孤儿进程

    • init / systemd 进程(PID = 1)会成为这些孤儿进程的新父进程。这是一个原子性的操作

  3. 新父进程的行为

    • init / systemd 进程被设计为一个简单的守护进程,它会持续地调用 wait() 来回收其任何终止的子进程。

    • 因此,被接管后的孤儿进程如果终止,会很快被它的新父进程(init)回收,不会长时间保持僵尸状态


总结与关键点

行为子进程先终止父进程先终止
触发事件子调用 exit()父调用 exit()
子进程状态僵尸 (Zombie)孤儿 (Orphan)
操作系统动作向父发送 SIGCHLD 信号reparenting:将子进程过继给 init
父进程职责调用 wait() 回收子进程无(父进程已死)
最终结果父进程回收后,子进程彻底消失init 会负责回收子进程,子进程彻底消失

关键结论:

  1. 僵尸进程是正常的中间状态:一个进程终止后,到其父进程调用 wait() 读取其状态之前的这段时间,它必然处于僵尸状态。这是设计使然,目的是为了让父进程有机会获取子进程的最终状态。

  2. 僵尸进程的危害:如果父进程永远不调用 wait()( due to a bug),僵尸进程就会一直存在。虽然它不消耗内存和CPU,但会占用宝贵的进程ID(PID)。如果系统中僵尸进程过多,将无法创建新进程。

  3. 孤儿进程无害:孤儿进程本身仍在正常运行,只是父进程变了。它们最终会被 init 进程妥善处理,不会造成资源泄漏。这是一个重要的安全机制。

总结:进程终止的本质

进程终止的本质是:一个进程的执行序列结束,操作系统通过原子性的终止原语,回收其占用的所有资源,并将其从系统的进程列表中彻底移除,从而确保系统资源不会泄漏,并为后续进程的运行腾出空间。

http://www.dtcms.com/a/418307.html

相关文章:

  • Kafka09-速答-尚硅谷
  • Jenkins与GitLab-CI的技术对比分析
  • 2025 年 AI 智能体(Agent)发展全景:技术突破、场景落地与产业重构
  • 电子商务的网站建设名词解释网站设计的流程简答题
  • Spark源码中的线程池
  • Kafka06-进阶-尚硅谷
  • TDengine 时序函数 IRATE 用户手册
  • 网站模板源码下载广告网站建设
  • 一键部署 Spring Boot 到远程 Docker 容器
  • Docker 入门:容器化开发的强大工具
  • iOS 26 全景揭秘,新界面、功能创新、兼容挑战与各种工具在新版系统中的定位
  • 北京交易中心网站电商网站建设需要
  • 【ansible/K8s】K8s的自动化部署源码分享
  • C++STL之list
  • CentOS 7安装部署RabbitMQ
  • 本地怎么远程调试服务器
  • AndroidID重置功能开发
  • 【Byte 类型】编程基石:揭开 `Byte`(字节)的神秘面纱
  • 天津做网站哪家服务好北京正邦品牌设计公司
  • 外贸搜素网站android studio开发app实例
  • 5. Prompt 提示词
  • android 自定义样式 Toast 实现(兼容 Android 4.1+~Android 16(API 16))
  • android SharedPreferences 工具类 * 兼容 Android 16+ (API 16)
  • 宁波易通建设网站网站备案信息代码
  • 阿里云OpenLake及行业解决方案年度发布,助力千行百业Data+AI一体化融合
  • 独立站收款方式有哪些
  • 2025 年 Python 数据分析全栈学习路线:从入门到精通的进阶指南
  • 行业类网站应如何建设网站怎么建设以及维护
  • Go 和云原生 的现状和发展前景
  • C# 中Byte类型转化问题