java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > 操作系统的内核态和用户态场景

操作系统的内核态和用户态场景详解

作者:找不到、了

这篇文章主要介绍了操作系统的内核态和用户态场景,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

日常开发中,不知道有没有思考过这样的问题?

为什么Java的new Thread().start()只需几微秒,而Linux的fork()进程却要毫秒级?"

"为何Redis单线程却能支持10万QPS,而你的Java多线程程序卡在8千?"

或者如下图所示:

| 操作                | 耗时        | 状态切换次数 |
|---------------------|-------------|--------------|
| Java方法调用        | 3 ns        | 0            |
| 系统调用(read)      | 100 ns      | 2            |
| 线程上下文切换      | 1 μs        | 2+           |
| 进程创建(fork)      | 1 ms        | 10+          |

Java 方法调用为什么快?(3 ns)

纯用户态操作

直接跳转

硬件加速

而对于read方法为什么这么慢?可参考如下:

接下来就随着这篇文章来深入了解程序运行在操作系统里面的原理。

用户态和内核态是操作系统的两种运行状态,用于保护系统资源和确保安全。程序通过系统调用、异常中断从用户态进入内核态。

系统调用是进程主动请求操作系统服务的方式,涉及CPU上下文切换,包括用户栈到内核栈的切换,并在完成后返回用户态。

下面将从硬件实现、操作系统原理及JVM设计三个层次进行深度剖析。

1、硬件层

分为特权级与保护机制,关于硬件这部分可以进行简单的了解。

1.1、CPU特权级(Ring Model)

如下图所示:

现代CPU通常采用多级特权环(x86架构为Ring 0~Ring 3):

关键点:硬件强制隔离用户态与内核态,任何越权操作都会触发CPU异常。

1.2、内存保护:MMU与页表

稍微扩展下:

U/S位(bit 2):控制访问权限

示例:Linux进程地址空间布局
用户态可访问:
  0x00000000-0x7fffffff ┌───────────────┐
                        │    用户空间    │
内核态独占:
  0xc0000000-0xffffffff ┌───────────────┐
                        │    内核空间    │

1.3、调用流程

当执行open()系统调用时:

如用户态代码访问内核态内存的时候:

// 用户态尝试读取内核地址会导致段错误
void *kernel_addr = (void *)0xffff888000000000;
printf("%d", *(int *)kernel_addr); // 触发#PF(Page Fault)异常

小结

从硬件特权级到JVM的巧妙封装,每一层设计都在平衡安全与效率。现代Java生态(如GraalVM、Project Loom)正在进一步模糊这一界限,但底层原理仍是性能优化的核心知识。

2、操作系统状态

2.1、分类

如下图所示:

用户态和内核态是操作系统的两种运行状态。

内核态:

定义:操作系统内核运行的特权模式。

特点

处于内核态的 CPU 可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态。

用户态:

定义:应用程序运行的普通权限模式。

特点

处于用户态的 CPU 只能访问受限资源,不能直接访问内存等硬件设备,不能直接访问内存等硬件设备,必须通过「系统调用」陷入到内核中,才能访问这些特权资源。

2.2、切换方式

如下图所示:

分为系统调用、中断和异常三种切换方式。

2.3、系统调用

系统调用实际上是一个软件中断,它将执行的上下文从用户模式切换到内核模式。操作系统内核作为更高的特权级别,可以访问保护的内存区域和硬件资源。这是一个非常重要的安全机制,因为它阻止了用户程序直接访问硬件和敏感信息。

以下是一些常见的系统调用:

1、文件操作:

2、进程管理:

3、内存管理:

4、设备管理:

5、通信:

当程序发出系统调用时,它会提供一个系统调用的编号和一组参数来指定操作系统需要执行的具体任务。然后,CPU会将执行上下文切换到内核模式,并开始执行与编号对应的系统调用

当发出系统调用的时候,如下图所示:

3、在Java领域的体现

操作系统内核态与用户态的划分是计算机系统安全的基石,而Java作为运行在用户态的高级语言,其与操作系统的交互涉及复杂的底层机制。

3.1. JVM与操作系统的交互

如下图所示:

JVM本身:大部分运行在用户态,但会通过系统调用与内核交互

系统调用示例

3.2. Java Native Interface (JNI)

3.3. 内存管理

3.4. 多线程实现

3.5. 性能考量

系统调用开销:频繁的I/O操作会导致用户态/内核态切换,影响性能

优化技术

4、系统调用

系统调用与进程管理,如下图所示:

更详细的调用可参考:

4.1. 系统调用(Syscall)

以Linux的read()系统调用为例:

性能开销:一次系统调用约需100~300ns,主要消耗在寄存器保存/恢复和缓存失效(TLB flush)。

1、用户态与内核态的缓存区

如下所示:

1.用户态缓存区

来源

用户程序(如 C/C++ 的stdio缓冲、Java 的BufferedReader)会维护自己的用户空间缓冲区。

作用

减少系统调用次数,提高性能。例如:用户程序调用fwrite()时,数据先写入用户缓冲区,达到一定大小后才触发系统调用(write())。若用户缓冲区未命中(无数据),才会进入内核态。

2.内核态缓存区

来源

操作系统内核维护页缓存(Page Cache)和网络栈缓冲区。

作用

2、调用的流程

触发方式:用户程序通过read()/write()等系统调用进入内核态。

流程

1、检查用户缓冲区

若用户程序的缓冲区有数据,直接返回(无需进入内核)。

2、进入内核态

若用户缓冲区无数据,触发系统调用(如read()),进入内核态。

3、内核处理请求

检查内核缓存(Page Cache):若有数据,直接返回给用户态。若无数据:发起物理IO(磁盘/网络),等待硬件完成。

3、内核如何与硬件交互

1、内核的缓存机制

Page Cache

当用户读取文件时,内核会先检查 Page Cache 是否有数据。若无数据,内核发起磁盘IO,将数据读入 Page Cache,再返回给用户。

网络栈缓冲区

当用户程序发送网络数据时,内核将数据写入TCP 发送缓冲区,由网络协议栈异步发送。

2、硬件IO流程

更多关于I/O介绍,可参考:不同网络I/O模型的原理

1、磁盘IO

内核通过块设备驱动(如ext4文件系统的驱动)与硬盘通信。数据通过DMA(直接内存访问)传输,绕过CPU,提高效率。

2、网络IO

内核通过网卡驱动与网卡通信。数据通过零拷贝技术(如sendfile()直接从磁盘到网卡,减少内存拷贝。

4.2. 线程与进程的实现

轻量级进程(LWP):Java线程对应内核线程(1:1模型,通过clone()系统调用创建)

调度时机

4.3、网络IO的特殊性

1、网络数据传输流程

用户程序调用send():

内核处理

接收数据

5、注意事项

5.1. 减少模式切换的开销

5.2. 内核旁路(Kernel Bypass)技术

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

理解这些概念有助于Java开发者编写更高效、更稳定的应用程序,特别是在性能敏感或系统级编程场景中。

您可能感兴趣的文章:
阅读全文