系统调用与用户态陷入内核态

简介: 我们都知道操作系统中运行着很多的进程,如果普通的进程可以直接操作硬件那么这个系统的安全性没办法保障,所以操作系统分出了两种状态,一种状态是运行的代码可以操作硬件;一种状态不能操作硬件,只能切换到第一种状态去操作后再切换回来,这就是内核态和用户态。

我们都知道操作系统中运行着很多的进程,如果普通的进程可以直接操作硬件那么这个系统的安全性没办法保障,所以操作系统分出了两种状态,一种状态是运行的代码可以操作硬件;一种状态不能操作硬件,只能切换到第一种状态去操作后再切换回来,这就是内核态和用户态。

在用户态下,进程只能访问自己的虚拟地址空间和一些受限的资源,不能直接访问系统硬件设备或其他进程的地址空间;而在内核态下,进程可以访问系统的全部资源,包括硬件设备和其他进程的地址空间。内核态比用户态拥有更高的权限和更多的系统资源。当进程需要访问系统资源时,比如进行文件读写、创建新进程等操作,就需要通过系统调用的方式从用户态切换到内核态来执行相应的操作。在进程执行系统调用之前,它的运行状态处于用户态;而在系统调用执行期间,进程的运行状态会从用户态切换到内核态;系统调用执行完毕后,进程又会从内核态切换回用户态。这种用户态和内核态的切换是操作系统实现进程间资源隔离和保护的重要手段。

1、特权级别位

为了保护操作系统和应用程序的安全性,将CPU指令划分为不同的权限级别,并对不同的指令权限级别进行不同的访问限制。一般来说,CPU指令权限划分可以分为四个级别,分别是内核态、用户态、超级用户态和虚拟机态,CPU会为每个指令设置相应的权限级别。当一个程序运行时CPU会检查程序所使用的指令是否有足够的权限级别来执行,如果权限级别不够就会抛出异常,这个异常会被操作系统捕获,并转交给内核进行处理,内核会决定代表程序执行受限的操作或终止程序。


CPU通过特权级别位来区分当前程序是运行在用户态还是内核态,只有运行在内核态时才有足够的权限执行内核态指令。


特权级别位存储在CPU的控制寄存器中,具体来说,是存储在处理器状态字(Processor Status Word,PSW)或类似的寄存器中。在x86架构的CPU中,PSW包含了多个标志位,其中包括特权级别位。特权级别位通常称为处理器状态字的"特权位"(Privilege Bit),它可以是一个单独的标志位,也可以是一个多位的标志位。


在x86架构的CPU中,特权级别位通常被设置为两种状态:用户态和内核态。特权级别位被称为特权级别(Privilege Level,PL),它有两种状态:PL0表示最高的特权级别,即内核态;PL3表示最低的特权级别,即用户态。当CPU执行一个指令时,它会根据这个指令的权限级别来检查当前特权级别位的值,如果指令的权限级别高于当前特权级别,则会产生异常,这个异常可以被操作系统内核捕获并处理。


需要注意的是,不同CPU架构的特权级别位可能存储在不同的寄存器中,而且具体的实现方式也有所不同。因此,在编写操作系统内核或者底层驱动程序时,需要特别注意特权级别位的存储和使用方式,以确保程序的正确性和可移植性。

2、用户态陷入内核态

当用户态的程序想要执行特权操作时,就需要切换到内核态去执行。而陷入内核态也是一种特权操作,用户程序不能直接陷入内核态,只能通过系统调用或者在发生中断或异常时才会陷入用户态,其中系统调用是唯一一种由用户程序主动发起的陷入内核态的方式

    异常(Exception):当处理器执行指令时发生异常(如缺页异常、非法操作码等),处理器会自动切换到内核态,并将控制权交给操作系统内核来处理异常。操作系统内核可以根据异常的类型和信息来进行相应的处理,如分配物理页面、关闭程序等。

    中断(Interrupt):当外部设备发生中断事件时,处理器会自动切换到内核态,并将控制权交给操作系统内核来处理中断。操作系统内核可以根据中断的类型和信息来进行相应的处理,如读取输入设备的数据、发送输出数据等。

     

    3、系统调用

    系统调用(System Call)是操作系统提供给用户程序的接口,通过系统调用,用户程序可以请求操作系统内核来执行一些特权操作,如创建进程、打开文件、读写设备等。系统调用的执行过程可以概括为以下几个步骤:


    用户程序调用系统函数,将系统调用的参数传递给系统函数。

    系统函数将系统调用的参数存储到寄存器中,并将系统调用号(syscall number)存储到寄存器eax中。

    系统函数执行中断指令int 0x80,触发CPU从用户态切换到内核态,将控制权交给操作系统内核。

    操作系统内核根据系统调用号和参数,执行相应的特权操作,如创建进程、打开文件、读写设备等。

    操作系统内核将特权操作的结果返回给用户程序,将控制权重新切换到用户态,继续执行用户程序。

    IDT表(Interrupt Descriptor Table)是指中断描述符表,它是操作系统内核用来管理和响应中断、异常和系统调用等事件的数据结构之一。


    在x86架构的计算机中,IDT表是一个由256个条目(或者说表项)组成的数组,每个表项都包含一个处理程序的地址和相关的标志信息,用于响应特定类型的中断或异常。其中,前32个表项用于响应CPU预定义的中断和异常,称为异常描述符表(Exception Descriptor Table);第33到第255个表项用于用户自定义的中断、异常和系统调用等事件,称为中断描述符表(Interrupt Descriptor Table)。


    **当发生一个中断或异常时,CPU会查找IDT表中对应的表项,根据其中存储的处理程序地址跳转到相应的处理程序中执行。**如果处理程序执行成功,则可以对中断或异常进行处理并返回;否则,处理程序可能会抛出异常、生成错误信息或者直接导致系统崩溃等。


    int 0x80的IDT表中的DPL被设置成了3,所以才能从用户态能直接调用int 0x80的中断指令(因为用户态的CPL为3,小于等于DPL),从而陷入内核态。


    也就是如果强制在用户态代码中埋一些特权指令那么执行的时候会因为权限不足而报错,想要执行特权指令就只能进入内核态才能执行,而要主动进入内核态只能通过系统调用去调用内核定义的系统函数,也就是只能通过系统提供的合法、安全的方式来执行,不能自己直接调用特权指令,从而提高了系统的安全性。

    4、内核空间与用户空间

    需要注意的是,操作系统内核和用户程序在物理上并没有区别,它们在同一块内存中,只是在执行时CPU通过特权级别位来区分当前程序执行指令的权限级别,限制了一定要通过系统函数执行特权指令。但是系统函数常驻在内核空间,我们还需要保护这部分函数不被修改。所以操作系统内核需要通过一些特殊的手段来保证自己的安全性和稳定性,例如使用不同的内存地址空间来隔离不同的程序,限制程序的访问范围等。


    内核是指内存管理、文件管理、硬件管理等这些模块的集合,可以看作是一个全局共享的,有权限保护功能的最底层的库,本质上是一个二进制文件,常驻在内存中。而系统调用是进程和内核交互的本质,当有进程想做一些高特权的操作时,如文件读写,就会通过系统调用去执行它的部分代码。开机之后,引导程序就会将内核载入内存。


    在现代操作系统中,内存空间通常被划分为内核空间和用户空间。内核空间是操作系统内核所在的内存区域,它包含了操作系统内核的所有代码和数据结构,以及受保护的硬件资源,如中断控制器、时钟、设备驱动程序等。用户空间是普通应用程序所在的内存区域,它包含了应用程序的代码和数据结构,但是不能直接访问受保护的内核空间资源。


    在x86架构的CPU中,内核空间通常被限制在地址空间的最高端,而用户空间则被限制在地址空间的最低端。具体来说,在32位的x86架构中,内核空间通常被限制在0xC0000000及以上的地址范围,而用户空间则被限制在0x00000000至0xBFFFFFFF之间的地址范围。这样做的目的是为了避免用户程序越界访问内核空间的代码和数据,从而提高操作系统的安全性和稳定性。


    每次访问内存时,需要通过比较DPL(目标内存段的特权级,存在段描述符中)和CPL(当前特权级,也叫进程特权级,存在cs寄存器的低两位)来判断有是否足够的权限级别访问。只有DPL>=CPL时,才允许访问。而内核段的DPL在内核初始化阶段就都设置为了0,所以内核态可以访问任何数据,而用户态不能访问内核数据。

    5、内核栈与用户栈切换

    在x86架构的计算机中,每个进程都有自己的内核栈和用户栈,它们分别用于处理内核态和用户态的函数调用。在进程被创建时,操作系统会为其分配一定数量的虚拟内存空间,其中包括用户栈和内核栈。


    当进程发起系统调用或中断请求进入内核态时,处理器会自动将当前用户态的栈指针(ESP)和标志寄存器(EFLAGS)压入进程内核态堆栈中,同时把内核态堆栈指针加载到ESP中,此时进程就开始在内核态运行。在内核态执行完系统调用或中断服务例程后,处理器会自动从内核态的堆栈中取出之前保存的用户态堆栈指针,并将其加载到ESP寄存器中,以便返回到用户态继续执行。这样,用户态进程就可以通过恢复之前的ESP值,继续使用自己的用户态堆栈。

    相关文章
    |
    6天前
    |
    Linux
    Linux进程与线程的内核实现
    task_struct称为进程描述符结构,该结构定义在文件中。进程描述符中包含一个具体进程的所有信息 进程描述符中包含的数据能完整地描述一个正在执行的程序:它打开的文件,进程的地址空间,挂起的信号,进程的状态等
    40 0
    Linux进程与线程的内核实现
    |
    6天前
    |
    算法 Unix Linux
    进程原理及系统调用
    进程原理及系统调用
    |
    6天前
    |
    存储 安全 数据中心
    系统调用与用户态陷入内核态
    我们都知道操作系统中运行着很多的进程,如果普通的进程可以直接操作硬件那么这个系统的安全性没办法保障,所以操作系统分出了两种状态,一种状态是运行的代码可以操作硬件;一种状态不能操作硬件,只能切换到第一种状态去操作后再切换回来,这就是内核态和用户态。
    |
    11月前
    |
    调度 数据安全/隐私保护
    用户态和内核态 中断处理机制
    用户态和内核态 中断处理机制
    227 0
    |
    7月前
    |
    安全 Linux C++
    操作系统:用户态与内核态
    操作系统:用户态与内核态
    101 0
    |
    10月前
    |
    Unix Shell Linux
    进程系统调用
    进程系统调用
    66 0
    |
    10月前
    |
    Unix Linux
    进程原理及其系统调用(下)
    进程原理及其系统调用
    57 0
    |
    10月前
    |
    算法 Linux 调度
    进程原理及其系统调用(上)
    进程原理及其系统调用
    94 0
    |
    消息中间件 缓存 算法
    嵌入式Linux C多进程编程(五)——进程退出和进程的等待
    嵌入式Linux C多进程编程(五)——进程退出和进程的等待
    211 0
    嵌入式Linux C多进程编程(五)——进程退出和进程的等待
    |
    存储 消息中间件 算法
    操作系统学习笔记_2 中断和系统调用;进程和线程
    学习自计算机科学单本&b站王道课程。
    122 0
    操作系统学习笔记_2 中断和系统调用;进程和线程