『阿男的Linux内核世界』*13 从User Space到Kernel Space(一)*
Linux操作系统在设计上分为两部分,分别是User Space和Kernel Space。我们在这篇文章里讲解一下,一个Process是如何在User Space与Kernel Space之间切换运行的。
首先我们得明白一点,就是一个Process在什么时候会在User Space运行,什么时候会在Kernel Space运行?
其实我们之前几篇文章已经学习了好多关于Process的只是,大家应该已经对Process有了一个直观的认识:Process就是正在运行的程序。既然是正在运行的程序,那么Process具有以下特征:
- 代码被读入到内存之中
- 使用内存保存所需数据
- 使用CPU执行指令
- 和输入输出设备打交道
此外,每一个Process是运行在Kernel提供的虚拟资源之上的:
- Process使用的内存空间是Kernel提供的统一模型的虚拟内存空间
- Process并不能访问所有的CPU寄存器
- Process不可以执行一些CPU只允许Kernel执行的汇编指令
- Process不可以直接和输入输出设备打交道
以上几条,阿男为大家逐一说明一下。
首先,我们使用Intel架构的CPU为例说明,因为大家基本上都使用Intel架构的CPU,可能很少人会使用RISC指令集架构的芯片,比如SPARC。此外,我们使用Intel架构的CPU说明,很多设计上面的思想在其它架构的CPU上也适用,只是细节的不同。我们的重点还是放在讲设计思想上面。
要知道,CPU有很多寄存器是不开放给User Space使用的,比如IDTR
寄存器,,也就是保存Interrupt Descriptor Table
起始地址的寄存器。类似这种寄存器是不会开放给User Space使用的。至于什么是Interrupt Descriptor Table
,什么是IDTR
寄存器,阿男后续会给大家讲,总之现在我们要知道,User Space不能访问所有的CPU寄存器就可以了。
其次,CPU还有很多指令是不开放给User Space使用的。所有的程序,不管你用什么语言写,运行在什么操作系统之下,最终都是转化为最底层的机器码。机器码因为都是1010
这样的二进制代码,不好阅读,所以和它一一映射的就是汇编代码。我们所讲的CPU所执行的指令就是指汇编代码。
而汇编代码就是由CPU的指令集所组成。CPU有很多基本的内存操作和运算相关的指令,比如MOV
,ADD
这些。像这些指令,User Space是可以执行的,并没有问题。
但是还有很多和硬件设备以及真正的物理内存打交道的指令,是只有Kernel才有权运行的,比如LGDT
,LLDT
这些掌管CPU与Kernel相关的特定寄存器的指令,User Space是无权执行的。就算你直接写汇编代码,里面使用这些高权限指令,编译完成后运行,你会发现CPU会抛出异常给Kernel,然后Kernel就会把异常直接报错给你。
因此要明白,CPU的这种权限管理是硬件级别的。在Intel的CPU架构下,这些不同的运行模式叫做Ring^1:
如上图所示,CPU有不同的运行模式,就是不同的Ring,每一层Ring所拥有的权限不一样,所能访问的寄存器和执行的指令集也不同。
因此当计算机开机启动以后,Kernel从BIOS手里接管过来CPU的时候,就是接管了最高权限Ring 0
,后续User Space在Kernel的管理下,会运行在Ring 3
,这个对于Windows NT内核也是一样的,现代的操作系统基本上都会使用CPU提供的这种硬件级别的安全保护功能。可以看到,现代操作系统和CPU之间的联系早已经非常紧密。