结合内核态和线程结构谈谈 --- 为什么线程切换比进程快?

前言

​ 看过之前两篇文章,应该都大致了解了两件事情。

  • 在Linux系统里面,进程线程几乎没有区别,没有为线程设置额外的调度算法,数据结构

  • 进程/线程调度其实经历这几个阶段

    • 进程A —> 陷入到内核态,内核进程A

    • 内核进程A —> 进程调度器 —> 内核进程B

    • 内核进程B —> 进程B

    • 既然如此必然经过页表,跟栈的切换

​ 之后,我思考一个问题,为什么说线程切换的效率要比进程低呢?线程切换的时候不也要走内核空间吗?进入内核空间就需要切换页表栈,一样有开销。

​ 而且线程也没有特殊的调度算法和数据结构。带着这个问题我继续深入研究….

切换的开销

​ 我们知道,进程是资源分配的单位,不同进程之间,很显然它的内存空间和栈空间是隔离的,进程A一般不能访问进程B的地址空间。

​ 线程,作为一种轻量级进程,同一进程下的线程起码是共享地址空间的。

​ 所以一般来说,进程切换需要切换地址空间也就是(page table),线程之间的切换则不需要page table。

​ 这是这个问题的一般答案。但是有没有考虑过一个问题。(1)线程跟进程的结构很相似,没有区别也没有特别的调度算法 (2)也就是说,即使是线程,调度的时候也要进入内核态。但是内核态程序是有自己的页表的。

​ 因此,不管怎么样,即使是同一进程下的线程切换,仍旧需要进入到内核态,切换到内核程序的页表。这样,似乎上面的答案就不成立了?

​ 因此,我仔细阅读了,陷入内核空间的代码。

切换的过程

​ 具体过程就不说了,以时间片到期为例。

  • 内核的timer到期后,产生一个中断,发起一个进程调度
  • 进程A —> 陷入到内核态,内核进程A
  • 内核进程A —> 进程调度器 —> 内核进程B
  • 内核进程B —> 进程B

​ 这就是进程之间的调度的过程。那么我们就看看,陷入内核的时候发生了什么事情?

  • 系统调用 - ecall
  • usersevc-trampoline page
  • usertrap

ecall究竟做了什么

​ 再次仔细阅读ecall的源码,发现ecall其实就做了这几件事情

1
2
3
1.把mode位 切换为 supervisor mode
2.保存当前PC的值到 SEPC
3.取出SPEVC trap指令的第一条放入PC

​ 所以,ecall执行的东西并不多。并且这样就算已经陷入到内核态了,因为能够执行内核指令。但是此时页表和栈仍然没有切换。

​ 为什么ecall这样设计?参考上一篇文章,这样设计是为了ecall的效率。

因此,进行线程调度的时候,进入内核态的过程应该是不会切换内存的。