这样才能玩转Linux内核之CPU篇原创
哈喽,我是子牙,一个很卷的硬核男人
深入研究计算机底层、Windows内核、Linux内核、Hotspot源码……聚焦做那些大家想学没地方学的课程。为了保证课程质量及教学效果,一年磨一剑,三年先后做了三个课程:手写JVM、手写OS及带你用纯汇编写OS、手写64位多核OS及Linux内核…
上篇文章给大家分享了:Linux内核这样学才能学会之内存篇,收到了很多小伙伴的反馈,表示自己研究Linux内核很迷茫,很多代码看不懂,不知道是干啥的,需要这样的文章指明方向。今天带来的是它的兄弟篇:CPU篇
01研究CPU的正确姿势
关于CPU,其实大家研究得很少,无非是:线程开销大,开销源于用户态切内核态、研究并发的时候,会研究到CPU的缓存一致性协议MESI与MOESI、有些感兴趣的小伙伴会去研究CPU的执行引擎与流水线、还有一些无法证明的概念:分支预测、乱序执行、happens-before、as-if-serial……
研究这些东西跟看懂Linux内核源码有关系吗?有,但是这些东西都是任务切换与线程并发控制那块的,所以研究明白了也只能看懂多线程及并发控制相关的代码,Linux内核启动代码、初始化CPU核及激活CPU核的代码还是看不懂。
而且因为这些都是发生在CPU内部,不好做实验证明,所以小伙伴们在研究的时候,其实也不确定自己是否真的懂了。如何证明呢?手写一个OS,无其他办法
对于一个OS来说,CPU就这么点东西吗?显然不是。要知道,CPU是大脑版的存在。可以这样说,OS是面向CPU提供的机制实现出来的
那CPU还有哪些东西需要研究呢?如果是研究多线程,就是研究上面说的这些。但是孤立的研究上面这些,其实也看不懂,因为这些是CPU的后半段,前半段包括:
-
CPU的运行模式
-
CPU核的初始化
-
CPU核的激活
-
CPU核的亲和性设置
-
CPU的特权级
就是说,你把上面这些研究明白了,Linux内核的启动代码、初始化CPU核的代码、设置CPU亲和的代码、激活CPU核抢占任务的代码,你才能看懂。有了这些CPU前置知识,多线程及并发控制相关的代码,才能真正意义上的理解。以及CPU读写数据、执行代码背后做了哪些事情,你才能如数家珍一样将它讲出来
接下来,follow me,听我娓娓道来~
02CPU的运行模式
我们打开电脑,到看到可以交互的图形界面,其实这个过程中,OS代码经历了CPU的多种运行模式切换。如果是32位CPU,OS只经历了由实模式切入保护模式。如果是64位CPU,就复杂很多:先由实模式切入保护模式,再由保护模式切入IA32-e兼容模式,再进入正统的64位长模式。即经历了3次CPU模式切换
关于CPU的运行模式,如果你想详细了解,请看下图。系统管理模式为什么没讲?因为我们正常研究Linux内核机制,看不到这玩意,感兴趣的小伙伴可以自行chatgpt
不管是32位CPU,还是64位CPU,起始运行模式都是实模式。区别是,如果是32位CPU,OS代码中只需要做到让CPU由实模式进入保护模式就可以了。说白了就是只需要进行一次CPU模式的切到,代码类似于
如果是64位CPU,经历了三次CPU运行模式切换。由实模式切入保护模式的代码跟上面一样,由保护模式进入IA32-e模式,代码类似于:
由IA32-e兼容模式进入长模式,写法有两种,我都列出来,方便你到时看Linux内核源码能看懂
所以想玩明白64位CPU,就得先玩明白32位CPU,因为由于历史原因,64位CPU的很多机制不是凭空产生的,而是在32位CPU的基础上发展来的。不了解古今,何以只当下
如果是多核CPU,上面的代码其实只是让一个CPU核进入了长模式,其他CPU核还处于实模式,需要BSP(Bootstrap Processor)核给所有AP(Application Processor)核发送IPI(Inter-Processor Interrupt)信号,让这些核去执行初始化操作,进入长模式,代码类似于
所以你看,如果你不了解这个知识点,那Linux内核的启动代码你就看不懂。启动代码看不懂,就不知道Linux内核在启动的时候做了哪些初始化操作,那后面各大模块的代码要么看不懂,要么读起来非常吃力,前后没办法关联起来理解
03用户态切内核态
上面说的是CPU的运行模式,下面再谈谈CPU的特权级。CPU运行模式相信你看了上一啪应该已经基本了解了吧(瓦特?还不了解!那我就打个比方,实模式相当于你刚出生,保护模式相当于你长大了,IA32-e模式相当于你有两把刷子了,长模式相当于你成熟了),那如何理解CPU的特权级呢?
相信你肯定听过用户态、内核态,这个就是由CPU的特权级引申出来的。如图,CPU被设计成四个特权级:r0、r1、r2、r3,数字越大,权限越小。我们都知道OS内核需要有最高权限,所以运行在CPU的0特权级。r1、r2没用。我们的用户程序运行在CPU的3特权级
所以你可以这样理解,CPU的0特权级,就是我们常说的内核态。CPU的3特权级,就是我们常说的用户态。我们常说的用户态切内核态,其实说的就是编码实现CPU由3特权级切入0特权级。当在内核态将任务执行完,这时候一般说回到用户态,而不会说内核态切用户态
那关于态的切换,CPU提供了哪些方式呢?三种:
-
CPU的门机制:中断门、调用门、任务门、陷阱门
-
x86架构下的sysenter与sysexit
-
x64架构下的syscall与sysret
这个就不展开讲了,感兴趣的自行研究。这些东西研究明白,用户态切内核态相关的代码、系统调用相关的代码、中断处理相关的代码就可以看懂了
上面说的是主动触发由用户态切内核态,其实还有被动的,比如:
-
软中断如除零异常、调试中断、内存错误…
-
硬中断如键盘鼠标触发、读写硬盘上的数据、网卡有数据包到来…
那自实现OS的时候,代码层面是如何体现CPU特权级的呢?是由CPU的段机制控制的
与DPL关联的,还有两个名词:CPL、RPL。DPL设置的是访问内核段的条件,得有东西和它比较,就是CPL,那CPL从哪来?取值自RPL。这块你可能听不懂了。所以其实研究Linux内核,最难的是研究CPU,因为讲CPU的书几乎没有,大家都是啃Intel手册啃出来的
04CPU如何读写数据
接下来我通过一个案例,把前面讲的所有知识点串起来,让大家能够更清晰的了解CPU是如何利用这些机制运行的。考虑到大家的基础,以32位CPU为前提吧。
当我们按下开机键,这时候CPU处于实模式下运行。我们自实现OS的时候,这时候要做的事情就是准备好让CPU进入保护模式需要的所有数据。我们自实现OS如此,Linux内核、Windows内核,乃至所有运行在Intel CPU上的OS,都需要这样做。这就是学底层一通百通的本质所在
进入保护模式以后,我们需要设置页表,开启虚拟内存分页模式。这时候的CPU的环境是:开启虚拟内存分页模式下的保护模式。这时候在读写数据的时候,CPU中的段部件、页部件需要参与进来
再详细点,当我们代码中有读写内存的代码时,无论你是什么语言,最终一层一层向下编译,到CPU层面,就是这样。其实CPU执行的是机器码,这里为了让大家能看懂,我用的是汇编
代码的意思就是将虚拟地址为0x1100处的内存中的数据读入寄存器eax。接下来我展开讲讲这个过程
当我们让CPU读数据的时候,CPU会这样一步步实现:
-
读数据操作的是数据段,所以CPU会从cs段寄存器中取到段选择子,从段选择子中解析出RPL、数据段描述符在GDT表中的索引。这个时候的RPL就是CPL,即CPU的当前请求特权级
-
拿着索引去GDT中拿到数据段描述符,进行一系列的验证,这个验证我就不展开了,我们这里假设验证通过,就可以计算出线性地址=base+0x1100。到这里,段部件的工作就结束了
-
CPU拿到线性地址,需要读cr3寄存器,拿到当前进程的页表地址,一层一层解析,最后计算出真实的物理地址。这中间其实还有很多小部件参与工作,比如高速缓存器、TLB
-
当CPU拿到物理地址,就可以去读内存了,然后写入CPU内部的eax寄存器
这个过程中有很多新名词,给大家准备好了
看到这里,你是什么样的心情呢?这个人怎么这么牛哇(玩笑玩笑)!我怎么这么菜呀(太真实了有木有)!我也想有这样的技术实力!送给你卖油翁的:无他,唯手熟尔。需要学习哪些东西我已经告诉你了,学习的节奏通过这个案例你应该也能看出来,自己找资料学习吧。
题外话
你好,我是子牙。十余年技术生涯,一路披荆斩棘从小白到技术总监到大厂中间件到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核及特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。不考虑交个朋友吗?关注硬核子牙: