内核态,用户态
以典型 JAVA / LINUX 为例解释:
- CPU 为了更好的 管理内存 并区分对 CPU指令的执行权限,对程序(普通程序,操作系统,驱动等等)进行分类管理,提供了 Ring0,Ring1,Ring2,Ring3 4个运行级别,其中 Ring0 内存管理范围及操作权限最大,Ring3 管理权限最小
- Linux 针对 CPU 对应设计了两种级别的运行,程序运行在 Ring0 状态级别 及 Ring3 状态级别
- Linux Ring3 用户态:只能受限的访问内存, 且不允许访问外围设备. 占用CPU的能力被剥夺, CPU资源可以被其他程序获取
- Linux Ring0 内核态:CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡. CPU也可以将自己从一个程序切换到另一个程序
- JAVA JVM 针对 LINUX 不同内核,提供不同对接模式,典型的 JAVA 7 Hotspot 程序运行时运行于用户态(Ring3),在发生系统调用(读写文件,写控制台,网络通信时),JVM会自动切换到内核态运行,运行完毕再切会用户态运行
基本上可以认为,内核态针对的是“操作系统”来说,用户态针对 “应用程序”(JVM)来讲
内核线程,用户线程
“内核线程” 通常是由 "操作系统" 针对 "CPU" 内核创立的,线程数一般多余内核数,由“操作系统”采用“时间片轮转法”调度(抢占式调度概念见操作系统)
“用户线程” 通常由应用程序创建,管理,销毁,操作系统对该线程不可见
两者关系:
- 任何程序最后运行指令时,都是由内核线程完成的,那么为什么还区分“内核线程”和“用户线程”呢?
- 实际上这种区分是根据线程当前 运行状态来区分 的,另外就是为了 增强程序扩展能力,将线程进行了区分。
以 JAVA 举例:
- JVM程序启动 ——> JAVA 进程(main 线程启动,JVM 向 LINUX 申请一个内核线程对接)——> LINUX对接给 JVM 一个内核线程 ——> main函数执行(此时程序运行在 LINUX Ring3 态)
- main 打印 LOG 到文件 ——> JVM线程发送指令给LINUX内核线程(调用系统API)——> LINUX 将线程暂停,设置线程运行级别 Ring3 到 Ring0,保存Ring3的状态,并copy相关数据到Ring0内存栈——>LINUX 唤醒“内核线程”继续运行线程,并通知JVM对应的 main 线程休眠 ——> “内核线程”写完文件,设置 Ring0 到 Ring3,唤醒JVM线程,告知文件写完,main()继续运行
由此看出,程序运行时,必须由“内核线程”来执行操作,只是内核线程运行在了不同的状态,这就是状态区分
早期的LINUX 采用 Linux pthread,即一个JVM程序启动后,LINUX只分配一个内核线程给该JVM使用,JVM虽然启动的多线程,但是这些只能轮替使用这一个“内核线程”执行指令。一旦某个线程执行文件操作,像上面写LOG,其他线程只能等待,因为内核线程只有一个
LINUX2.6 以后系统采用了 NPTL (Native POSIX Thread Library) ,LINUX 启动内核线程后,提供对外接口 (也叫轻量级进程 LWP),一个内核线程对应一个 LWP ,JVM启动程序后,进程对应着多个LWP (即一个进程对应多个内核线程),而 JVM Hotspot采用的是直接调用操作系统启动线程,所以 JAVA 7 hotspot的 JVM线程 和操作系统线程 是 1:1 对应
综上
简单说就是:JAVA启动一个线程就是调用一个 LWP 对接到一个内核线程。(JAVA 与 内核线程 是 1:1 就是一个JAVA线程执行时真实对应一个内核线程)