用户态和内核态

内核态#

  • 即内核、内核空间。控制计算机硬件资源,包括CPU资源、存储资源、I/O资源等,提供上层应用程序运行的环境。为上层应用提供系统调用访问的接口。

用户态#

  • 即用户空间。上层应用程序的活动空间,只能访问受限资源,不能直接访问内存等硬件设备,必须通过系统调用陷入到内核中,才能访问这些特权资源。

系统调用#

  • 由内核提供的,供上层应用访问到计算机硬件资源的接口。
  • 是操作系统的最小功能单位,不能再简化,类似于原子操作。大致在240-350之间。
  • 用户态进程主动要求切换到内核态的一种方式

库函数#

  • 系统调用的封装,将简单的业务逻辑接口呈现给用户,方便用户调用。如C语言库函数

良好的程序设计方法是:程序要只需要关注上层的业务逻辑操作,而尽可能避免底层复杂的实现细节。

Shell#

  • 特殊的应用程序,俗称命令行。命令解释器,对系统调用做了一层封装,充当"胶水",联通系统调用-上层应用。

关系图

访问内核资源的3种方式#

  1. 系统调用
  2. 库函数
  3. Shell脚本

内核职能#

  • 向下控制硬件资源
  • 向内管理操作系统资源:进程的调度和管理、内存的管理、文件系统的管理、设备驱动程序的管理以及网络资源的管理
  • 向上提供上层应用程序系统调用的接口

img

内核态和用户态构成Linux操作系统的体系架构
这种分层的架构极大地提高了资源管理的可扩展性和灵活性,而且方便用户对资源的调用和集中式的管理,带来一定的安全性。

用户态和内核态的切换#

  • 操作系统的资源是有限的,如果访问资源的操作过多,必然会消耗过多的资源,而且如果不对这些操作加以区分,很可能造成资源访问的冲突。
  • 为了减少有限资源的访问和使用冲突,Unix/Linux的设计哲学之一就是:对不同的操作赋予不同的执行等级,就是所谓特权的概念。
  • 用户态的进程可以执行的操作和访问的资源都会受到极大的限制,内核态的进程则可以执行任何操作并且在资源的使用上没有限制。
  • 某些操作需要在内核权限下执行,这就涉及到一个从用户态切换到内核态的过程。
  • Linux操作系统中主要采用了Ring0(内核态)和Ring3(用户态)两个特权级
  • C函数库中的内存分配函数malloc(),实际使用sbrk()系统调用来分配内存。
  • C函数库printf(),实际是wirte()系统调用来输出字符串。

用户态到内核态的切换3种情况#

  1. 系统调用:int 80H中断。
  2. 异常事件:CPU正在执行运行在用户态的程序时,突然发生某些预先不可知的异常事件,这个时候就会触发从当前用户态执行的进程转向内核态执行相关的异常事件,典型的如缺页异常。
  3. 外围设备的中断:当外围设备完成用户的请求操作后,会像CPU发出中断信号,此时,CPU就会暂停执行下一条即将要执行的指令,转而去执行中断信号对应的处理程序,如果先前执行的指令是在用户态下,则自然就发生从用户态到内核态的转换。

系统调用本质也是中断。最初系统调用是通过汇编指令int(中断)来实现的,当用户态进程发出int 80H指令时,CPU切换到内核态并开始执行system_call函数。因为int指令要执行一致性和安全性检查,系统调用太慢,后来Intel又提供了“快速系统调用”的sysenter指令。

系统调用可以认为是用户进程主动发起的,异常外围设备中断则是被动的。

切换开销#

一个cpu要么在用户态工作,要么在内核态工作

  1. 使用int 80H软中断指令,保存用户程序上下文context(寄存器、内存数据)开销。
  2. 进程/线程调度开销
  3. 内核代码对用户不信任,权限安全检查
  4. 不同用户程序间切换的话,那么还要更新cr3寄存器,会更换每个程序的虚拟内存到物理内存映射表的地址

一次系统调用损耗#

  1. CPU上下文切换,先保存CPU寄存器中用户态的指令位置

  2. 再重新更新为内核指令的位置

  3. 系统调用结束,CPU寄存器恢复到原来保存的用户态

    一次系统调用,发生了两次CPU上下文切换

上下文切换#

  • 上下文切换种类:

    1. 线程切换:同一进程中的两个线程之间的切换
    2. 进程切换:两个进程之间的切换
    3. 模式切换:给定线程中,用户模式和内核模式的切换

    在上下文切换过程中,CPU会停止处理当前运行的程序,并保存当前程序运行的具体位置以便之后继续运行
    在程序中上下文切换过程中的“页码”信息是保存在进程控制块(PCB, process control block),切换帧
    PCB通常是系统内存占用区中的一个连续存区,它存放着操作系统用于描述进程情况及控制进程运行所需的全部信息

  • 上下文切换执行步骤:

    1. 保存进程A的状态(寄存器和操作系统数据);
    2. 更新PCB中的信息,对进程A的“运行态”做出相应更改;
    3. 将进程A的PCB放入相关状态的队列;
    4. 将进程B的PCB信息改为“运行态”,并执行进程B;
    5. B执行完后,从队列中取出进程A的PCB,恢复进程A被切换时的上下文,继续执行A;
  • CPU 上下文切换:把前一个任务的CPU上下文(CPU寄存器和程序计数器-内核栈)保存,然后加载新任务的上下文到寄存器和程序计数器中,跳转到程序计数器所指向的位置,开始执行任务。

  • 进程切换:进程资源(用户态的虚拟内存、栈,内核态的栈和硬件上下文-CPU寄存器)
    1.切换页目录以使用新的地址空间(刷新新进程的虚拟内存和用户栈,当虚拟内存更新后,TLB也需要更新)
    2.切换内核栈和硬件上下文

  • 线程切换:1.切换内核栈和硬件上下文, 线程的切换虚拟内存空间依然是相同的(TLB),但是进程切换是不同的

参考引用#

Linux探秘之用户态与内核态
一次系统调用开销到底有多大?
从Linux用户态和内核态开始,去理解CPU附带的线程切换开销,以及线程池
为什么用户程序发生一次系统调用损耗要大于同一进程内多线程上下文切换的损耗?
Linux 系统调用权威指南(2016)
https://www.cnblogs.com/vamei/archive/2012/09/19/2692452.html?spm=a2c6h.12873639.0.0.d1c47d42jfeqEf

Linux 内核空间与用户空间

https://www.cnblogs.com/gtblog/p/12155109.html

https://time.geekbang.org/column/article/69859

posted @   FynnWang  阅读(383)  评论(0编辑  收藏  举报
编辑推荐:
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程
more_horiz
keyboard_arrow_up dark_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示