操作系统实战 45 讲
彭东
网名 LMOS,Intel 傲腾项目关键开发者
65203 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 60 讲
尝尝鲜:从一个Hello到另一个Hello (2讲)
特别放送 (1讲)
操作系统实战 45 讲
15
15
1.0x
00:00/00:00
登录|注册

25 | 多个活动要安排(上):多进程如何调度?

调用iretq指令
恢复进程保存在内核栈中的RIP、CS、RFLAGS,(有可能需要恢复进程应用程序的RSP、SS)寄存器
恢复进程保存在内核栈中的通用寄存器
恢复进程保存在内核栈中的段寄存器
设置CPU的RSP寄存器为该进程机器上下文结构中的RSP
如果是新建进程第一次运行,进行处理
装载下一个运行进程的MMU页表
设置当前CPU的tss中的R0栈为下一个运行进程的内核栈
设置下一个运行进程的tss为当前CPU的tss
设置当前运行进程为下一个运行的进程
通过cpuid获取当前CPU的调度数据结构
从下一个进程的内核栈中恢复下一个进程的通用寄存器
调用一个函数切换MMU页表
读取保存在下一个进程机器上下文结构中的RSP的值,把它存到CPU的RSP寄存器中
保存CPU的RSP寄存器到当前进程的机器上下文结构中
保存当前进程的通用寄存器到当前进程的内核栈中
通过cpuid获取当前CPU的调度数据结构
若没有找到合适的进程,返回默认的空转进程
若当前优先级进程链表不为空,选择第一个进程
从高到低扫描优先级进程链表
通过cpuid获取当前CPU的调度数据结构
选择下一个将要运行的进程
确定当前正在运行的进程
初始化osschedcls变量的代码
初始化schedclass_t结构的全局变量
每个优先级对应一个链表头
使用链表数据结构
进程状态表示进程的生命周期
进程调度
组织多个进程
为什么需要多进程调度
进程切换
进程切换
获取空转进程
进程切换
获取空转进程
选择下一个进程
如何获取当前运行的进程
进程调度器入口
管理进程的初始化
如何组织进程
进程的生命周期
进程拿不到资源就要让出CPU
进程需要共享CPU
CPU同一时刻只能运行一个进程
思考题
重点回顾
设计实现进程调度器
管理进程
为什么需要多进程调度
多个活动要安排(上):多进程如何调度?

该思维导图由 AI 生成,仅供参考

你好,我是 LMOS。
上节课,我们了解了什么是进程,还一起写好了建立进程的代码。不知道你想过没有,如果在系统中只有一个进程,那我们提出进程相关的概念和实现与进程有关的功能,是不是就失去了意义呢?
显然,提出进程的目的之一,就是为了实现多个进程,使系统能运行多个应用程序。今天我们就在单进程的基础上扩展多进程,并在进程与进程之间进行调度。
“你存在,我深深的脑海里,我的梦里,我的心里,我的代码里”,我经常一边哼着歌,一边写着代码,这就是我们大脑中最典型“多进程”场景。
再来举一个例子:你在 Windows 上,边听音乐,边浏览网页,还能回复微信消息。Windows 之所以能同时运行多个应用程序,就是因为 Windows 内核支持多进程机制,这就是最典型的多进程场景了。
这节课配套代码,你可以点击这里下载。

为什么需要多进程调度

我们先来搞清楚多进程调度的原因是什么,我来归纳一下。
第一,CPU 同一时刻只能运行一个进程,而 CPU 个数总是比进程个数少,这就需要让多进程共用一个 CPU,每个进程在这个 CPU 上运行一段时间。
第二点原因,当一个进程不能获取某种资源,导致它不能继续运行时,就应该让出 CPU。当然你也可以把第一点中的 CPU 时间,也归纳为一种资源,这样就合并为一点:进程拿不到资源就要让出 CPU。我来为你画幅图就明白了,如下所示。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入介绍了多进程调度的原因和管理进程的方法。首先,文章解释了为什么需要多进程调度,主要是因为CPU同一时刻只能运行一个进程,而系统中通常有多个进程需要共享CPU资源。其次,文章详细介绍了管理进程的方法,包括进程的生命周期、进程状态的定义和进程的组织方式。文章还展示了如何初始化管理进程的数据结构,并设计实现了进程调度器。通过对进程调度器的设计和实现,读者可以了解如何在合适的时间点进行进程调度,从而让系统能够高效地运行多个应用程序。文章内容涵盖了进程调度的原理和实际操作,对于想深入了解多进程调度的读者具有一定的参考价值。文章中还介绍了进程调度器的入口函数、获取当前运行的进程、选择下一个进程以及获取空转进程的方法,为读者提供了实用的技术指导。整体而言,本文内容深入浅出,适合对多进程调度感兴趣的读者阅读学习。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《操作系统实战 45 讲》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(22)

  • 最新
  • 精选
  • Ziggy_aa
    置顶
    一刷的时候只是简单过了内容,到后面就跟不上了。现在二刷是跟着源码一步一步在看。从一开始初始化部分需要对照学习。渐渐熟悉代码风格和逻辑后,从内存中段部分开始就变成了先自己理解代码,再看文章确认了。还是推荐大家把源码读起来,学习的过程中会感觉越来越快。 这一章节最后的几个切换函数让我苦恼了一下。后来发现是自己搞蒙了两件事: 1. 如果你也一下子没反应过来为什么寄存器保存和复原的内容里没有 CS 和 RIP。那是因为 call 指令调用的时候自动已经把它们入栈了。别把这个搞忘了。 2. 我们是在调用 __to_new_context() 之前就已经切换到了新进程的栈了。这样我们在调用 __to_new_context() 的时候,已经是入栈在新进程的栈,所以,随着我们 return 回去,我们就在运行新的教程了。 至于新建进程为什么要用 retnfrom_first_sched 调转。对比代码可以发现,新建进程的 stack 和 __to_new_context() 返回后所需要的 stack 内容是不同的 (新建进程里还有 segment registers 的内容)。

    作者回复: 对的

    2022-07-22
    4
  • neohope
    置顶
    一、数据结构 全局有一个osschedcls变量,其数据结构为schedclass_t,用于管理所有cpu的所有进程。 schedclass_t包括一个 schdata_t数组,每个cpu对应一个。schedclass_t[i],用于管理第i个cpu的全部进程。 schedclass_t[i]包括一个thrdlst_t数组,每个进程优先级对应一个。schedclass_t[i].schdata_t[j]中,管理了第i个cpu的,优先级为j的全部进程。 二、初始化 进程结构初始化: init_krl->init_krlsched->schedclass_t_init ->对于每个cpu,调用schdata_t_init,进行初始化 ->对于每个cpu的每个进程优先级,调用thrdlst_t_init初始化列表 idel进程初始化: init_krl->init_krlcpuidle ->new_cpuidle->new_cpuidle_thread->krlthread_kernstack_init【krlcpuidle_main传参】->krlschedul ->krlcpuidle_start->retnfrom_first_sched启动idel进程 三、进程调度 init_krl->init_krlcpuidle->new_cpuidle->new_cpuidle_thread->krlthread_kernstack_init【krlcpuidle_main传参】->krlschedul ->krlsched_retn_currthread根据当前cpuid,获取正在运行的进程 ->krlsched_select_thread,获取下一个运行的进程 A、根据当前cpuid,选择优先级最高进程列表中的第一个进程 B、并将该优先级的当前进程,重新放回进程列表 C、如果没有进程要运行,则返回idel进程 ->save_to_new_context,进行进程切换 A、保存当前进程的寄存器状态,到当前寄存器的栈 B、切换栈寄存器,指“向下一进程”的栈 C、调用__to_new_context D、从“下一进程”的栈,恢复寄存器状态 E、而上面这些保存的状态,都是“下一进程”在释放CPU时保存好的 F、当CPU再次回到这个所谓的“下一进程”时,又会用同样的方式还原现场,继续执行 ->->__to_new_context A、更新当前运行进程为“下一个进程” B、设置“下一进程”的tss为当前CPU的tss C、设置“下一进程”的内核栈 D、装载“下一进程“的MMU页表 E、如果“下一个进程”没有运行过,调用retnfrom_first_sched,进行第一次运行初始化 ->->-> retnfrom_first_sched 设置好新进程的相关寄存器和栈,直接运行新建进程的相关代码

    作者回复: 大佬 梳理总结到位

    2021-07-06
    3
    14
  • pedro
    置顶
    文中已有答案: retnfrom_first_sched 函数不会返回到调用它的 __to_new_context 函数中,而是直接运行新建进程的相关代码。 新的进程暂时还没有上下文,所以也就没办法切换了,于是直接运行。

    作者回复: 对的 铁子

    2021-07-05
    5
  • 菜鸟
    我想问一下进程产生的整个过程是怎样的?即:命令行运行程序实例,是怎么初始化进程?是怎么把磁盘上的程序加载到内存?怎么确定进程的入口地址等

    作者回复: 看看后面的课程

    2021-08-10
    1
  • 青玉白露
    文中——“retnfrom_first_sched 函数不会返回到调用它的 __to_new_context 函数中,而是直接运行新建进程的相关代码”——就是思考题的答案了。 retnfrom_first_shed可以视为一个初始化的过程

    作者回复: 哈哈 正确的

    2021-07-06
    1
  • cugphoenix
    新建进程初始化内核栈的时候,会给存储在内核栈中的intstkregs_t.r_rip_old,r_cs_old,r_rflgs...这些赋值,在retnfrom_first_sched里面iretq时,正好会把这些值弹出到对应寄存器中,从而实现新进程的运行。

    作者回复: 正确的

    2021-07-06
    1
  • │.Sk
    老师好,请问 td_context.ctx_nextrip 这个属性是不是其实没有用到呀?

    作者回复: 是的 x86上没用到

    2022-09-03归属地:湖北
  • likejjj
    除了进程自己让出cpu,进程调度是不是都是时钟中断触发的?时间片粒度的大小,是不是由时钟的频率决定的?比如1/250

    作者回复: 那不一定

    2022-06-29
  • 如果是你
    请问又是谁调用的进程调度器呢?

    作者回复: 中断 内核

    2022-06-13
  • 艾恩凝
    内存一过,一马平川

    作者回复: 内存很难

    2022-05-05
收起评论
显示
设置
留言
22
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部