用户态和内核态的理解和区别
1、linux 进程有 4GB 地址空间,如图所示:
3G-4G 大部分是共享的,是内核态的地址空间。这里存放整个内核的代码和所有的内核模块以及内核所维护的数据。
2、特权级的概念:
对于任何操作系统来说,创建一个进程是核心功能。创建进程要做很多工作,会消耗很多物理资源。比如分配物理内存,父子进程拷贝信息,拷贝设置页目录页表等等,这些工作得由特定的进程去做,所以就有了特权级别的概念。最关键的工作必须交给特权级最高的进程去执行,这样可以做到集中管理,减少有限资源的访问和使用冲突。inter x86 架构的 cpu 一共有四个级别,0-3 级,0 级特权级最高,3 级特权级最低。
3、用户态和内核态的概念:
当一个进程在执行用户自己的代码时处于用户运行态(用户态),此时特权级最低,为 3 级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。Ring3 状态不能访问 Ring0 的地址空间,包括代码和数据;当一个进程因为系统调用陷入内核代码中执行时处于内核运行态(内核态),此时特权级最高,为 0 级。执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈。
用户运行一个程序,该程序创建的进程开始时运行自己的代码,处于用户态。如果要执行文件操作、网络数据发送等操作必须通过 write、send 等系统调用,这些系统调用会调用内核的代码。进程会切换到 Ring0,然后进入 3G-4G 中的内核地址空间去执行内核代码来完成相应的操作。内核态的进程执行完后又会切换到 Ring3,回到用户态。这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。这说的保护模式是指通过内存页表操作等机制,保证进程间的地址空间不会互相冲突,一个进程的操作不会修改另一个进程地址空间中的数据。
4、用户态和内核态的切换
当在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成一些用户态自己没有特权和能力完成的操作时就会切换到内核态。
用户态切换到内核态的 3 种方式
(1)系统调用
这是用户态进程主动要求切换到内核态的一种方式。用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。例如 fork()就是执行了一个创建新进程的系统调用。系统调用的机制和新是使用了操作系统为用户特别开放的一个中断来实现,如 Linux 的 int 80h 中断。
(2)异常
当 cpu 在执行运行在用户态下的程序时,发生了一些没有预知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关进程中,也就是切换到了内核态,如缺页异常。
(3)外围设备的中断
当外围设备完成用户请求的操作后,会向 CPU 发出相应的中断信号,这时 CPU 会暂停执行下一条即将要执行的指令而转到与中断信号对应的处理程序去执行,如果前面执行的指令时用户态下的程序,那么转换的过程自然就会是 由用户态到内核态的切换。如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后边的操作等。
这三种方式是系统在运行时由用户态切换到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。从触发方式上看,切换方式都不一样,但从最终实际完成由用户态到内核态的切换操作来看,步骤有事一样的,都相当于执行了一个中断响应的过程。系统调用实际上最终是中断机制实现的,而异常和中断的处理机制基本一致。
5、用户态到内核态具体的切换步骤:
(1)从当前进程的描述符中提取其内核栈的 ss0 及 esp0 信息。
关于内核栈和用户栈,可以看这篇文章:https://www.cnblogs.com/shengge/archive/2011/08/29/2158748.html.
内核在创建进程的时候,在创建task_struct的同事,会为进程创建相应的堆栈。每个进程会有两个栈,一个用户栈,存在于用户空间,一个内核栈,存在于内核空间。当进程在用户空间运行时,cpu堆栈指针寄存器里面的内容是用户堆栈地址,使用用户栈;当进程在内核空间时,cpu堆栈指针寄存器里面的内容是内核栈空间地址,使用内核栈。
(2)使用 ss0 和 esp0 指向的内核栈将当前进程的 cs,eip,eflags,ss,esp 信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。
(3)将先前由中断向量检索得到的中断处理程序的 cs,eip 信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。
- 资源访问
- 用户态无法访问内核态的资源,这防止了黑客通过应用程序攻击操作系统
- 内核态可以通过一定的方式访问用户态的资源