[Linux]内核态与用户态详解

内核态和用户态是针对CPU状态的描述。在内核态可以执行一切特权代码,在用户态只能执行那些受限级别的代码。如果需要调用特权代码需要进行内核态切换。

一、内核态和用户态概况

内核态:

  • 系统中既有操作系统的程序,也有普通用户程序。为了安全性和稳定性,操作系统的程序不能随便访问,这就是内核态。即需要执行操作系统的程序就必须转换到内核态才能执行
  • 内核态可以使用计算机所有的硬件资源

用户态:

  • 不能直接使用系统资源,也不能改变 CPU 的工作状态,并且只能访问这个用户程序自己的存储空间!

为什么要区分内核态和用户态?

本质意义是为了进行权限保护。限定用户的程序不能乱搞操作系统,如果人人都能任意读写任意地址空间软件管理就乱套了。例如:

  • 在CPU的所有指令中,有一些指令是非常危险的,如果错用,将导致整个系统崩溃。比如:清内存、设置时钟等。如果所有的程序都能使用这些指令,那么你的系统一天死机N回就不足为奇了。
  • 所以,CPU将指令分为特权指令和非特权指令,对于那些危险的指令,只允许操作系统及其相关模块使用,普通的应用程序只能使用那些不会造成灾难的指令。

二、CPU指令集权限

指令集是CPU实现软件指挥硬件执行的媒介,具体来说每一条汇编语句都对应了一条CPU指令,而非常非常多的CPU指令在一起,可以组成一个、甚至多个集合,指令的集合叫CPU指令集。

同时CPU指令集有权限分级,大家试想,CPU指令集可以直接操作硬件的,要是因为指令操作的不规范,造成的错误会影响整个计算机系统的。好比你写程序,因为对硬件操作不熟悉,导致操作系统内核、及其他所有正在运行的程序,都可能会因为操作失误而受到不可挽回的错误,最后只能重启计算机才行。

而对于硬件的操作是非常复杂的,参数众多,出问题的几率相当大 ,必须谨慎的进行操作,对开发人员来说是个艰巨的任务,还会增加负担,同时开发人员在这方面也不被信任,所以操作系统内核直接屏蔽开发人员对硬件操作的可能,都不让你碰到这些CPU指令集。


那么是如何解决上面的问题呢?

针对上面的需求,硬件设备商直接提供硬件级别的支持,做法就是对CPU指令集设置了权限,不同级别权限能使用的CPU指令集 是有限的,以Intel CPU为例,Inter把CPU指令集操作的权限由高到低划为4级:

  • ring 0
  • ring 1
  • ring 2
  • ring 3

Linux系统仅采用ring0和ring3这2个权限。用户态的程序工作在3,内核态的程序处于0

  • ring0权限最高,可以使用所有CPU指令集,有对硬件的所有操作权限
  • ring3权限最低,仅能使用常规CPU指令集,不能使用操作硬件资源的CPU指令集。代码没有对硬件的直接控制权限,也不能直接访问地址的内存,程序是通过调用系统接口(System Call APIs)来达到访问硬件和内存

三、内核态与用户态的空间

1.内核空间和用户空间的划分

在内存资源上的使用,操作系统对用户态与内核态也做了限制,每个进程创建都会分配「虚拟空间地址」,以Linux32位操作系统为例,它的寻址空间范围是4G(2的32次方),而操作系统会把虚拟控制地址划分为两部分,一部分为内核空间,另一部分为用户空间 ,高位的 1G(从虚拟地址 0xC0000000 到 0xFFFFFFFF)由内核使用,而低位的 3G(从虚拟地址 0x00000000 到 0xBFFFFFFF)由各个进程使用。

  • 用户态:只能操作0-3G范围的低位虚拟空间地址
  • 内核态:0-4G范围的虚拟空间地址都可以操作,尤其是对3-4G范围的高位虚拟空间地址必须由内核态去操作
  • 补充:3G-4G部分大家是共享的(指所有进程的内核态逻辑地址是共享同一块内存地址) ,是内核态的地址空间,这里存放着整个内核的代码和所有的内核模块,以及内核所维护的数据。每个进程的4G虚拟空间地址中高位1G都是一样的,即内核空间。只有剩余的3G才归进程自己使用,换句话说就是, 高位1G的内核空间是被所有进程共享的!

最后做个小结,我们通过指令集权限区分用户态和内核态,还限制了内存资源的使用,操作系统为用户态与内核态划分了两块内存空间,给它们对应的指令集使用

2.内核空间、用户空间与物理内存的映射关系

每一个进程都有自己的进程地址空间,该进程地址空间由内核空间和用户空间组成:

  • 用户所写的代码和数据位于用户空间,通过用户级页表与物理内存之间建立映射关系。
  • 内核空间存储的实际上是操作系统代码和数据,通过内核级页表与物理内存之间建立映射关系。

内核级页表是一个全局的页表,它用来维护操作系统的代码与进程之间的关系 。因此,在每个进程的进程地址空间中,用户空间是属于当前进程的,每个进程看到的代码和数据是完全不同的,但内核空间所存放的都是操作系统的代码和数据,所有进程看到的都是一样的内容 需要注意的是,虽然每个进程都能够看到操作系统,但并不意味着每个进程都能够随时对其进行访问。


四、内核态和用户态切换为何提供系统调用?

相信大家都听过这样的话「用户态和内核态切换的开销大」,但是它的开销大在那里呢?简单点来说有下面几点:

  • 保留用户态现场(上下文、寄存器、用户栈等)
  • 复制用户态参数,用户栈切到内核栈,进入内核态
  • 额外的检查(因为内核代码对用户不信任)
  • 执行内核态代码
  • 复制内核态代码执行结果,回到用户态
  • 恢复用户态现场(上下文、寄存器、用户栈等)

实际上操作系统会比上述的更复杂,这里只是个大概,我们可以发现一次切换经历了用户态 --->内核态 --->用户态

用户态要主动切换到内核态要有统一的入口,它们就是内核提供的系统调用接口 ,下面是Linux整体架构图: 我们可以看出来通过系统调用将Linux整个体系分为内核态和用户态,而内核提供了一组通用的访问接口,它们使应用程序能访问到内核的资源,如CPU、内存、I/O,这些接口就叫系统调用

  • 库函数就是屏蔽这些复杂的底层实现细节,减轻程序员的负担,从而更加关注上层的逻辑实现,它对系统调用进行封装,提供简单的基本接口给程序员。

  • Shell顾名思义,就是外壳的意思,就好像把内核包裹起来的外壳,它是一种特殊的应用程序,俗称命令行。Shell也是可编程的,它有标准的Shell语法,符合其语法的文本叫Shell脚本,很多人都会用Shell脚本实现一些常用的功能,可以提高工作效率。


五、内核态和用户态切换

如何理解进程切换?

  • 在当前进程的进程地址空间中的内核空间,找到操作系统的代码和数据。
  • 执行操作系统的代码,将当前进程的代码和数据剥离下来,并换上另一个进程的代码和数据。

注意: 当你访问用户空间时你必须处于用户态,当你访问内核空间时你必须处于内核态。

1.用户态切换到内核态的几种方式

  • 系统调用(本质是内中断):用户态进程主动切换到内核态的方式,用户态进程通过系统调用向操作系统申请资源完成工作,例如 fork( )就是一个创建新进程的系统调用,系统调用的机制核心使用了操作系统为用户特别开放的一个中断来实现,如Linux的 int80h 中断,也可以称为软中断
  • 进程的时间片到了,导致进程切换(软中断):属于时钟中断(Timer Interrupt)的一种实现方式。
  • 异常(内中断):当CPU在执行用户态的进程时,发生了一些没有预知的异常,这时当前运行进程会切换到处理此异常的内核相关进程中,也就是切换到了内核态,如缺页异常
  • 外围设备的中断(硬中断):当CPU在执行用户态的进程时,外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令,转到与中断信号对应的处理程序去执行,也就是切换到了内核态。如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后边的操作等。

2.内核态切换到用户态的几种方式

  • 系统调用返回时。
  • 进程切换完毕。
  • 异常、中断、陷阱等处理完毕。

特别鸣谢:

操作系统之内核态与用户态

什么是用户态和内核态?

Linux进程信号

相关推荐
路溪非溪12 分钟前
关于Linux内核中头文件问题相关总结
linux
Lovyk3 小时前
Linux 正则表达式
linux·运维
Fireworkitte3 小时前
Ubuntu、CentOS、AlmaLinux 9.5的 rc.local实现 开机启动
linux·ubuntu·centos
sword devil9004 小时前
ubuntu常见问题汇总
linux·ubuntu
ac.char4 小时前
在CentOS系统中查询已删除但仍占用磁盘空间的文件
linux·运维·centos
淮北也生橘126 小时前
Linux的ALSA音频框架学习笔记
linux·笔记·学习
华强笔记9 小时前
Linux内存管理系统性总结
linux·运维·网络
十五年专注C++开发9 小时前
CMake进阶: CMake Modules---简化CMake配置的利器
linux·c++·windows·cmake·自动化构建
phoenix098110 小时前
ansible部署lnmp-allinone
linux·运维·ansible
winds~10 小时前
【git】 撤销revert一次commit中的某几个文件
linux·c++