一.CPU指令集权限
指令集是实现CPU实现软件指挥硬件执行的媒介,具体来说每一条汇编语句都对应了一条CPU指令,而非常多的CPU指令再一起组成一个甚至多个集合,指令的集合叫CPU指令集;
因为CPU指令集可以操纵硬件,会造成较大的影响,所以会对CPU指令集权限分级。
Inter把CPU指令集分为四级从高到低为ring0 ring1 ring2 ring3
Linux系统仅采用ring 0 和 ring 3 这2个权限。
ring0也被叫做内核态 ,完全在操作系统内核中运行
ring3被叫做用户态,在应用程序中运行;
二.用户态内核态
当我们在写程序是,凡是涉及到IO读写、内存分配等硬件资源的操作时,往往不能直接操作,而是通过一种叫系统调用的过程,让程序陷入到内核态运行,然后内核态的CPU执行有关硬件资源操作指令,得到相关的硬件资源后在返回到用户态继续执行,之间还要进行一系列的数据传输。
内核态:也叫内核空间,是内核进程/线程所在的区域。主要负责运行系统、硬件交互。
用户态:也叫用户空间,是用户进程/线程所在的区域。主要用于执行用户程序。
内核态主要工作:向下控制硬件资源,向内管理操作系统资源:包括进程的调度和管理、内存的管理、文件系统的管理、设备驱动程序的管理以及网络资源的管理,向上则向应用程序提供系统调用的接口。
2.1内核态与用户态指令集权限区别
指令集权限:用户态与内核态的概念主要区别就是CPU指令集权限的区别,进程中要读写IO,必然会用到ring0级别的指令集,而此时CPU指令集权限只有ring3,为了可以操作ring0的指令集,C P U 切换指令集操作权限级别为 ring 0,C P U再执行相应的ring 0 级别的 C P U 指令集
(内核代码),执行的内核代码会使用当前进程的内核栈。
操作系统在执行用户程序时,主要工作在用户态,只有在其执行没有权限完成的任务时才会切换到内核态。
PS:每个进程都有两个栈,分别是用户栈与内核栈,对应用户态与内核态的使用
2.2.用户态与内核态空间
内存资源:在内存资源上的使用操作系统对用户态与内核态也做了限制,每个进程创建都会分配 虚拟空间地址 以linux32位操作系统为例子,他的寻址空间是4G(2的32次方),而操作系统会把虚拟空间换分为两部分,一部分是内核空间,另一部分是用户空间;高位的 1G
(从虚拟地址 0xC0000000 到 0xFFFFFFFF)由内核使用,而低位的 3G
(从虚拟地址 0x00000000 到 0xBFFFFFFF)由各个进程使用。
- 用户态:只能操作
0-3G
范围的低位虚拟空间地址 - 内核态:
0-4G
范围的虚拟空间地址都可以操作,尤其是对3-4G
范围的高位虚拟空间地址必须由内核态去操作 - 补充:
3G-4G
部分大家是共享的(指所有进程的内核态逻辑地址是共享同一块内存地址),是内核态的地址空间,这里存放在整个内核的代码和所有的内核模块,以及内核所维护的数据
每个进程的 4G
虚拟空间地址,高位 1G
都是一样的,即内核空间。只有剩余的 3G
才归进程自己使用,换句话说就是, 高位 1G
的内核空间是被所有进程共享的。
最后做个小结,我们通过指令集权限区分用户态和内核态,还限制了内存资源的使用,操作系统为用户态与内核态划分了两块内存空间,给它们对应的指令集使用
2.3.用户态访问内核态资源的方式
用户态要主动切换到内核态,那必须要有入口才行,实际上内核态是提供了统一的入口,下面是Linux整体架构图
用户态的应用程序可以通过三种方式来访问内核态的资源:
- 1)系统调用
- 2)库函数
- 3)Shell脚本
1.系统调用
从上图我们可以看出来通过系统调用将Linux整个体系分为用户态和内核态,为了使应用程序访问到内核的资源,如CPU、内存、I/O,内核必须提供一组通用的访问接口,这些接口就叫系统调用。
系统调用是操作系统的最小功能单位
,根据不同的应用场景,不同的Linux发行版本提供的系统调用数量也不尽相同,大致在240-350之间。系统调用组成了用户态跟内核态交互的基本接口。
2.库函数
库函数就是屏蔽这些复杂的底层实现细节,减轻程序员的负担,从而更加关注上层的逻辑实现。它对系统调用进行封装,提供简单的基本接口给用户。例如:open(),write(),read()
等等。库函数根据不同的标准也有不同的版本,例如:glibc库,posix库等
。
注:系统调用过多,这势必会加重程序员的负担,良好的程序设计方法是:重视上层的业务逻辑操作,而尽可能避免底层复杂的实现细节。
库函数正是为了将程序员从复杂的细节中解脱出来而提出的一种有效方法。它实现对系统调用的封装,将简单的业务逻辑接口呈现给用户,方便用户调用,从这个角度上看,库函数就像是组成汉字的“偏旁”。
如“人”,对于复杂操作,我们借助于库函数来实现,如“仁”。显然,这样的库函数依据不同的标准也可以有不同的实现版本,如ISO C标准库,POSIX标准库等。
3.Shell脚本
Shell 就是一个“中间商”,它在用户和内核之间“倒卖”数据,只是用户不知道罢了。
Shell 本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序,它和 QQ、迅雷、Firefox等其它软件没有什么区别。然而Shell 也有着它的特殊性,就是开机立马启动,并呈现在用户面前;用户通过 Shell 来使用Linux,不启动 Shell 的话,用户就没办法使用 Linux。
shell的作用:shell 能够接收用户输入的命令,并对命令进行处理,处理完毕后再将结果反馈给用户,比如输出到显示器、写入到文件等,这就是大部分读者对 Shell 的认知。
2.4.用户态和内核态的切换
相信大家都听过这样的话「用户态和内核态切换的开销大」,但是它的开销大在那里呢?简单点来说有下面几点
- 保留用户态现场(上下文、寄存器、用户栈等)
- 复制用户态参数,用户栈切到内核栈,进入内核态
- 额外的检查(因为内核代码对用户不信任)
- 执行内核态代码
- 复制内核态代码执行结果,回到用户态
- 恢复用户态现场(上下文、寄存器、用户栈等)
实际上操作系统会比上述的更复杂,这里只是个大概,我们可以发现一次切换经历了「用户态 -> 内核态 -> 用户态」
最后来说说,什么情况会导致用户态到内核态切换
- 系统调用:用户态进程主动切换到内核态的方式,用户态进程通过系统调用向操作系统申请资源完成工作,例如 fork()就是一个创建新进程的系统调用,系统调用的机制核心使用了操作系统为用户特别开放的一个中断来实现,如Linux 的 int 80h 中断,也可以称为软中断。
- 由于用户态无法完成某些任务,用户态会请求切换到内核态,内核态通过为用户专门开放的中断完成切换。
- 异常:当 C P U 在执行用户态的进程时,发生了一些没有预知的异常,这时当前运行进程会切换到处理此异常的内核相关进程中,也就是切换到了内核态,如缺页异常
- 中断:当 C P U 在执行用户态的进程时,外围设备完成用户请求的操作后,会向 C P U 发出相应的中断信号,这时 C P U 会暂停执行下一条即将要执行的指令,转到与中断信号对应的处理程序去执行,也就是切换到了内核态。如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后边的操作等。
2.5个人问题
1.内核态用户态切换运行具体过程
左边是普通的用户进程,平时在运行时,是访问的低3GB的虚拟内存空间,当通过系统调用后,此用户进程陷入到内核态,执行的内核提供的系统调用API。从用户态进入到内核态,涉及到状态切换,因此需要保留现场,用户态和内核态各自都维护了一套栈空间。
右边是内核线程(在内核中可以理解为进程),其只有1GB的内核空间,无用户态地址空间。由于内核态空间与用户态空间采用了不同的映射机制,虽然内核态只有1GB的虚拟地址空间,但是它可以访问所有的物理内存地址。
*学习记录来源
从根上理解用户态与内核态 - 知乎 (zhihu.com)
Linux常问面试问题之——用户态与内核态、用户态访问内核态资源的方式、用户态到内核态的切换_用户态和内核态数据存储访问方式都是什么-CSDN博客