内核态和用户态解析

内核态和用户态解析

首先需要明确用户态和内核态分别是什么,然后再去想为什么需要有这两个事物。

CPU 指令集权限

在说用户态和内核态之前,有必要说一下 CPU 指令集。指令集是值 CPU 实现软件指挥硬件执行的媒介,具体来说就是每一条汇编语句都对应了一条 CPU 指令,非常多的 CPU 指令组合在一起,就是一个或者多个指令的集合,这就是指令集。

存在指令集以后我们可以直接通过 CPU 指令去操作硬件了,但是可能出现指令操作不规范或者一些其他原因出现指令错误直接导致整个机器都崩溃。这是因为能直接操作 CPU 指令集这个事情的权限太大了,所以需要针对程序员开发人员去收回大部分权限,在 CPU 指令集之上再次抽象封装一层,既可以隐藏CPU 指令集,又可以让程序开发者关注逻辑业务的实现,而不需要去关注底层指令的执行。

针对上边的情况,硬件设备商直接提供硬件级别的支持,对 CPU 指令设置了权限,不同的权限可以使用不同的 CPU 指令集,以 intel 为例,CPU指令集操作的权限被分为四层:

  • ring 0
  • ring 1
  • ring 2
  • ring 3

其中 ring 0 权限最高,可以使用所有的 CPU 指令集去操作所有硬件设备。ring 3权限最低,仅能使用常规的 CPU 指令集,不能使用操作硬件资源的指令集,例如网卡访问,申请内存,硬盘访问等。

注意:Linux 仅仅采用 ring 0 和 ring 3这两个权限级别

内核态和用户态

  • 内核态

ring 0 就是内核态,完全在操作系统中运行。执行内核空间的代码,拥有 ring 0 的保护级别,拥有对硬件资源的全部操作权限,可以执行全部的 CPU指令集,访问任何地址的内存。在内核模式下运行的任何异常都是灾难级别的,将会导致整台机器崩溃。

  • 用户态

ring 3 就是用户态,在用户模式下,拥有 ring 3 的保护级别。代码美欧对硬件的直接控制权限,也不能直接访问地址的内存,需要调用系统接口来达到访问硬件和内存的目的。在这种保护模式下,即使程序发生崩溃也是可以恢复的,大部分电脑都是在用户模式下运行的。

  • 两者区别

用户态和内核态概念其实就是 CPU 指令集权限的区别,进程中需要读写 IO,必然会涉及到 ring 0 级别的指令集,但此时只有 ring 3 级别,所以为了能够操作 ring 0 级别的指令集,CPU 需要切换指令集操作权限到 ring 0 级别,然后再执行相应的 ring 0 级别的 CPU 指令集(内核代码),执行的内核代码会使用当前进程的内核栈。

每个进程都有两个栈,分别是内核栈和用户栈,对用内核态和用户态使用。

  • 内存空间使用

image-20220127144738388

用户态:只能操作 0-3G 范围的地位虚拟空间地址

内核态:0-4G 范围的虚拟空间地址都可以操作,尤其是 3-4G 的高位虚拟空间地址只能由内核态去操作

3-4G 的高位虚拟空间地址是共享的,所有的进程的内核态逻辑地址都是同一块内存地址,这里存放整个内核的代码和所有的内核模块,以及内核维护的数据。

小结

通过指令集权限区分内核态和用户态,还限制了内存资源的使用,操作系统为内核态和用户态划分了两块内存空间,让那对应的指令集使用。

内核态和用户态切换

内核态和用户态切换的需要做什么?

  • 保留用户态现场(上下文、寄存器、用户栈等)
  • 复制用户态参数,用户栈切换到内核栈,进入内核态
  • 额外的检查(内核代码对于用户不信任)
  • 执行内核态代码
  • 复制内核态代码执行结果,返回用户态
  • 恢复用户态现场(上下文、寄存器、用户栈等)

一图说明一切

image-20220127145658628

什么时候切换?

  1. 系统调用:用户态进程主动切换到内核态的方式,用户态进程通过系统调用向操作系统申请资源完成工作,例如 fork() 就是一个创建新进程的系统调用,系统调用的机制核心使用了操作系统为用户特别开放的一个中断来实现,如 Linux 的 int 80h 中断,也可以称为软中断
  2. 异常:当 CPU 在执行用户态的进程时,发生了一些没有预知的异常,这时当前运行进程会切换到处理此异常的内核相关进程中,也就是切换到了内核态,如缺页异常
  3. 中断:当 CPU 在执行用户态的进程时,外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令,转到与中断信号对应的处理程序去执行,也就是切换到了内核态。如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后边的操作等。

为什么开销大?

其实就是来回复制数据,另外保留恢复现场需要的时间和资源比较多!

用户态线程

用户态线程也叫做用户级线程,操作系统不知道它的存在,它完全是在用户空间中创建的。

用户态线程优势:

  • 管理开销小:创建、销毁不需要系统调用
  • 切换成本低:用户空间程序可以自己维护,不需要走操作系统调度

用户态线程劣势:

  • 与内核协作成本高:线程完全在用户空间程序管理,一旦需要进行 I/O 操作的时候无法利用到内核的优势,需要频繁的进行用户态到内核态的切换
  • 线程间协作成本高:例如两个线程进行通信的时候,需要进行 I/O,而 I/O需要进行系统调用,所以需要负担系统调用的资源消耗
  • 无法利用多核优势:操作系统进行调度的时候始终是调度线程所属的进程,所以不论每一个进程有多少用户态的线程,都只能被并发调用一个线程执行
  • 操作系统无法针对线程调度进行优化:当一个进程的一个用户态线程阻塞后,操作系统无法及时发现和处理阻塞问题,不会调度其他的线程去执行

内核态线程

内核态线程也叫做内核级线程,在内核态中执行,通过系统调用可以创建一个内核态线程

内核态线程优势:

  • 可以利用多核 CPU:内核由操作系统调度,可以在多个 CPU 核上执行
  • 操作系统级优化:内核中的线程操作 I/O 不需要进行系统调用,一个线程阻塞了,可以立刻调度另外一个线程执行

内核态线程劣势:

  • 创建成本高:创建的时候需要进行系统调用,也就是切换到内核态
  • 扩展性差:一个内核程序管理,数量不多
  • 切换成本高:切换的时候在内核中操作,需要切换到内核态