“各位小伙伴,我是小笨叔。笨叔尽量每天给大家分享一点点小东西,可能是笨笨的、傻傻的、甜甜的、酸酸的小点滴,记录这每一刻每一天的小感悟,就像小雨点一样,它会缓缓会合到大江大海!”
上次提到 LinuxCon 大会,这种大会正如笨叔说的
“听君一席话,胜读十年书”
笨叔有幸在北京访问了 Linux 社区外面一位老前辈,凝听他当年在 80~90 年代做 CPU 处理器体系结构和 Linux 操作系统的鲜为人知的历史和对人生和技术的感悟,让笨叔大受启发。
在闲聊中,他提到为什么 ARM32 的处理器须要 7 种处理器模式,而且每一种处理器模式都须要一个独自的栈空间?比方 irq 模式,为什么 ARM32 的设计里须要一个独自的 irq 模式,而且这个 irq 模式只有 12 个字节?为什么不和 SVC 模式专用一个栈呢?比方 x86 等传统经典的处理器架构里,是没有为 irq 独自开拓一个栈的。
这些是十分好的问题,引发了笨叔回到上海持续思考。本文抛砖引玉,心愿有趣味的小伙伴能够在文章前面留言发表您的观点。
01 ARM32 上的设计
—
咱们来看一下 ARM32 上,如果产生了一个 irq 中断,ARM32 处理器是怎么解决的?咱们晓得 ARM32 外面有一个奇葩的 7 个工作模式:
如上图所示,在 ARMV6 之前,ARM 的处理器模式有 7 个。失常的内核是跑在 SVC 模式的,用户态 app 是跑在 User 模式,而其余几个就是咱们常说的 IRQ 中断,FIQ 中断和几个异样模式。如果比照 x86 的话,x86 只有 ring0 ~ ring3 这 4 个特权级别,内核跑着 ring 0 里,而用户态跑在 ring 3 里,而没有辨别 SVC 模式,IRQ 模式等。
咱们在看看 ARMv7 上改良。
抛开 secure 模式不谈,在 non-secure 即 normal world 来说,ARMv7 在持续沿用 7 个模式的根底上又辨别了 PL0 模式和 PL1 和 PL2 模式,PL 是 privilege level 的意思。简略来说:
PL0 模式:用户模式,等同于以前的 USER 模式
PL1 操作系统模式:蕴含以前的 SVC 模式,IRQ 模式,FIQ 模式
PL2 虚拟化模式:新增的模式,有点相似 x86 上虚拟化扩大的 root 模式
如果说 ARM32 的设计的正当的话,咱们看看 ARM v8 里的设计。
ARMv8 外面曾经齐全摈弃 ARMv7 之前的做法了,连名字都该了,当初叫做 EL,也就是 exception level,而且每一个 EL 级别的异样也变了。其中
名字改了之后,每个 EL 级别管辖的范畴和权力就不一样了,是不是很像 x86 外面的 ring0~ring3 呢,谁叫你 x86 在 PC 和服务器上这么火呢?所以,x86 上的长处,ARM 当然要好好学习啦。
除了这个之外,还齐全摈弃了 SVC, IRQ, FIQ 等等那 7 个模式了,这和 ARMv7 又有很多不同了。当初 ARMv8 上,异样分成两种:
同步异样:比方 MMU 的一些拜访权限问题
异步异样:这个就大家常见的 IRQ, FIQ, ERR
而且还有一点,ARMv8 外面,不再为每个异样类型设置一个专门的栈,比方 IRQ 曾经没有独自的栈了,当初的栈,只有每个 EL 才有栈。
02 ARM32 中断栈
—
ARM32 产生中断之后,IRQ 栈和 SVC 栈的状况如下:
上述这个图就是 ARM32 在 Linux 内核中,产生 IRQ 中,栈的变动状况,大家能够看《奔跑吧 Linux 内核》第 621~626 页。总的来说,IRQ 模式上面的栈值保留了产生中断那个现场的 SPSR, LR, SP_IRQ 三个寄存器的内容。而后切换模式到 SVC 模式,进一步保留通用寄存器到 SVC 的栈空间里。所以在 ARM32 里,中断产生之后,其实是应用了两个模式的栈,一个是 IRQ 的栈,另外一个 SVC 模式的栈,这个 SVC 的栈,在 Linux 内核里其实就是内核栈。
那在 X86 里,是怎么样的呢?x86 处理器里有一个 TSS(Task State Segment),当中断产生时,用户过程或者处于用户态(特权级 3)或者处于内核态 (特权级 0),如果是在用户态,那么会产生栈的切换问题,也就是会切换到内核态的栈,如果是在内核态,那么就没有栈切换的问题。然而 x86 处理器在特权级 0 上只有一个 ESP, 这意味着中断产生后,只能应用一个栈,这个栈就是内核栈(kernel stack)。处理器的硬件逻辑会将被中断过程的下条指令(CS,EIP) 以及 EFLAG 压入栈,当然如果产生用户态栈向内核态栈的切换,处理器还会把用户态的 (SS, ESP) 也压入栈,此时应用的就是内核栈。这个行为属于处理器的硬件逻辑领域,不是系统软件的行为。
另外一方面,当初 Linux 内核在 x86 或者其余体系结构里,曾经反对内核栈和中断栈拆散的办法了。当然,这里说的中断栈不是本文说的那个 CPU 外部的中断栈,而是和内核栈相似的一个栈,也就是在中断处理程序中,和 SVC 模式应用的内核栈拆散。这是内核设计的问题了,总之,中断栈可与内核栈共享,也可重新分配一个独立的中断栈。然而负面因素是中断栈如果产生嵌套,可能毁坏内核栈的一些数据,因为毕竟共享,所以栈空间有时候难免会顾此失彼。所以在 x86 的 Linux 中,总是应用拆散的内核栈设计。零碎中每个过程都会领有属于本人的内核栈,而零碎中每个 CPU 都将为中断解决筹备了两个独立的中断栈,别离是 hardirq 栈和 softirq 栈。留神,这是 OS 的设计问题,不是明天咱们想探讨的 CPU 硬件设计上的中断栈拆散。
上面是 Linux 内核外面 x86 架构应用拆散的中断栈的补丁。
https://git.kernel.org/pub/sc…
那为啥子 ARM32 要为 IRQ 模式设置独自一个 12 字节的栈呢,而不是复用 SVC 模式的栈呢?
笨叔翻阅了 ARM 相干的技术文档也没找到答案,我猜想可能是因为一个重要的起因是 ARM32 这个架构设计的时候是 90 年代,那个年代的 ARM 处理器是相当慢的,独自一个 IRQ 栈能够进步中断的响应速度,特地是晚期的 ARM 处理器次要利用场景是嵌入式零碎,反对中断嵌套的形式。从下面的 x86 的拆散的中断栈的 patch 来看,拆散的中断栈确实有不少劣势,至多能够保障不会导致 SVC 模式的栈产生 overflow。那到底是 ARM32 这种设计先进呢还是 ARMv8 和 x86 的设计理念先进呢,是提高还是倒退呢?我有点 confuse 了!
笨叔抛砖引玉,心愿感兴趣的同学能够在前面留言,大家一起探讨。