写给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状态–用户态
。
系统调用会进行 内核态
、用户态
转换
- 【架构拾集】: Android 移动应用架构设计
- Dore 混合应用框架 —— 基于 React Native 的混合应用迁移方案
- Android6.0源码分析之View(二)--measure Android6.0源码分析之View(一)
- 程序员,2017年你的技能树上增加了哪些新技能?
- Android6.0源码分析之View(一)
- 2017 年,我挖的那些开源软件坑
- Android6.0蓝牙开发中获取附近低功耗蓝牙设备结果权限问题分析
- 我的技术投资策略:如何决定学习哪一个新技术的?
- 我的技术投资策略:如何决定学习哪一个新技术的?
- Android6.0源码之蓝牙研究汇总(一)--from初学者
- Android6.0蓝牙协议之OPP
- 简单直白教你理解Java中四大引用强引用,软引用,弱引用,虚引用
- ubuntu系统上配置git
- Android源码编译出错No rule to make...
- 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 文档注释
- Python 技术篇-利用pyqt5库读取剪切板已复制数据的格式类型实例演示,python判断复制文件的文件类型
- 第36期:二叉树的遍历(小白必看)
- Python 技术篇-index()字符串倒叙匹配获取索引,字符串切片反向输出,逆向输出字符串
- JavaScript 技术篇-navigator.permissions读取chrome剪切板权限获取不生效原因:只有在https协议下使用有效。手动设置chrome页面剪切板读取权限方法
- 生产prometheus-operator 监控二进制kubernetes
- Excel 技术篇-跨页签统计某一区域下符合条件值的数量方法,COUNTIF函数、数量统计公式的用法实例演示
- Oracle 数据库用户锁定与解锁,用户锁定最大密码失败次数设置方法,ORA-28000: the account is locked问题解决方法
- TypeScript 注意事项
- Linux下MySQL数据库的备份与还原,mysql快速导入导出数据库实例演示,解决mysql大数据量数据库导出慢的问题
- 前端如何分片上传文件?
- PL/SQL 工具远程连接Oracle数据库方法,plsql免安装oracle客户端直接配置oci实战演示
- Python 技巧篇-官方网站打不开的情况下通过官方获取最新python安装包方法
- MySQL 技术篇- linux下mysql数据库利用binlog文件进行数据回滚实例演示,binlog恢复数据库的两种方式
- Oracle 数据库表空间不足拓展方法实例演示,表空间剩余大小查看,通过新增表空间文件拓展表空间,表空间文件路径查看
- dbvis 数据库连接工具-更新数据库驱动方法示例演示,驱动与数据库版本不匹配问题:Unknown system variable ‘query_cache_size‘解决方法