揭秘Linux内核线程切换底层实现

哈喽,我是子牙,一个很卷的硬核男人

深入研究计算机底层、Windows内核、Linux内核、Hotspot源码……聚焦做那些大家想学没地方学的课程。

为了保证课程质量及教学效果,一年磨一剑,三年先后做了这些课程:手写JVM、手写OS、带你用纯汇编写OS、手写64位多核OS、实战Linux内核…

今天想跟大家聊什么呢?线程切换,一个很抽象的话题。为什么说抽象呢?因为线程是一个很抽象的东西,切换也是一个很抽象的东西,两个在一起就更抽象了。我争取讲得具象一点,让大家看完以后,能够彻底理解线程切换

我会讲到这些:

  1. 线程切换 VS 进程切换

  2. 线程切换的通用框架

  3. Linux内核的线程切换底层实现

  4. 什么是线程上下文?如何保存?如何恢复?

  5. 免费领取我写的OS中的线程切换代码

01  如何正确理解进程切换
 

问大家一个问题:Linux内核会直接做进程切换吗?什么时候做?由谁来做?

如果你开始思考这个问题的答案,而没有去思考这个问题本身是否存在问题,恭喜你,你入坑了!这个问题本身是有问题的,有问题的地方就是标红的那两个字:直接。Linux内核是不会直接做进程切换的,但是会间接去做,如何理解呢?

我们都知道,所有的操作系统内部都有调度器,但是调度器调度的基本单位是线程,而非进程,所以说操作系统不会直接调度进程。但是如果调度器将要调度的线程与正在运行的线程不是同一个进程的,就会发生进程切换。

为什么要发生进程切换呢?这就要说到如何理解进程与线程。其实视角有很多,无所谓对错,说下我的看法,我打个比方:操作系统就相当于一个世界,线程是每一个干活的人,但是人,他一定是属于某个公司,或者某个行业,或者某个领域,因为这些决定了一个人活动的有效范围、可以用的资源。

线程也是一样的,它需要内存空间、需要描述符空间、需要缓存空间、甚至调度优先级…这些东西都是谁给它的,它所属的进程。

所以,如果一个操作系统,只要一个进程,所有的线程共享进程提供的内存空间、描述符空间、信号空间…就不存在进程切换一说,很明显这不现实。

所以问题的答案是:Linux内核不会直接做进程切换,会在调度线程的时候判断,如果将要调度的线程与正在运行的线程不是同一个进程的,就需要发生进程切换。这件事是由调度器来完成的,先切换进程,切换虚拟内存空间,再调度线程…

02   线程切换通用框架

我们先不急着研究Linux内核线程切换的底层实现,肯定封装得很抽象,很难看得懂它的代码。如果你从来没有接触过这块知识,甚至要看哪块代码都不清楚……我们先自己想想,如果我们自己来实现一个os,线程切换机制我们会如何去规划、代码会如何去写…

如果对此有一点点研究的小伙伴,应该能略微看懂下面这张图。一般操作系统线程切换机制大概就长这样

揭秘Linux内核线程切换底层实现_第1张图片

为什么称这张图描述的内容是通用框架呢?因为不管是pc端,还是手机端,亦或是嵌入式设备,线程切换几乎都是这样实现的。接下来我就以x86_64架构为例,给大家解释一下这张图

线程切换是个动作,得有一个力量来触发这个动作,这个力量是什么呢?如果研究过Linux内核,应该听过:时钟中断。时钟中断的特点是什么呢?周期性的产生一个中断信号。使用这个力量,让它去调用调度器,就可以实现线程切换。
 

揭秘Linux内核线程切换底层实现_第2张图片

时钟中断是一种中断信号,是谁让它周期性产生中断的呢?是由系统定时器PIT,准确地说,是由主板上的8253或8254芯片实现的。这个芯片是可编程的,我们可以通过写代码控制时钟中断产生的频率,即可以影响线程切换发生的频率,是不是很酷!揭秘Linux内核线程切换底层实现_第3张图片

时钟中断是单核环境下调度线程的力量,多核环境下就不用了,那多核环境下是用哪股力量的呢?时间片到期中断

揭秘Linux内核线程切换底层实现_第4张图片

 多核环境下用多核中断控制器APIC,每个CPU核内部有个local APIC,local APIC内部有定时器,多核环境下调度线程的力量,就源于local APIC内部的定时器周期性产生中断信号,我们同样可以通过配置定时器频率来左右调度线程的频率。现实中这个值是测算出来的,我这边随便写了一个

揭秘Linux内核线程切换底层实现_第5张图片

毫无疑问,硬件本身提供的机制都太呆板了,很难实现最大限度的公平,因为线程真正使用了多少CPU时间片,会受到很多因素的影响如中断的产生、线程阻塞、优先级的变化……于是就出现了Linux内核中的CFS算法。它会将绝大多数因素考虑进去,动态计算vruntime的值,参考vruntime的值来完成调度。

发生中断以后会进入中断处理例程,在这个例程的起始位置就需要判断是否有线程正在运行,如果有,就要保存上下文。什么是线程上下文?如何保存?保存在哪里?下文有讲到。如果没有,就让调度器去决定调度哪个线程,最终完成线程的运行与退出

绝大多数线程不是一轮CPU时间片周期就能运行完的,但是各种中断都在不断的发生,意味着线程运行在不断被打断、不断被调度、不断保存恢复上下文。如果你想彻底玩明白这里,你得学会堆栈图

揭秘Linux内核线程切换底层实现_第6张图片

关注公众号【硬核子牙】回复【汇编教程】免费领取我讲的汇编课程,里面有讲到堆栈图 当然,这里展示的只是一个通用的框架,具体实现的话,会多出来很多细节。但是现在,我们至少对线程调度有个总体上的认识了,我们至少掌握了线程切换那20%的知识点,再去研究剩余的80%,就会轻松很多…

03  什么是线程上下文

比如线程函数长这样

揭秘Linux内核线程切换底层实现_第7张图片

描绘一下场景:在本轮CPU时间片周期内,这个线程执行到i = 1233,这时候发生了时间片到期中断,这个线程就没有机会运行了,需要将CPU时间片让出去,当下次获得调度,能够从i = 1234位置接着运行,而不是从i = 0开始又从头运行

什么是线程上下文呢?其实就是程序执行的时候,CPU内部寄存器中的值、线程栈当前使用的位置

揭秘Linux内核线程切换底层实现_第8张图片

当发生中断,CPU进入中断处理例程的那一刻,就像给上一个运行的线程拍了张快照,这时候我们将寄存器中的值及线程栈当前使用的位置保存下来,就叫保存上下文。具体如何保存,看实现操作系统的人如何去规划,比如Linux内核是保存在栈中的,thread_info中只保存栈指针rbp、rsp即可

在我写的操作系统中,我是保存在结构体中的,这样可以看得更直观一下,实现的时候,也好调试一些

揭秘Linux内核线程切换底层实现_第9张图片保存了上下文,下次这个线程获得调度的时候,我们将上下文恢复到特定的寄存器中,程序就可以接着运行了

揭秘Linux内核线程切换底层实现_第10张图片

是不是amazing!

其实如果你能看得懂那些商业项目如Linux内核、hotspot虚拟机、MySQL…并能理解它们的思想,你会非常有成就感,会不禁感叹这些人的智慧!

04  核心代码展示

Linux内核中的代码我就不带你看了,如果你看懂了我前面讲的这些,想自己去研究,Linux5.4中的线程切换调用链:schedule->__schedule->context_switch->switch_to

关注公众号【硬核子牙】回复【线程切换代码】即可免费领取我写的操作系统与线程切换相关的代码揭秘Linux内核线程切换底层实现_第11张图片

通过阅读本篇文章,相信大家能意识到一个问题:如果你想真正看得懂Linux内核的源码,手写一个x64多核OS是一个必备的前提,不自己写一个,是永远不可能真正玩明白Linux内核的,因为你没有办法站在一个设计者的角度去思考问题…

就像本篇文章分享的Linux内核线程切换底层实现,如果你自己没有写过一次,你对此,其实是一点概念都没有的。其实就算你看了我从实战的角度做了分享,你自己不去写一遍,离真正的透彻理解,还是存在一定距离的……

题外话

我们的实战Linux内核二期开始招生了,当前预售价,仅50个名额。课程共十二大专题,三个月时间,带你手写x86单核os、手写x64多核os打底,再带你以手写Linux驱动的方式实战Linux内核。只有这样学,才能真正学会Linux内核,才能做实验,而不是只是停留在原理层面,无法论证…

为了保证课程质量,课程所有内容由子牙老师亲授!当今科技世界用的操作系统,除了华为、苹果、微软用的是自家的,其他几乎全是基于Linux内核的操作系统!可以这样说,Linux是今天这个科技世界的地基!玩转Linux内核,你就等于玩转了这个科技世界的核心技术:底层操作系统。那你的未来只能用“海阔凭鱼跃,天高任鸟飞”来形容

如果你对我们课程感兴趣,欢迎关注下面公众号了解详情。

你可能感兴趣的:(汇编,c语言,linux)