-
2020-12-22 21:30:27
1. 切换方式
从用户态到内核态切换可以通过三种方式,或者说会导致从用户态切换到内核态的操作:
- 系统调用,这个上面已经讲解过了,在我公众号之前的文章也有讲解过。其实系统调用本身就是中断,但是软件中断,跟硬中断不同。系统调用机制是使用了操作系统为用户特别开放的一个中断来实现,如 Linux 的 int 80h 中断。
- 异常:如果当前进程运行在用户态,如果这个时候发生了异常事件,会触发由当前运行进程切换到处理此异常的内核相关进程中
- 外围设备中断:外围设备完成用户请求的操作之后,会向CPU发出中断信号,这时CPU会转去处理对应的中断处理程序。
2. 代价何在
当发生用户态到内核态的切换时,会发生如下过程(本质上是从“用户程序”切换到“内核程序”)
- 设置处理器至内核态。
- 保存当前寄存器(栈指针、程序计数器、通用寄存器)。
- 将栈指针设置指向内核栈地址。
- 将程序计数器设置为一个事先约定的地址上,该地址上存放的是系统调用处理程序的起始地址。
而之后从内核态返回用户态时,又会进行类似的工作。
3. 如何避免频繁切换
用户态和内核态之间的切换有一定的开销,如果频繁发生切换势必会带来很大的开销,所以要想尽一切办法来减少切换。这也是面试常考的问题。
3.1 减少线程切换
因为线程的切换会导致用户态和内核态之间的切换,所以减少线程切换也会减少用户态和内核态之间的切换。那么如何减少线程切换呢?
- 无锁并发编程。多线程竞争锁时,加锁、释放锁会导致比较多的上下文切换。(为什么加锁和释放锁会导致上下文切换,看文末的补充解释)
- CAS算法。使用CAS避免加锁,避免阻塞线程
- 使用最少的线程。避免创建不需要的线程
- 协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
3.2 一个面试问题
I/O 频繁发生内核态和用户态切换,怎么解决。
首先要同意这个说法,即I/O会导致系统调用,从而导致内核态和用户态之间的切换。因为对I/O设备的操作是发生在内核态。那如何减少因为I/O导致的系统调用呢?答案是:使用户进程缓冲区。下面解释一下原因
用户进程缓冲区
你看一些程序在读取文件时,会先申请一块内存数组,称为buffer,然后每次调用read,读取设定字节长度的数据,写入buffer。之后的程序都是从buffer中获取数据,当buffer使用完后,在进行下一次调用,填充buffer。所以说:用户缓冲区的目的就是是为了减少系统调用次数,从而降低操作系统在用户态与核心态切换所耗费的时间。除了在进程中设计缓冲区,内核也有自己的缓冲区。
内核缓存区
当一个用户进程要从磁盘读取数据时,内核一般不直接读磁盘,而是将内核缓冲区中的数据复制到进程缓冲区中。但若是内核缓冲区中没有数据,内核会把对数据块的请求,加入到请求队列,然后把进程挂起,为其它进程提供服务。等到数据已经读取到内核缓冲区时,把内核缓冲区中的数据读取到用户进程中,才会通知进程,当然不同的IO模型,在调度和使用内核缓冲区的方式上有所不同。
小结
图中的read,write和sync都是系统调用。read是把数据从内核缓冲区复制到进程缓冲区。write是把进程缓冲区复制到内核缓冲区。当然,write并不一定导致内核的缓存同步动作sync,比如OS可能会把内核缓冲区的数据积累到一定量后,再一次性同步到磁盘中。这也就是为什么断电有时会导致数据丢失。所以说内核缓冲区,可以在OS级别,提高磁盘IO效率,优化磁盘写操作。
4. 补充解释
为什么加锁和释放锁会导致上下文切换
Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。但是由于使用Mutex Lock需要将当前线程挂起并从用户态切换到内核态来执行,这种切换的代价是非常昂贵的因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。
更多相关内容 -
关于内核态和用户态切换开销的测试
2021-01-26 17:37:57最近开发用到fuse文件系统,这个文件系统的功能实现是在用户态下进行的,然而它的文件系统操作接口必须在内核态注册,所以需要研究一下内核态到用户态的开销到底如何。 下面这个例子是在stackoverflow上看到的,基本...最近开发用到fuse文件系统,这个文件系统的功能实现是在用户态下进行的,然而它的文件系统操作接口必须在内核态注册,所以需要研究一下内核态到用户态的开销到底如何。
下面这个例子是在stackoverflow上看到的,基本能反映开销的差异了:
在linux测试这个测试程序,getuid是一个系统调用,返回当前用户的id1 #include <unistd.h> 2 3 #define MAX 100000000 4 int main() { 5 int ii; 6 for (ii=0; ii<MAX; ii++) getuid(); 7 return 0; 8 }
结果显示循环1亿次用时8秒多
real 0m8.438s
user 0m3.952s
sys 0m4.485s当使用如下程序测试时,
1 #include <unistd.h> 2 #define MAX 100000000 3 4 int _getid() 5 { 6 return 1; 7 } 8 9 int main() { 10 int ii; 11 for (ii=0; ii<MAX; ii++) _getid(); 12 return 0; 13 }
运行结果如下:
real 0m0.305s
user 0m0.304s
sys 0m0.000s这时时间消耗明显减少了很多。
两个程序时间的消耗的差异主要在于内核态和用户态的切换,每一次循环都涉及一次用户态->内核态->用户态的切换,所以消耗的时间就多了很多。
同时也说明我必须要修改fuse的各个文件接口的功能实现来让它不再反复切换用户内核态了。
-
用户态切换内核态的开销测试
2020-08-05 07:54:18纸上得来终觉浅,希望通过直观的代码测试出来内核切换的开销 测试过程 通过两段程序进行对比,第一段是进行了系统调用获取uid,第二段是直接返回一个uid的值 gcc callsystem.c -o callsystem gcc calluser.c -o ...前文
纸上得来终觉浅,希望通过直观的代码测试出来内核切换的开销
测试过程
通过两段程序进行对比,第一段是进行了系统调用获取uid,第二段是直接返回一个uid的值
gcc callsystem.c -o callsystem gcc calluser.c -o calluser
测试结果
我们可以看到带有系统调用耗时需要11秒左右,用户态的耗时0.15s,开销上的差异非常明显系统调用的代码
#include<unistd.h> #define MAX 50000000 int main(){ int ii; for(ii = 0;ii < MAX; ii++) getuid(); return 0; }
非系统调用的代码
#include<unistd.h> #define MAX 50000000 int _getuid(){ return 1001; } int main(){ int ii; for(ii = 0;ii < MAX; ii++) _getuid(); return 0; }
strace 跟踪系统调用
其实getuid不管系统还是用户,都是返回数字就是,测试下来可以看出差异那么大
我们使用strace跟踪一下调用strace -c ./callsystem
下面这个uid其实已经跑不出来了,基本也就是在系统调用的时候耗时
系统调用慢的缘由
系统调用其实就是应用程序和内核空间的一票接口,因为服务是在内核中提供的,所以不能直接调用调用流程如下:
实际上,系统调用的过程首先需要把用户态切换成内核态,系统调用过程是通过软中断0x80实现的,这个是比较核心的因素,因为一旦是涉及到其他中断就需要经历和其他中断一样的过程,当年这块可是考试重点:
关键的结论其实就是,宏观上来说CPU利用率是提高了,但是实际上来说中断的工作以及处理恢复工作其实都是非常耗时的,系统调用慢的缘由便是如此! -
为什么用户态和内核态的切换耗费时间?
2021-01-26 17:26:29思考这个问题的导火线是,看到java中的synchronized关键字。 经过编译,synchronized标注的函数会加一个读写锁,一般不推荐...上面涉及的内核态与用户态切换时系统调用,内核也相当于一个软件,他们切换就相当于从https://www.cnblogs.com/bakari/p/5520860.html
思考这个问题的导火线是,看到java中的synchronized关键字。
经过编译,synchronized标注的函数会加一个读写锁,一般不推荐使用,因为加锁解锁设计到内核态与用户态的转换,有时转化耗时比函数体执行时间还长,所以不推荐使用。
这个观点在学操作系统的时候作为常识被认知,那背后具体的原理是什么呢?
一句话回答问题:
上面涉及的内核态与用户态切换时系统调用,内核也相当于一个软件,他们切换就相当于从一个软件换用另一个软件————控制计算机的硬件资源,并提供上层应用程序运行的环境。
很受启发,我理解的就是,用户态的程序其实经过编译成底层机器码的,比内核态接口还低级的机器码,系统调用反而是往上走了一个level,相当于调用一个软件包。
具体来说是这样的,
当程序中有系统调用语句,程序执行到系统调用时,首先使用类似int 80H的软中断指令,保存现场,去的系统调用号,在内核态执行,然后恢复现场,每个进程都会有两个栈,一个内核态栈和一个用户态栈。当执行int中断执行时就会由用户态,栈转向内核栈。系统调用时需要进行栈的切换。而且内核代码对用户不信任,需要进行额外的检查。系统调用的返回过程有很多额外工作,比如检查是否需要调度等。系统调用一般都需要保存用户程序得上下文(context), 在进入内核得时候需要保存用户态得寄存器,在内核态返回用户态得时候会恢复这些寄存器得内容。这是一个开销的地方。 如果需要在不同用户程序间切换的话,那么还要更新cr3寄存器,这样会更换每个程序的虚拟内存到物理内存映射表的地址,也是一个比较高负担的操作。
引自:为什么系统调用比普通的函数调用更耗时?用户态和内核态切换的代价在哪?——————华丽的分割线——————
接下来是介绍内核态和用户态如上图所示,从宏观上来看,Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核)。内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境。用户态即上层应用程序的活动空间,应用程序的执行必须依托于内核提供的资源,包括CPU资源、存储资源、I/O资源等。为了使上层应用能够访问到这些资源,内核必须为上层应用提供访问的接口:即系统调用。
系统调用是操作系统的最小功能单位,这些系统调用根据不同的应用场景可以进行扩展和裁剪,现在各种版本的Unix实现都提供了不同数量的系统调用,如Linux的不同版本提供了240-260个系统调用,FreeBSD大约提供了320个(reference:UNIX环境高级编程)。我们可以把系统调用看成是一种不能再化简的操作(类似于原子操作,但是不同概念),有人把它比作一个汉字的一个“笔画”,而一个“汉字”就代表一个上层应用,我觉得这个比喻非常贴切。因此,有时候如果要实现一个完整的汉字(给某个变量分配内存空间),就必须调用很多的系统调用。如果从实现者(程序员)的角度来看,这势必会加重程序员的负担,良好的程序设计方法是:重视上层的业务逻辑操作,而尽可能避免底层复杂的实现细节。库函数正是为了将程序员从复杂的细节中解脱出来而提出的一种有效方法。它实现对系统调用的封装,将简单的业务逻辑接口呈现给用户,方便用户调用,从这个角度上看,库函数就像是组成汉字的“偏旁”。这样的一种组成方式极大增强了程序设计的灵活性,对于简单的操作,我们可以直接调用系统调用来访问资源,如“人”,对于复杂操作,我们借助于库函数来实现,如“仁”。显然,这样的库函数依据不同的标准也可以有不同的实现版本,如ISO C 标准库,POSIX标准库等。
Shell是一个特殊的应用程序,俗称命令行,本质上是一个命令解释器,它下通系统调用,上通各种应用,通常充当着一种“胶水”的角色,来连接各个小功能程序,让不同程序能够以一个清晰的接口协同工作,从而增强各个程序的功能。同时,Shell是可编程的,它可以执行符合Shell语法的文本,这样的文本称为Shell脚本,通常短短的几行Shell脚本就可以实现一个非常大的功能,原因就是这些Shell语句通常都对系统调用做了一层封装。为了方便用户和系统交互,一般,一个Shell对应一个终端,终端是一个硬件设备,呈现给用户的是一个图形化窗口。我们可以通过这个窗口输入或者输出文本。这个文本直接传递给shell进行分析解释,然后执行。
总结一下,用户态的应用程序可以通过三种方式来访问内核态的资源:
1)系统调用
2)库函数
3)Shell脚本
下图是对上图的一个细分结构,从这个图上可以更进一步对内核所做的事有一个“全景式”的印象。主要表现为:向下控制硬件资源,向内管理操作系统资源:包括进程的调度和管理、内存的管理、文件系统的管理、设备驱动程序的管理以及网络资源的管理,向上则向应用程序提供系统调用的接口。从整体上来看,整个操作系统分为两层:用户态和内核态,这种分层的架构极大地提高了资源管理的可扩展性和灵活性,而且方便用户对资源的调用和集中式的管理,带来一定的安全性。
用户态和内核态的切换
因为操作系统的资源是有限的,如果访问资源的操作过多,必然会消耗过多的资源,而且如果不对这些操作加以区分,很可能造成资源访问的冲突。所以,为了减少有限资源的访问和使用冲突,Unix/Linux的设计哲学之一就是:对不同的操作赋予不同的执行等级,就是所谓特权的概念。简单说就是有多大能力做多大的事,与系统相关的一些特别关键的操作必须由最高特权的程序来完成。Intel的X86架构的CPU提供了0到3四个特权级,数字越小,特权越高,Linux操作系统中主要采用了0和3两个特权级,分别对应的就是内核态和用户态。运行于用户态的进程可以执行的操作和访问的资源都会受到极大的限制,而运行在内核态的进程则可以执行任何操作并且在资源的使用上没有限制。很多程序开始时运行于用户态,但在执行的过程中,一些操作需要在内核权限下才能执行,这就涉及到一个从用户态切换到内核态的过程。比如C函数库中的内存分配函数malloc(),它具体是使用sbrk()系统调用来分配内存,当malloc调用sbrk()的时候就涉及一次从用户态到内核态的切换,类似的函数还有printf(),调用的是wirte()系统调用来输出字符串,等等。
到底在什么情况下会发生从用户态到内核态的切换,一般存在以下三种情况:
1)当然就是系统调用:原因如上的分析。
2)异常事件: 当CPU正在执行运行在用户态的程序时,突然发生某些预先不可知的异常事件,这个时候就会触发从当前用户态执行的进程转向内核态执行相关的异常事件,典型的如缺页异常。
3)外围设备的中断:当外围设备完成用户的请求操作后,会像CPU发出中断信号,此时,CPU就会暂停执行下一条即将要执行的指令,转而去执行中断信号对应的处理程序,如果先前执行的指令是在用户态下,则自然就发生从用户态到内核态的转换。
注意:系统调用的本质其实也是中断,相对于外围设备的硬中断,这种中断称为软中断,这是操作系统为用户特别开放的一种中断,如Linux int 80h中断。所以,从触发方式和效果上来看,这三种切换方式是完全一样的,都相当于是执行了一个中断响应的过程。但是从触发的对象来看,系统调用是进程主动请求切换的,而异常和硬中断则是被动的。
-
用户态和内核态的切换
2020-07-28 17:21:27用户态和内核态的切换 我们经常说,一个程序依赖DMA设备来减少用户态和内核态的切换次数,以此来提升性能。那么对于它的细节是如何进行的呢?本篇,博主与你一起研究一下用户态和内核态的划分,以及这件事情的... -
用户态和内核态的切换耗费时间的原因
2020-06-12 20:30:46总的来说,就是线程切换或者加锁解锁都是因为需要用户态和内核态的切换,从而导致的开销大。 应用程序的执行需要依托内核提供的资源,包括CPU、存储、IO等,因此内核提供了一个统一的接口, 也就是系统调用,一个... -
Linux | 为什么用户态和内核态的切换耗费时间?
2019-04-01 13:41:16思考这个问题的导火线是,看到java中的synchronized关键字。 经过编译,synchronized标注的函数会加一个读写锁,一般不推荐使用,因为加锁解锁...上面涉及的内核态与用户态切换时系统调用,内核也相当于一个软件,... -
用户态和内核态之间的切换
2021-03-19 23:30:15用户态和内核态之间的切换 切换方式 从用户态到内核态切换可以通过三种方式,或者说会导致从用户态切换到内核态的操作: 系统调用,这个上面已经讲解过了,在我公众号之前的文章也有讲解过。其实系统调用本身就是... -
操作系统 | 用户态和内核态的切换(中断、系统调用与过程(库函数)调用)
2021-08-12 16:39:35文章目录过程调用系统调用过程调用和系统调用的区别 过程调用 过程调用也就是 微机原理里的 CALL 、编程时调用的 库函数 。由于库函数调用是基于C库的,因此也就不可能用于内核空间的驱动程序对设备的操作。 系统... -
Linux | 用户态和内核态的切换耗费时间的原因
2020-07-30 10:49:37最近看到的一个问题,Java线程切换为什么成本会高,由于Java线程的切换是需要用户态和内核态转换的,在学习计算机操作系统时,状态转换成本高就被当做常识,那么为什么会成本高,记录在这方便复习 (此外,java程序... -
用户态和内核态:用户态线程和内核态线程有什么区别?
2020-10-24 16:36:04文章来源于 拉钩教育 重学操作系统 林䭽 用户态和内核态:用户态线程和内核态线程有什么区别? 什么是用户态和内核态 Kernel 运行在超级权限模式(Supervisor Mode)下,所以拥有很高的权限。按照权限管理的原则,... -
操作系统:用户态和内核态,linux一切皆文件,进程线程切换
2021-10-12 09:24:51用户态和内核态什么是用户态,什么是内核态用户态到内核态的切换2. linux一切皆文件linux文件目录结构linux一切皆文件3. 进程切换,与线程切换的开销为什么线程切换的开销小于进程切换的开销。 1.用户态和内核态 ... -
Linux进程从用户态到内核态的切换
2021-05-09 05:43:15快凌晨吃了宵夜,分分钟搞明白了.TSSLinux没有使用intel原始预设的为每个进程设置一段TSS这中方案作为进程切换时保存堆栈信息,CPU寄存器等进程上下文的方案.intel这种设计导致在进程切换时开销很大,因此Linux... -
操作系统基础知识用户态和内核态的区别
2020-10-28 15:44:10这节课给你带来了一道非常经典的面试题目:用户态线程和内核态线程有什么区别? 这是一个组合型的问题,由很多小问题组装而成,比如: 用户态和内核态是什么? 用户级线程和内核级线程是一个怎样的对应关系? ... -
Java架构直通车——为什么线程切换会导致用户态与内核态的切换?
2020-05-13 16:26:20上下文切换的开销互斥锁与自旋锁为什么线程切换会导致用户态与内核台的切换? 什么是上下文切换?上下文切换的时机? CPU通过分配时间片来执行任务,当一个任务的时间片用完,就会切换到另一个任务。在切换之前会... -
内核态到用户态切换分析(一)
2016-05-15 23:56:41引言:本文主要分析从内核态到用户态的切换,同时理清内核线程、用户空间进行之间的关系。内核进行一系统初始化后,会进入到rest_init,首先会产生一个kernel_init的内核线程,最终切换到用户空间的init进程,从而... -
IO操作底层调用过程 | 用户态切换内核态原理 | 中断概念
2021-05-19 21:57:35IO操作底层调用过程|内核|中断| 做后端的程序员都知道我们编写的程序主要分方法程序和IO...我们的程序分为 系统程序kernel(内核程序,操作系统自带的程序) 和 用户程序(app之类的我们自己放在系统程序)。 系统程序k -
Linux的内核态与用户态
2021-12-12 22:36:02引言:最近在看Java内置锁的实现时看到重量级锁的性能开销较大,主要因为使用重量级锁需要用到一个pthread_mutex_lock系统调用,导致Java程序需要在用户态和内核态之间切换,由于不太了解用户态和内核态到底是什么,... -
Java线程中的用户态和内核态
2021-05-04 14:26:35内核态用户态是什么? 操作系统对程序的执行权限进行分级,分别为用户态和内核态。用户态相比内核态有较低的执行权限,很多操作是不被操作系统允许的,简单来说就是用户态只能访问内存,防止程序错误影响到其他程序,而... -
用户态和内核态的区别
2018-08-30 09:10:25用户态和内核态的区别 你是世界的一杆称,一头重一头轻。 –RuiDer --大侄子的纪念日2018-8-27 before 本篇文章是我在学习Java的synchornized锁底层原理时碰到的一个知识点,了解过synchornized底层... -
用户态线程和内核态线程有什么区别?
2021-04-23 17:46:50什么是用户态和内核态? Kernel 运行在超级权限模式(Supervisor Mode)下,所以拥有很高的权限。按照权限管理的原则,多数应用程序应该运行在最小权限下。因此,很多操作系统,将内存分成了两个区域: 内核空间... -
线程池你真的懂了吗,什么是线程上下文切换?用户态和内核态?
2020-07-08 13:17:38并且当前线程的任务可能并没有执行完毕,所以在进行切换时需要保存线程的运行状态,以便下次重新切换回来时,能够继续切换之前的状态运行,这个过程就要涉及到用户态和内核态的切换。 什么是用户态和内核态? 当在... -
操作系统:为什么要区分用户态和内核态
2022-03-11 12:57:41文章目录前言一、用户态和内核态的区分二、为什么要区分用户态和内核CPU指令集权限三、用户态和内核态切换切换开销用户态到内核态切换的场景总结 前言 这篇文章记录笔者对于操作系统用户态和内核态的复习整理 一、... -
用户态线程与内核态线程的优缺点
2021-12-25 17:30:50与内核协作成本高:比如这种线程完全是用户空间程序在管理,当它进行I/O的时候,无法利用到内核的优势,需要频繁进行用户态到内核态的切换。 线程间协作成本高:设想两个线程需要通信,通信需要I/O,I/O需要系统调用... -
系统调用原理与用户态以及内核态相互切换过程,以linux系统为主
2019-04-14 11:09:03是沟通用户与内核之间沟通的桥梁。 我们知道我们计算机几乎所有的硬件等资源都由我们的操作系统管理,不论是我们还是我们自己编写的软件再或者用户的应用软件对计算机硬件或者其他资源进行操作时 都需要操作系统的... -
TCP/IP协议栈到底是内核态的好还是用户态的好?
2018-06-02 08:10:51”这根本就是一个错误的问题,问题的根源在于,干嘛非要这么刻意地去区分什么内核态和用户态。 引子 为了不让本人成为干巴巴的说教,在文章开头,我以一个实例分析开始。 最近一段时间,我几乎每天深夜都在做... -
一文理解JVM线程属于用户态还是内核态
2021-06-03 00:16:44Linux操作系统的体系架构分为用户态和内核态(或者用户空间和内核)。用户态与内核态内核从本质上看是一种软件——控制计算机的硬件资源,并提供上层应用程序运行的环境。用户态即上层应用程序的活... -
Synchronized 耗时的原因,用户态切换内核态耗时的原因
2019-12-05 11:45:50Synchronized 是基于底层操作系统的 Mutex Lock 实现的,每次获取和释放锁操作都会带来用户态和内核态的切换,从而增加系统性能开销。 用户态切换内核态的过程如下 开销的地方 1、切换线程上下文,需要保护和... -
内核态线程,进程和CPU
2020-12-15 15:16:38用户态和内核态的概念 —>内核态: CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序 —>用户态: 只能受限的访问内存, 且不允许访问外围设备. 线程占用CPU...