进入内核态究竟是什么意思?( 二 )


上面这一部分有一些抽象了 , 简单来说 , 操作系统在内存中圈了一块地 , 把自己的代码放在这块地中 , 并设置了访问权限:闲人免进 , 非Ring0权限禁止入内 。
随后 , 操作系统又把自己圈的这块地映射到了每一个进程的虚拟地址空间中 , 这样一来 , 所有进程抬头一看自己的进程地址空间 , 都会看到它了:好家伙 , 这一块区域被操作系统占了 , 咱也不敢写 , 咱也不敢看 。
操作系统圈的这块地 , 就是内核地址空间!一般位于进程地址空间中较高的区域 , 以32位下Windows为例 , 它是在0x80000000~0xFFFFFFFF这个区域 。
我们把位于这个空间中的代码叫做操作系统的内核代码 , 有时候也简称内核 。 而把应用程序代码所活动的区域叫做用户地址空间 。
进一步 , 我们常把CPU执行内核代码的模式称为内核态 , 把执行用户程序时的模式称为用户态 。
CPU执行代码的过程 , 就是不断游走于用户态和内核态的过程 。
进入和离开
现在还有最后一个问题:内核态的进入和离开怎么实现?
假如没有任何约束 , 那普通应用程序 , 不是随便执行一条jmp指令就能跳进内核地址空间执行了?
应用程序可以随便进进出出 , 高兴了就来一个内核一日游 , 那还不天下大乱了?
况且话说回来 , 内核所在的内存空间因为权限保护 , 应用程序也是没办法jmp过去的 , 前面不说了吗:闲人免进!
那怎么办呢?
CPU提供了专门的入口 , 用来从用户态进入内核态 。
这几个入口是:
1、中断:
当硬件设备有消息来了之后 , 会通过中断通知CPU , 比如你移动了鼠标 , 敲下了键盘 , 收到了一个数据包 , 收到了时钟的滴答声···
当中断发生时 , CPU会将当前执行的上下文保存到栈中 , 转入内核执行中断处理程序 。
通过中断进入内核 , 入口是记录在中断描述符表IDT中的 , 由操作系统在系统启动的时候就安排好了 。
2、异常:
当CPU执行过程中发现一些异常情况 , 比如执行除法指令的除数是0 , 访问的内存地址无效 , 或者访问的内存地址属于特权页面等这些情况 , CPU都会触发异常 。
异常和中断的流程有一些类似 , 遇到异常时 , CPU也会将执行的上下文保存在栈中 , 转入内核执行中断处理程序 。
通过异常进入内核的入口和中断一样 , 也是记录在IDT中的 , 同样是操作系统在系统启动的时候就安排好了 。
3、系统调用:
在系统编程中 , 我们经常会调用很多操作系统提供的API函数 , 比如文件操作、内存操作、网络操作等等 , 这些函数都是操作系统封装出来的应用程序编程API , 只是一个接口 , 真正的底层实现是位于内核中的系统调用函数 。
应用层上的API通过CPU专门的指令(如sysenter/syscall)进入内核来完成对应的功能 , 进入内核后的入口同样也是操作系统提前安排好了的 。
总结
最后 , 让我们回答最开始知乎的那个问题:进入内核态究竟是什么意思?
CPU为了进行权限管控 , 引入了特权级的概念 , CPU工作在不同的特权级下能够执行的指令和能够访问的内存区域是不一样的 。
计算机在启动之初 , CPU运行在高特权级下 , 操作系统率先获得了执行权限 , 在内存中圈了一块地 , 将自己的程序代码放了进去 , 并设定了这一部分内存只有高特权级才能访问 。
随后 , 操作系统在创建进程的时候 , 都会把自己所在的这块内存区域映射到每一个进程地址空间中 , 这样所有进程都能看到自己的进程空间中 , 有一块叫“内核”的区域 , 这一块区域是无法擅入的 。