一. 前言
想起很多年前被问到了用户态和内核态的相关问题,当时一知半解,但是工作这么些年,也极少听到这个概念,直到最近碰到后,终于决定以Java的角度,真正的了解一下这2个状态的实际作用。
二. 内核态的概念和本质
2.1 概念与体现
内核主要控制计算机的硬件资源,并且提供运行环境给上层应用程序。
// 概念 :
态是一种操作系统的运行级别.
- 内核态通常是管理程序运行的状态,具有更高的级别,在这个状态可以执行所有的指令,以及使用所有的资源
- 用户态通常是用户应用的运行状态,权限较低,不可以使用特权指令,也不能直接使用系统资源,只能访问各自的存储空间
// 行为 :
- 内核可以控制计算机的硬件资源,并且提供应用程序的运行环境
- 通过内核态调用内核的核心资源
// 原因 :
- 权限的管理 : 避免高危指令执行出现问题,引发系统崩溃。高风险指令只能有限的开发给用户。
- 避免资源消耗 :系统资源是有限的,要减少访问系统资源的操作,避免过多的消耗
// 场地 :
CPU 执行用户态和内核态指令,标识着当前CPU的运行态 (运行级别)
其他基础概念 :
- 系统调用 : 内核为上层应用提供访问的接口,用于访问内核中的资源 系统调用是操作系统的最小功能资源
- 库函数 : 是对系统调用的封装,再通过简单的接口呈现给用户
- Shell : 特殊的应用程序,可以通过 Shell 命令方便的进行系统调用,并且可以集成到编程体系中
区别和对比
// 超级管理员与内核态的区别 :
超级管理员是特殊的计算机用户,主要是软件层面的一种角色。 超级管理员不一定在内核态处理业务。
2.2 内核态和用户态分级
功能区别
// 用户态功能 :
汇编 / 编译 / 编辑 / shell / API 函数 (读写)
// 内核态功能 :
进程管理 / 存储管理 / 文件管理 / 设备管理
2.3 用户态到内核态的切换场景
用户态 -> 内核态
- 系统调用 :通过任何途径进行系统调用的时候,都会发生2态的装欢
- 异常事件 :当发生一些系统级别的异常事件时 , CPU 会处理异常事件,从而发生切换
- 外围系统中断 : 当外围系统完成后,会向 CPU 发送中断信号 ,此时 CPU 开始处理中断信号
内核态 -> 用户态
- 设置程序状态字 PSW
总结 :内核态可以随意切换到用户态。用户态只能通过系统调用和中断来到内核态
三. Java 中内核态和用户态的涉及情况
3.1 Java 上内核态的使用
- 线程进行上下文切换,堵塞,时间片使用完成,睡眠等操作时,会发生多态和切换
- 线程切换的调度器在内核中
- 线程切换和态切换没有直接关系,但是多数切换都是放在内核中进行
3.2 多态转换的流程
- 设置处理器至内核态。
- 保存当前寄存器(栈指针、程序计数器、通用寄存器)。
- 将栈指针设置指向内核栈地址。
- 将程序计数器设置为一个事先约定的地址上,该地址上存放的是系统调用处理程序的起始地址。
- 而之后从内核态返回用户态时,又会进行类似的工作。
3.2 多态切换会有哪些风险
无锁并发编程。多线程竞争锁时,加锁、释放锁会导致比较多的上下文切换 CAS算法。使用CAS避免加锁,避免阻塞线程 使用最少的线程。避免创建不需要的线程协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
切换耗时原因 :
当发生切换时, 第一个操作点就是保存现场,再在另外的态中恢复现场。这种时间消耗不多,但是如果调用非常频繁,耗时累加也是很多的。
第二个操作点时 ,当切换内核态的时候,内核需要进行额外的校验,检查是否需要调度等,也会占用额外的耗时。
四. JVM 和 Linux 关于内核态和用户态的边界
4.1 底层角度出发
内核栈和内存的隔离
- 堆和栈和Java角度是一致的,在Linux 中,进程/线程都是通过一个 task_struct的实例描述 task_struct`中有一个 stack 用于保存内核栈地址
- 用户态的堆、栈对应用户进程虚拟地址空间里的一个区域
- 内核通过虚拟地址访问权限来限制用户程序访问内存地址
4.2 JVM 层面分析
空间划分 :
- 内核空间 :最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF)由内核态使用
- 用户空间 :较低的 3G 字节(从虚拟地址 0x00000000 到 0xBFFFFFFF)由各个进程使用
进程上下文
- 在上下文中,用户进程会将参数传递给内核,同时内核会保存寄存器值和当前环境信息 用户级上下文: 正文、数据、用户堆栈以及共享存储区 寄存器上下文: 通用寄存器、程序寄存器(IP)、处理器状态寄存器(EFLAGS)、栈指针(ESP) 系统级上下文: 进程控制块task_struct、内存管理信息(mm_struct、vm_area_struct、pgd、pte)、内核栈
PS : 这里就存在上下文切换的场景
Java 中涉及切换的操作
- IO 操作会直接调用系统硬件
- 线程操作 ,线程创建切换直接涉及到多个态的转换
- 创建对象等场景的内存分配
4.3 开发中对内核态和用户态的使用
- C语言中的 sork() 来分配内存
- print() 函数会调用 wirte() 进行系统调用输出字符串
- 申请动态的内存空间
总结
看了很多文档,还是感觉一知半解,没有从实践角度弄懂,到底他们在业务中有什么影响。只能说大概了解了其中的组成。