Java线程中的用户态和内核态解读
作者:梨瓜
Java线程中用户态和内核态
内核态用户态是什么?
操作系统对程序的执行权限进行分级,分别为用户态和内核态。用户态相比内核态有较低的执行权限,很多操作是不被操作系统允许的,简单来说就是用户态只能访问内存,防止程序错误影响到其他程序,而内核态则是可以操作系统的程序和普通用户程序
内核态: cpu可以访问计算机所有的软硬件资源
用户态: cpu权限受限,只能访问到自己内存中的数据,无法访问其他资源
为什么要有用户态和内核态?
系统需要限制不同的程序之间的访问能力,防止程序获取不相同程序的内存数据,或者外围设备的数据,并发送到网络,所有cpu划分出两个权限等级用户态和内核态
用户态和内核态的转换
用户应用程序在用户态下,但是如果需要执行一些操作例如申请内存,网络读写时,自己的权限不够,就需要转换到内核态去让内核去帮忙干一些事情,下面三个方式会进行转换
1.系统调用
这是用户态主动要求切换到内核态的一种方式,用户进程通过系统调用申请使用操作系统提供的服务程序完成工作,比如前列中fork()实际上就是执行了一个创建新进程的系统调用。而系统调用的机制其核心还是使用了操作系统为用户特别开发的一个中断来实现,列表liunx的int 80h中断
2.异常
当cpu在运行在用户态下的程序时,发生了些某些事先不可知的异常,这时会触发由当前运行进程切换此异常的内核相关程序中,也就转到了内核态
3.外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信息,这时CPU会暂停执行下一条即将执行的指令而去执行与中断信号对应的处理程序,如果先前执行的指令时用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换,比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等
Java线程是用户态还是内核态
Java线程在jdk1.2之前,是基于称为"绿色线程"的用户线程实现,但从JDK1.3起,主流平台上的"主流"商用Java虚拟机的线程模型普遍都被替换为基于操作系统原生原生线程模型来实现,即采用1:1的线程模型
操作系统支持怎么样的线程模型,在很大程度想回影响上面的Java虚拟机的线程是怎么映射的,这一点在不同的平台上很难达成一致,因此《Java虚拟机规范》中才不去限定Java线程需要使用哪种线程模型来实现。
1.证明java线程不是纯粹用户级线程:java中有个fork join框架,这个框架是利用多处理技术进行maprudce的工作,也就证明了内核是可以感知到用户线程的存在,因此才会将多个线程调度到多个处理器中。还有,java应用程序中的某个线程阻塞,是不会引起整个进程的阻塞,从这两点看,java线程绝不是纯粹的用户级线程。
2.再来证明java线程不是纯粹内核级线程:如果使用纯粹的内核级线程,那么有关线程的所有管理工作都是内核完成的,用户程序中没有管理线程的代码。显然,java线程库提供了大量的线程管理机制,因此java线程绝不是纯粹的内核级线程。 综上,java线程是混合型的线程模型,一般而言是通过lwp将用户级线程映射到内核线程中
Java线程用户态内核态切换
cpu采用时间轮片机制给每个线程分发执行时间片来让线程执行,如果线程在进行上下文切换,堵塞,时间片用完,睡眠,\等情况,就会发生用户态和内核态的切换
线程切换的调度器存放于内核中,中断处理也在内核。,从这个角度来说,如果线程当前处于用户态,若要发生线程切换,必然是要先进入内核态,发生状态切换的。线程切换的原因还可能是其他类型的中断,或者线程自身主动进入等待和睡眠.这些情况无一例外会进入内核。
但是线程当前就处于内核态,也可能一样中断,或者主动进入等待或者睡眠,这也是一样会发送的,但就是没有了用户态和内核态的状态切换了
总而言之线程切换和状态切换没有直接的关联,,只不过线程切换一般都放到内核实现而已,当然也还有所谓的用户级线程,也就是在用户态直接切换线程的栈和寄存器而已,这也无需切换到内核态
用户态和内核态切换性能问题
当发生用户态到内核态的切换时,会发生如下过程:
- 设置处理器至内核态。
- 保存当前寄存器(栈指针、程序计数器、通用寄存器)。
- 将栈指针设置指向内核栈地址。
- 将程序计数器设置为一个事先约定的地址上,该地址上存放的是系统调用处理程序的起始地址。
- 而之后从内核态返回用户态时,又会进行类似的工作。
用户态和内核态之间的切换有一定的开销,如果频繁发生切换势必会带来很大的开销,所以要想尽一切办法来减少切换
避免频繁切换
因为线程的切换会导致用户态和内核态之间的切换,所以减少线程切换也会减少用户态和内核态之间的切换。那么如何减少线程切换呢?
- 无锁并发编程。多线程竞争锁时,加锁、释放锁会导致比较多的上下文切换
- CAS算法。使用CAS避免加锁,避免阻塞线程
- 使用最少的线程。避免创建不需要的线程协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换
用户态和内核态的区别
操作系统需要两种CPU状态
- 内核态(Kernel Mode):运行操作系统程序,操作硬件
- 用户态(User Mode):运行用户程序
指令划分
- 特权指令:只能由操作系统使用、用户程序不能使用的指令。 举例:启动I/O 内存清零 修改程序状态字 设置时钟 允许/禁止终端 停机
- 非特权指令:用户程序可以使用的指令。 举例:控制转移 算数运算 取数指令 访管指令(使用户程序从用户态陷入内核态)
特权级别
- 特权环:R0、R1、R2和R3
- R0相当于内核态,R3相当于用户态;
- 不同级别能够运行不同的指令集合;
CPU状态之间的转换
- 用户态--->内核态:唯一途径是通过中断、异常、陷入机制(访管指令)
- 内核态--->用户态:设置程序状态字PSW
内核态与用户态的区别
内核态与用户态是操作系统的两种运行级别,当程序运行在3级特权级上时,就可以称之为运行在用户态。因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态;
当程序运行在0级特权级上时,就可以称之为运行在内核态。
运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态(比如操作硬件)。
这两种状态的主要差别是:
- 处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理器是可被抢占的
- 处于内核态执行时,则能访问所有的内存空间和对象,且所占有的处理器是不允许被抢占的。
通常来说,以下三种情况会导致用户态到内核态的切换
1、系统调用
这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。比如前例中fork()实际上就是执行了一个创建新进程的系统调用。
而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。
用户程序通常调用库函数,由库函数再调用系统调用,因此有的库函数会使用户程序进入内核态(只要库函数中某处调用了系统调用),有的则不会。
2、异常
当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
3、外围设备的中断
当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,
如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。