操作系統(tǒng)的內(nèi)核態(tài)和用戶態(tài)場景詳解
日常開發(fā)中,不知道有沒有思考過這樣的問題?
為什么Java的new Thread().start()只需幾微秒,而Linux的fork()進程卻要毫秒級?"
"為何Redis單線程卻能支持10萬QPS,而你的Java多線程程序卡在8千?"
或者如下圖所示:
| 操作 | 耗時 | 狀態(tài)切換次數(shù) | |---------------------|-------------|--------------| | Java方法調(diào)用 | 3 ns | 0 | | 系統(tǒng)調(diào)用(read) | 100 ns | 2 | | 線程上下文切換 | 1 μs | 2+ | | 進程創(chuàng)建(fork) | 1 ms | 10+ |
Java 方法調(diào)用為什么快?(3 ns)
純用戶態(tài)操作:
- JVM 直接在用戶空間執(zhí)行方法調(diào)用,無需切換CPU特權(quán)級(無內(nèi)核介入)。
直接跳轉(zhuǎn):
- 通過棧幀和方法表直接跳轉(zhuǎn),現(xiàn)代JVM還會用JIT編譯優(yōu)化(內(nèi)聯(lián)等)。
硬件加速:
- CPU 的流水線、分支預(yù)測等機制可以高效處理這種簡單跳轉(zhuǎn)。
而對于read方法為什么這么慢?可參考如下:

接下來就隨著這篇文章來深入了解程序運行在操作系統(tǒng)里面的原理。
用戶態(tài)和內(nèi)核態(tài)是操作系統(tǒng)的兩種運行狀態(tài),用于保護系統(tǒng)資源和確保安全。程序通過系統(tǒng)調(diào)用、異常或中斷從用戶態(tài)進入內(nèi)核態(tài)。

系統(tǒng)調(diào)用是進程主動請求操作系統(tǒng)服務(wù)的方式,涉及CPU上下文切換,包括用戶棧到內(nèi)核棧的切換,并在完成后返回用戶態(tài)。
下面將從硬件實現(xiàn)、操作系統(tǒng)原理及JVM設(shè)計三個層次進行深度剖析。
1、硬件層
分為特權(quán)級與保護機制,關(guān)于硬件這部分可以進行簡單的了解。
1.1、CPU特權(quán)級(Ring Model)
如下圖所示:

現(xiàn)代CPU通常采用多級特權(quán)環(huán)(x86架構(gòu)為Ring 0~Ring 3):
- Ring 0(內(nèi)核態(tài)):執(zhí)行所有指令(如
LGDT加載全局描述符表、HLT停機指令) - Ring 3(用戶態(tài)):受限指令會觸發(fā)General Protection Fault(GPF)異常
- 特權(quán)級切換:通過syscall/sysenter(快速系統(tǒng)調(diào)用指令)或中斷門實現(xiàn)
關(guān)鍵點:硬件強制隔離用戶態(tài)與內(nèi)核態(tài),任何越權(quán)操作都會觸發(fā)CPU異常。
1.2、內(nèi)存保護:MMU與頁表
- 用戶態(tài)進程:只能訪問虛擬地址空間中用戶區(qū)(如Linux的
0x00000000~0x7fffffff) - 內(nèi)核態(tài):可訪問全部地址空間(包括內(nèi)核區(qū)
0xc0000000以上) - 頁表權(quán)限位:通過
PTE(Page Table Entry)的U/S位(User/Supervisor)控制訪問權(quán)限
稍微擴展下:
U/S位(bit 2):控制訪問權(quán)限
1:用戶態(tài)可訪問0:僅內(nèi)核態(tài)可訪問
示例:Linux進程地址空間布局
用戶態(tài)可訪問:
0x00000000-0x7fffffff ┌───────────────┐
│ 用戶空間 │
內(nèi)核態(tài)獨占:
0xc0000000-0xffffffff ┌───────────────┐
│ 內(nèi)核空間 │1.3、調(diào)用流程
當(dāng)執(zhí)行open()系統(tǒng)調(diào)用時:
- CPU從用戶態(tài)(Ring 3)進入內(nèi)核態(tài)(Ring 0)
- MMU臨時忽略U/S位檢查
- 內(nèi)核訪問struct file等數(shù)據(jù)結(jié)構(gòu)(位于內(nèi)核內(nèi)存區(qū)域)
- 返回用戶態(tài)時重新啟用U/S位保護
如用戶態(tài)代碼訪問內(nèi)核態(tài)內(nèi)存的時候:
// 用戶態(tài)嘗試讀取內(nèi)核地址會導(dǎo)致段錯誤
void *kernel_addr = (void *)0xffff888000000000;
printf("%d", *(int *)kernel_addr); // 觸發(fā)#PF(Page Fault)異常- CPU檢測到U/S=0的頁在用戶態(tài)被訪問
- 觸發(fā)Page Fault(錯誤碼
0x00000005:用戶態(tài)+讀操作) - 內(nèi)核發(fā)送sigsegv信號終止進程
小結(jié)
從硬件特權(quán)級到JVM的巧妙封裝,每一層設(shè)計都在平衡安全與效率?,F(xiàn)代Java生態(tài)(如GraalVM、Project Loom)正在進一步模糊這一界限,但底層原理仍是性能優(yōu)化的核心知識。
2、操作系統(tǒng)狀態(tài)
2.1、分類
如下圖所示:

用戶態(tài)和內(nèi)核態(tài)是操作系統(tǒng)的兩種運行狀態(tài)。
內(nèi)核態(tài):
定義:操作系統(tǒng)內(nèi)核運行的特權(quán)模式。
特點:
- 可以執(zhí)行所有CPU指令
- 可以訪問全部內(nèi)存空間
- 可以直接操作硬件設(shè)備
- 權(quán)限最高,但風(fēng)險也大
處于內(nèi)核態(tài)的 CPU 可以訪問任意的數(shù)據(jù),包括外圍設(shè)備,比如網(wǎng)卡、硬盤等,處于內(nèi)核態(tài)的 CPU 可以從一個程序切換到另外一個程序,并且占用 CPU 不會發(fā)生搶占情況,一般處于特權(quán)級 0 的狀態(tài)我們稱之為內(nèi)核態(tài)。
用戶態(tài):
定義:應(yīng)用程序運行的普通權(quán)限模式。
特點:
- 只能訪問受限的CPU指令集和內(nèi)存空間
- 不能直接訪問硬件設(shè)備
- 執(zhí)行特權(quán)指令會導(dǎo)致異常
- 安全性高,一個進程崩潰不會影響整個系統(tǒng)
處于用戶態(tài)的 CPU 只能訪問受限資源,不能直接訪問內(nèi)存等硬件設(shè)備,不能直接訪問內(nèi)存等硬件設(shè)備,必須通過「系統(tǒng)調(diào)用」陷入到內(nèi)核中,才能訪問這些特權(quán)資源。
2.2、切換方式
如下圖所示:

分為系統(tǒng)調(diào)用、中斷和異常三種切換方式。
2.3、系統(tǒng)調(diào)用
系統(tǒng)調(diào)用實際上是一個軟件中斷,它將執(zhí)行的上下文從用戶模式切換到內(nèi)核模式。操作系統(tǒng)內(nèi)核作為更高的特權(quán)級別,可以訪問保護的內(nèi)存區(qū)域和硬件資源。這是一個非常重要的安全機制,因為它阻止了用戶程序直接訪問硬件和敏感信息。
以下是一些常見的系統(tǒng)調(diào)用:
1、文件操作:
- open():打開或創(chuàng)建文件
- read():讀取文件內(nèi)容
- write():寫入文件內(nèi)容
- close():關(guān)閉打開的文件
- lseek():移動文件的讀/寫指針
2、進程管理:
- fork():創(chuàng)建新的子進程
- exit():結(jié)束進程
- wait():暫停父進程,直到子進程結(jié)束
- exec():在當(dāng)前進程上下文中執(zhí)行新的程序
3、內(nèi)存管理:
- brk()、sbrk():改變數(shù)據(jù)段的大小
- mmap():創(chuàng)建一個新的映射區(qū)域
- munmap():刪除一個映射區(qū)域
4、設(shè)備管理:
- ioctl():對設(shè)備進行控制
- fcntl():執(zhí)行各種文件操作
5、通信:
- socket():創(chuàng)建一個新的套接字
- bind():將套接字綁定到地址
- listen()、accept():在套接字上監(jiān)聽連接
- connect():發(fā)起到另一套接字的連接
- send()、recv():發(fā)送/接收數(shù)據(jù)
- shutdown():關(guān)閉套接字的部分功能
當(dāng)程序發(fā)出系統(tǒng)調(diào)用時,它會提供一個系統(tǒng)調(diào)用的編號和一組參數(shù)來指定操作系統(tǒng)需要執(zhí)行的具體任務(wù)。然后,CPU會將執(zhí)行上下文切換到內(nèi)核模式,并開始執(zhí)行與編號對應(yīng)的系統(tǒng)調(diào)用。
當(dāng)發(fā)出系統(tǒng)調(diào)用的時候,如下圖所示:

3、在Java領(lǐng)域的體現(xiàn)
操作系統(tǒng)內(nèi)核態(tài)與用戶態(tài)的劃分是計算機系統(tǒng)安全的基石,而Java作為運行在用戶態(tài)的高級語言,其與操作系統(tǒng)的交互涉及復(fù)雜的底層機制。
3.1. JVM與操作系統(tǒng)的交互
如下圖所示:

JVM本身:大部分運行在用戶態(tài),但會通過系統(tǒng)調(diào)用與內(nèi)核交互
系統(tǒng)調(diào)用示例:
- 文件I/O操作(java.io包)
- 網(wǎng)絡(luò)通信(java.net包)
- 線程管理(java.lang.Thread)
3.2. Java Native Interface (JNI)
- 本地方法:通過JNI調(diào)用的本地代碼可能涉及內(nèi)核態(tài)操作
- 風(fēng)險:錯誤的本地代碼可能導(dǎo)致系統(tǒng)不穩(wěn)定
3.3. 內(nèi)存管理
- Java堆內(nèi)存:在用戶態(tài)由JVM管理
- 直接內(nèi)存(NIO):ByteBuffer.allocateDireact()可能涉及內(nèi)核態(tài)的內(nèi)存分配
3.4. 多線程實現(xiàn)
- Java線程:通常映射到操作系統(tǒng)原生線程(1:1模型)
- 線程調(diào)度:最終由操作系統(tǒng)內(nèi)核調(diào)度器在內(nèi)核態(tài)完成
3.5. 性能考量
系統(tǒng)調(diào)用開銷:頻繁的I/O操作會導(dǎo)致用戶態(tài)/內(nèi)核態(tài)切換,影響性能
優(yōu)化技術(shù):
- 緩沖技術(shù)(如BufferedInputStream)
- 批量操作(如NIO的Selector)
- 零拷貝技術(shù)(FileChannel.transferTo)
4、系統(tǒng)調(diào)用
系統(tǒng)調(diào)用與進程管理,如下圖所示:

更詳細的調(diào)用可參考:

4.1. 系統(tǒng)調(diào)用(Syscall)
以Linux的read()系統(tǒng)調(diào)用為例:
- 用戶態(tài)觸發(fā):調(diào)用libc的read()函數(shù)
- 軟中斷:libc通過
int0x80(傳統(tǒng))或Syscall指令(x86-64)陷入內(nèi)核 - 查表跳轉(zhuǎn):CPU根據(jù)中斷描述符表(IDT)找到系統(tǒng)調(diào)用處理程序
- 內(nèi)核執(zhí)行:切換到內(nèi)核棧,執(zhí)行sys_read()函數(shù)
- 返回用戶態(tài):通過iret指令恢復(fù)用戶態(tài)上下文
性能開銷:一次系統(tǒng)調(diào)用約需100~300ns,主要消耗在寄存器保存/恢復(fù)和緩存失效(TLB flush)。
1、用戶態(tài)與內(nèi)核態(tài)的緩存區(qū)
如下所示:

1.用戶態(tài)緩存區(qū)
來源:
用戶程序(如 C/C++ 的stdio緩沖、Java 的BufferedReader)會維護自己的用戶空間緩沖區(qū)。
作用:
減少系統(tǒng)調(diào)用次數(shù),提高性能。例如:用戶程序調(diào)用fwrite()時,數(shù)據(jù)先寫入用戶緩沖區(qū),達到一定大小后才觸發(fā)系統(tǒng)調(diào)用(write())。若用戶緩沖區(qū)未命中(無數(shù)據(jù)),才會進入內(nèi)核態(tài)。
2.內(nèi)核態(tài)緩存區(qū)
來源:
操作系統(tǒng)內(nèi)核維護頁緩存(Page Cache)和網(wǎng)絡(luò)棧緩沖區(qū)。
作用:
- 頁緩存:緩存磁盤文件的數(shù)據(jù),減少磁盤IO。
- 網(wǎng)絡(luò)棧緩沖區(qū):緩存網(wǎng)絡(luò)數(shù)據(jù)包(如 TCP 的send buffer和recv buffer)。
2、調(diào)用的流程
觸發(fā)方式:用戶程序通過read()/write()等系統(tǒng)調(diào)用進入內(nèi)核態(tài)。
流程:
1、檢查用戶緩沖區(qū):
若用戶程序的緩沖區(qū)有數(shù)據(jù),直接返回(無需進入內(nèi)核)。
2、進入內(nèi)核態(tài):
若用戶緩沖區(qū)無數(shù)據(jù),觸發(fā)系統(tǒng)調(diào)用(如read()),進入內(nèi)核態(tài)。
3、內(nèi)核處理請求:
檢查內(nèi)核緩存(Page Cache):若有數(shù)據(jù),直接返回給用戶態(tài)。若無數(shù)據(jù):發(fā)起物理IO(磁盤/網(wǎng)絡(luò)),等待硬件完成。
3、內(nèi)核如何與硬件交互
1、內(nèi)核的緩存機制
Page Cache:
當(dāng)用戶讀取文件時,內(nèi)核會先檢查 Page Cache 是否有數(shù)據(jù)。若無數(shù)據(jù),內(nèi)核發(fā)起磁盤IO,將數(shù)據(jù)讀入 Page Cache,再返回給用戶。
網(wǎng)絡(luò)棧緩沖區(qū):
當(dāng)用戶程序發(fā)送網(wǎng)絡(luò)數(shù)據(jù)時,內(nèi)核將數(shù)據(jù)寫入TCP 發(fā)送緩沖區(qū),由網(wǎng)絡(luò)協(xié)議棧異步發(fā)送。
2、硬件IO流程
更多關(guān)于I/O介紹,可參考:不同網(wǎng)絡(luò)I/O模型的原理
1、磁盤IO:
內(nèi)核通過塊設(shè)備驅(qū)動(如ext4文件系統(tǒng)的驅(qū)動)與硬盤通信。數(shù)據(jù)通過DMA(直接內(nèi)存訪問)傳輸,繞過CPU,提高效率。
2、網(wǎng)絡(luò)IO:
內(nèi)核通過網(wǎng)卡驅(qū)動與網(wǎng)卡通信。數(shù)據(jù)通過零拷貝技術(shù)(如sendfile()直接從磁盤到網(wǎng)卡,減少內(nèi)存拷貝。
4.2. 線程與進程的實現(xiàn)
輕量級進程(LWP):Java線程對應(yīng)內(nèi)核線程(1:1模型,通過clone()系統(tǒng)調(diào)用創(chuàng)建)
調(diào)度時機:
- 時間片耗盡(內(nèi)核態(tài)時鐘中斷觸發(fā))
- 主動讓出CPU(如Thread.yield()最終調(diào)用sched_yield())
- 上下文切換成本:約1~10μs(需切換頁表、寄存器、TLB等)
4.3、網(wǎng)絡(luò)IO的特殊性
1、網(wǎng)絡(luò)數(shù)據(jù)傳輸流程
用戶程序調(diào)用send():
- 數(shù)據(jù)寫入用戶緩沖區(qū)(如 Java 的BufferedOutputStream)。
- 達到閾值后,觸發(fā)系統(tǒng)調(diào)用send(),進入內(nèi)核態(tài)。
內(nèi)核處理:
- 數(shù)據(jù)被復(fù)制到TCP 發(fā)送緩沖區(qū)(內(nèi)核態(tài))。
- 網(wǎng)絡(luò)協(xié)議棧異步將數(shù)據(jù)發(fā)送到網(wǎng)卡。
接收數(shù)據(jù):
- 網(wǎng)卡接收到數(shù)據(jù)包后,通過中斷通知內(nèi)核。
- 內(nèi)核將數(shù)據(jù)寫入TCP 接收緩沖區(qū)(內(nèi)核態(tài))。
- 用戶程序調(diào)用recv()時,內(nèi)核將數(shù)據(jù)復(fù)制到用戶緩沖區(qū)。
5、注意事項
5.1. 減少模式切換的開銷
- 批量IO:使用NIO的Selector合并就緒事件(單次系統(tǒng)調(diào)用處理多通道)
- 減少不必要的系統(tǒng)調(diào)用:如合并小文件操作為批量操作
- 內(nèi)存映射文件:MappedByteBuffer通過mmap()直接映射文件到用戶空間
- 合理設(shè)置堆外內(nèi)存:DirectByteBuffer不受GC管理,需注意內(nèi)存泄漏
- JNI優(yōu)化:避免頻繁跨越JNI邊界(如合并多次本地方法調(diào)用)
- 謹慎使用本地方法:避免引入不穩(wěn)定因素
- 線程池大小設(shè)置:考慮系統(tǒng)負載和上下文切換開銷
5.2. 內(nèi)核旁路(Kernel Bypass)技術(shù)
- DPDK/Netty Epoll:繞過內(nèi)核協(xié)議棧直接操作網(wǎng)卡(需特定驅(qū)動支持)
- Java的Project Loom:虛擬線程(協(xié)程)減少內(nèi)核線程切換開銷
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
理解這些概念有助于Java開發(fā)者編寫更高效、更穩(wěn)定的應(yīng)用程序,特別是在性能敏感或系統(tǒng)級編程場景中。
相關(guān)文章
Jmeter基于JDBC請求實現(xiàn)MySQL數(shù)據(jù)庫測試
這篇文章主要介紹了Jmeter基于JDBC請求實現(xiàn)MySQL數(shù)據(jù)庫測試,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-10-10
SpringBoot預(yù)加載與懶加載實現(xiàn)方法超詳細講解
Spring一直被詬病啟動時間慢,可Spring/SpringBoot是輕量級的框架。因為當(dāng)Spring項目越來越大的時候,在啟動時加載和初始化Bean就會變得越來越慢,很多時候我們在啟動時并不需要加載全部的Bean,在調(diào)用時再加載就行,那這就需要預(yù)加載與懶加載的功能了2022-11-11
Idea Project文件目錄不見了,只剩External Libraries和imi文件的解決
這篇文章主要介紹了Idea Project文件目錄不見了,只剩External Libraries和imi文件的解決方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
springboot3整合knife4j詳細圖文教程(swagger增強)
開發(fā)api提供對應(yīng)的接口規(guī)范進行聯(lián)調(diào)或并行開發(fā),api文檔管理必不可少,常用的Knife4j基于swagger(依賴已經(jīng)compile),可以進行管理,下面這篇文章主要給大家介紹了關(guān)于springboot3整合knife4j的相關(guān)資料,需要的朋友可以參考下2024-03-03

