写给Java程序员看的,CPU 上下文切换、用户态、内核态、进程与线程上下文切换(转)
1、概述
JDK源码中很多Native方法,特别是多线程、NIO部分,很多功能需要操作系统功能支持,作为Java程序员,如果要理解和掌握多线程和NIO等原理,就需要对操作系统的原理有所了解。
2、CPU 上下文切换
多任务操作系统中,多于CPU个数的任务同时运行就需要进行任务调度,从而多个任务轮流使用CPU。
从用户角度看好像所有的任务同时在运行,实际上是多个任务你运行一会,我运行一会,任务切换的速度很快,我们感觉不到而已。
而每个任务运行前,CPU需要知道从哪里加载这个任务的程序,还需要知道从程序哪行开始执行,这就要求OS事先帮任务设置好CPU的 寄存器
和程序计数器
。
CPU执行任务必须依赖的环境称为 CPU上下文
CPU 上下文切换
,就是先把前一个任务的 CPU 上下文(也就是 CPU 寄存器和程序计数器)保存起来,然后加载新任务的上下文到这些寄存器和程序计数器,最后再跳转到程序计数器所指的新位置,运行新任务。
CPU的上下文切换分为几种场景:进程上下文切换、线程上下文切换、中断上下文切换
2.1、用户态、内核态
Linux按特权等级,将进程的运行空间分为 内核空间
和 用户空间
。
Intel x86架构使用了4个级别来标明不同的特权级权限。
R0实际就是内核态
,拥有最高权限,可以直接访问所有资源(包括外围设备,例如硬盘,网卡等)。而一般应用程序处于R3状态–用户态
。
进程在用户空间运行时,被称为进程的 用户态
,而陷入内核空间的时候,被称为进程的 内核态
。
R0最高可以读取R0-3所有的内容,R1可以读R1-3的,R2以此类推,R3只能读自己的数据。
2.2、为什么分内核态和用户态
假设没有这种内核态和用户态之分,程序随随便便就能访问硬件资源,比如说分配内存,程序能随意的读写所有的内存空间,如果程序员一不小心将不适当的内容写到了不该写的地方,就很可能导致系统崩溃。
用户程序进行系统调用后,操作系统执行一系列的检查验证,确保这次调用是安全的,再进行相应的资源访问操作。内核态能有效保护硬件资源的安全。
2.3、系统调用
从用户态到内核态的转变,需要通过系统调用来完成。比如,当我们查看文件内容时,就需要多次系统调用来完成:首先调用 open() 打开文件,然后调用 read() 读取文件内容,并调用 write() 将内容写到标准输出,最后再调用 close() 关闭文件。
系统调用会将CPU从用户态切换到核心态,以便 CPU 访问受到保护的内核内存。
系统调用的过程会发生 CPU 上下文的切换,CPU 寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU 寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。
而系统调用结束后,CPU 寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,其实是发生了两次 CPU 上下文切换。
注意:系统调用过程中,并不会涉及到虚拟内存等进程用户态的资源,也不会切换进程。
系统调用过程通常称为特权模式切换,而不是进程上下文切换。
3、进程上下文切换
进程上下文切换跟系统调用又有什么区别呢?
首先,进程是由内核来管理和调度的,进程的切换只能发生在内核态。所以,进程的上下文不仅包括了虚拟内存、栈、全局变量等用户空间的资源,还包括了内核堆栈、寄存器等内核空间的状态。
因此,进程的上下文切换就比系统调用时多了一步:在保存当前进程的内核状态和 CPU 寄存器之前,需要先把该进程的虚拟内存、用户栈等保存下来;而加载下一个进程的内核态后,还需要加载这个进程的虚拟内存和用户栈。
根据 Tsuna 的测试报告,每次上下文切换都需要几十纳秒到数微秒的 CPU 时间,在进程上下文切换次数较多的情况下,这个时间对于CPU来说是相当可观的,会大大缩短CPU真正用于运行进程的时间。
3.1、什么时候会切换进程上下文?
只有在进程调度的时候,才需要切换上下文。Linux 为每个 CPU 都维护了一个就绪队列,将活跃进程(即正在运行和正在等待 CPU 的进程)按照优先级和等待 CPU 的时间排序,然后选择最需要 CPU 的进程,也就是优先级最高和等待 CPU 时间最长的进程来运行。
新进程在什么时候才会被调度到 CPU 上运行呢? 1.运行中的进程执行完终止了,CPU 会释放出来,新的基础进程就可以被调度到CPU上运行了。 2. 运行中的进程时间片用完,进程被挂起 3. 运行中的进程资源不足,进程被挂起 4. 运行中的进程执行Sleep方法主动挂起 5. 新进程优先级更高,运行中的进程被挂起 6. 发生硬件中断,运行中的进程会被中断挂起,转而执行内核中的中断服务程序。
4、线程上下文切换
线程是调度的基本单位,而进程则是资源拥有的基本单位。
所谓内核中的任务调度,实际上的调度对象是线程;而进程只是给线程提供了虚拟内存、全局变量等资源。
当进程只有一个线程时,可以认为进程就等于线程,当进程拥有多个线程时,这些线程会共享进程的虚拟内存和全局变量等资源。这些资源在上下文切换时是不需要修改的。
线程也有自己的私有数据,比如栈和寄存器等,这些在上下文切换时也是需要保存的。
线程的上下文切换其实就可以分为两种情况:
- 两个线程属于不同进程,因为资源不共享,切换过程和进程上线文切换一样
- 两个线程属于同一个进程,只需要切换线程的私有数据、寄存器等不共享的数据
5、总结
CPU上线文切换,切换寄存器、程序计数器
进程上线文切换,切换虚拟内存、用户栈
线程上下文切换,2种情况:(1)线程私有数据(比如线程栈、程序计数器等);(2)、(1)+ 线程资源 ;
系统调用:需要进行线程上下文切换,但不是进程上下文切换
R0实际就是内核态
,拥有最高权限,可以直接访问所有资源(包括外围设备,例如硬盘,网卡等)。
应用程序处于R3状态–用户态
。
系统调用会进行 内核态
、用户态
转换
- mono-3.4.0 源码安装时出现的问题 [do-install] Error 2 [install-pcl-targets] Error 1 解决方法
- 安装 IronPython
- Python 项目实践三(Web应用程序)第一篇
- Centos 7.0 安装Mono 3.4 和 Jexus 5.6
- 第一个IronPython程序(之二)
- Python 项目实践二(下载数据)第四篇
- Python 项目实践二(下载数据)第三篇
- 体验扁平化的WordPress 后台管理界面
- Python 项目实践二(生成数据)第二篇
- 重新审视SqlDataReader的使用
- Python 项目实践二(生成数据)第一篇
- 删除 WordPress 导航菜单的多余 CSS 选择器
- 删除 WordPress 导航菜单的多余 CSS 选择器
- 使用asp.net 2.0的CreateUserwizard控件如何向自己的数据表中添加数据
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- 浅谈pycharm下找不到sqlalchemy的问题
- PHP实现常用排序算法的方法
- 利用Python实现原创工具的Logo与Help
- PHP+ajax实现上传、删除、修改单张图片及后台处理逻辑操作详解
- 浅谈python下tiff图像的读取和保存方法
- Jmeter(二十五) - 从入门到精通 - JMeter函数 - 下篇(详解教程)
- 解决vscode python print 输出窗口中文乱码的问题
- 对python3新增的byte类型详解
- Python实现的特征提取操作示例
- Linux进程间通信方式之socket使用实例
- python实现图片识别汽车功能
- Mac下Anaconda的安装和使用教程
- linux ubuntu中安装、卸载和删除python-igraph的方法教程
- python实现汽车管理系统
- tp5框架基于ajax实现异步删除图片的方法示例