image.png

32位系统中,一个linux进程最大可以申请4GB地址空间,其中3g被划分为用户空间,1g被划分为内核空间。**内核空间的数据是进程间共享的(所有进程的内核空间映射的内存地址是一样的)。**

用户态与内核态

:::success

  • 当一个进程在执行用户自己的代码时处于用户运行态(用户态),此时特权级最低,为3级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态。
  • 当一个进程因为系统调用陷入内核代码中执行时处于内核运行态(内核态),此时特权级最高,为0级。执行的内核代码会使用当前进程的内核栈,每个进程都有自己的内核栈。
  • 用户态只能受限的访问内存,且不允许访问外围设备,不容许访问内核空间,占用CPU的能力被剥夺,CPU资源可以被其他程序获取; :::

用户运行一个程序,该程序创建的进程开始时运行自己的代码,处于用户态。
如果要执行文件操作、网络数据发送等操作必须通过write、send等系统调用,这些系统调用会调用内核的代码。进程会切换到Ring0,然后进入3G-4G中的内核地址空间去执行内核代码来完成相应的操作。内核态的进程执行完后又会切换到Ring3,回到用户态。这样,用户态的程序就不能随意操作内核地址空间,具有一定的安全保护作用。这说的保护模式是指通过内存页表操作等机制,保证进程间的地址空间不会互相冲突,一个进程的操作不会修改另一个进程地址空间中的数据。

用户态到内核态的切换

以下的三种情况会导致用户态到内核态的切换

  1. 系统调用** (用户进程主动发起)**
  2. 异常(被动),当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理异常的内核相关程序中,也就转到了内核态
  3. 外围设备的中断信号(被动)

image.png

用户态和内核态的切换涉及的CPU上下文切换

第一次切换:

CPU寄存器里原来用户态的指令位置,需要先保存起来。接着,为了执行内核态代码,CPU寄存器需要更新为内核态指令的新位置。最后才是跳转到内核态运行内核任务。

第二次切换:

而系统调用结束后,CPU寄存器需要恢复原来保存的用户态,然后再切换到用户空间,继续运行进程。所以,一次系统调用的过程,其实是发生了两次CPU上下文切换。

一个java的线程在运行的时候是内核态还是用户态?

其实站在java程序员的角度只需要关注系统调用,因为系统调用可以认为是用户进程主动发起的,比如调用线程的park()方法会对应到一个os的一个函数,从而使当前线程进入了内核态;再比如遇到synchronized关键字如果是重量锁则会调用pthread_mutex_lock()这样我们的线程也会切换到内核态;当执行完系统调用切换到用户态;

Java进程涉及用户态和内核态的切换的操作

  • 线程切换
  • java程序的加锁和解锁
  • 内存分配malloc() ,java中的堆外内存使用

    Java操作堆外内存的使用场景

  • 多用于网络编程中,实现zero copy ,数据不需要再native memory和jvm memory中来回copy

  • 由于构造和析构DirectBuffer时间成本高,建议使用缓冲池,参见netty的实现。DirectByteBuffer的创建和回收,涉及到用户态和内核态的切换