文章目錄
- 鎖
- 自旋鎖
- 互斥鎖
- 悲觀鎖和樂觀鎖
- 內存管理
- 物理/虛擬內存
- 頁表
- 段表
- 虛擬內存布局
- 寫時復制copy on write
- brk,mmap
- 頁面置換算法
- 中斷
- 中斷分類
- 中斷流程
- 網絡I/O
- I/O模型
- 服務器處理并發請求
鎖
自旋鎖
自旋鎖是一種基于忙等待(Busy-Waiting)的同步機制。
通過 CPU 提供的 CAS 函數),完成加鎖解鎖操作:
第一步:查看鎖的狀態,為空,則執行第二步
第二步:將鎖設置為當前線程持有
這兩步是原子指令,要么一次性執行完,要么都不執行。
當線程嘗試獲取鎖失敗時,它會循環檢查鎖的狀態(“自旋”),直到它拿到鎖。
等待時間較短的情況下效率較高,因為避免了線程上下文切換的開銷。但長時間等待會導致CPU資源的浪費。
適用于多核系統,且臨界區代碼執行時間非常短的場景。
互斥鎖
在訪問共享資源前對互斥量進行加鎖,在訪問完成后釋放互斥量上的鎖,確保同一時間只有一個線程訪問共享資源。
自旋鎖和互斥鎖的區別
工作機制:自旋鎖在獲取不到鎖時會一直循環檢查,而互斥鎖會讓線程進入睡眠(掛起)狀態,調度其他線程運行。
適用場景:自旋鎖適合保護執行時間極短的代碼段,自旋的代價可能小于線程切換的代價。互斥鎖適合保護執行時間較長的代碼段
性能影響:自旋鎖無上下文切換開銷,但一直占用CPU,互斥鎖掛起喚醒線程產生下上文切換開銷,但等待期間CPU執行其他任務,資源利用率更高.
實現復雜度:自旋鎖依賴CAS原子操作,不需要操作系統調度,而互斥鎖復雜度高,依賴操作系統調度
悲觀鎖和樂觀鎖
悲觀鎖:先加鎖,再操作
認為并發操作一定會發生沖突,因此每次訪問數據時都會加鎖,比如synchronized和ReentrantLock。
適用寫多的場景。
舉個例子:出門時鎖門(默認有小偷)
樂觀鎖:先操作,提交時再檢查沖突
認為并發操作很少發生沖突,只在提交操作時檢查是否沖突,比如CAS操作,數據庫的樂觀鎖和Java中的Atomic類。
適用讀多寫少。
舉個例子:
1.購物車結算時才檢查庫存(默認沒人搶購)
2.或者在網上訂票,系統顯示還有1個座位,你點擊預訂,系統會先讓你填寫信息,然后提交的時候檢查是否還有座位。如果有,預訂成功;如果沒有,提示你重新選擇
內存管理
計算機系統的核心功能之一,其目標是高效、安全地管理物理內存和虛擬內存資源,確保多個進程能共享內存且互不干擾
物理/虛擬內存
-
物理內存
計算機硬件中的實際內存芯片,容量固定,由硬件決定,直接由CPU通過物理地址訪問,是程序運行的真實存儲空間。
-
虛擬內存
操作系統為每個進程提供的邏輯地址空間,獨立于物理內存,通過通過頁表、MMU(內存管理單元,負責管理內存訪問和地址轉換)、缺頁中斷等機制管理,將虛擬地址映射到物理地址或磁盤空間。
-
虛擬內存允許程序使用比物理內存更大的地址空間
-
每個進程擁有獨立的虛擬地址空間,彼此無法直接訪問對方內存,避免惡意或錯誤操作。
-
頁表
操作系統與硬件(如MMU)協作實現虛擬內存的核心數據結構,負責記錄虛擬地址到物理地址的映射關系。
核心作用
- 地址映射
將進程的虛擬頁號 映射到物理內存的物理頁號。 - 權限控制
通過頁表項的權限位(讀/寫/執行)限制內存訪問。 - 狀態標記
記錄頁是否在物理內存中、是否被修改過等狀態信息。
工作流程
- 把虛擬內存地址,切分成頁號和頁內偏移量
- 根據頁號,從頁表里面,查詢對應的物理頁號
- 直接拿物理頁號,加上前面的偏移量,就得到了物理內存地址。
如果在頁表中沒有相應頁號,觸發缺頁中斷。此時進入系統內核空間分配物理內存、更新進程頁表,最后再返回用戶空間,恢復進程的運行。
段表
虛擬地址也可以通過段表與物理地址進行映射的
- 程序的內存空間被劃分為多個邏輯段,每個段代表一個邏輯單元
- 每個段有獨立的基址(起始地址)和界限(長度)
- 段的大小可變,與程序邏輯直接對應(例如代碼段大小取決于代碼量)
- 物理地址 = 段基址 + 段內偏移
虛擬內存布局
1. 代碼段
- 位置:低地址起始(如
0x400000
)。 - 內容:編譯后的機器指令(可執行代碼)。
- 權限:只讀+可執行(防止代碼被篡改)。
- 示例:
main
函數、庫函數的指令。
2. 數據段
- 位置:緊接代碼段。
- 內容:已初始化的全局變量和靜態變量。
- 權限:可讀寫,不可執行。
- 示例:
int global_var = 42;
。
3. BSS段
- 位置:緊接數據段。
- 內容:未初始化的全局變量和靜態變量。
- 權限:可讀寫。
- 示例:
int uninitialized_var;
。
4. 堆
- 位置:BSS段之上,向高地址增長。
- 管理:通過
malloc
、free
動態分配內存。 - 特點:碎片化問題常見,需手動管理(或依賴垃圾回收)。
- 示例:
int *arr = malloc(100 * sizeof(int));
。
5. 文件映射區域
- 位置:堆與棧之間。
- 內容:
- 共享庫(如
libc.so
、libm.so
)。 - 內存映射文件(通過
mmap
映射的文件)。 - 匿名映射(用于大塊內存分配,如
malloc
可能使用mmap
)。
- 共享庫(如
- 權限:按需設置(如可讀寫、可執行)。
6. 棧
- 位置:高地址區域(如
0x7FFFFFFFFFFFF000
),向下增長。 - 內容:函數調用棧幀(局部變量、返回地址、函數參數)。
- 管理:自動分配/釋放內存,由編譯器控制。
- 限制:棧大小固定(默認幾MB,可通過
ulimit
調整)。 - 示例:
int local_var = 10;
。
7. 內核空間
- 位置:虛擬地址空間的高位(如64位Linux中高128TB)。
- 權限:僅內核態可訪問,用戶進程無權直接讀寫。
- 內容:內核代碼、數據結構、設備內存映射等。
寫時復制copy on write
COW,一種內存管理優化技術,延遲數據的物理拷貝,直到真正需要修改數據時才進行復制。
-
問題場景:
假設父進程和子進程共享同一物理內存頁(未復制),且子進程修改了某個變量。如果此時父進程讀取該變量,會發現值被意外改變。- 示例:
父進程定義變量int x = 42
,子進程修改x = 100
。若未復制,父進程的x
也會變為 100,導致邏輯錯誤。
- 示例:
-
COW 的解決方案:
當子進程嘗試寫入x
時,觸發復制,子進程獲得獨立的物理頁副本。- 父進程的
x
保持 42,子進程的x
變為 100,兩者互不影響。
- 父進程的
具體步驟:
- 步驟1:共享內存頁
調用fork()
時,子進程與父進程共享所有物理內存頁,頁表項標記為只讀。 - 步驟2:觸發復制
- 當父進程或子進程嘗試寫入共享頁時,觸發缺頁中斷。
- 操作系統捕獲中斷,檢查觸發原因是 COW,執行以下操作:
- 分配新的物理頁,復制原頁內容到新頁。
- 更新觸發寫入的進程的頁表項,指向新物理頁,并標記為可寫。
- 另一進程仍指向原物理頁(保持只讀,直到其寫入時觸發復制)。
- 步驟3:后續操作
- 修改后的頁獨立于原頁,后續寫入不再觸發復制。
COW有什么好?
fork()的時候,子進程不需要復制父進程的物理內存,只需要復制父進程的頁表,避免了不必要的內存復制開銷,這時候父子進程的頁表指向的都是共享的物理內存。
只有當父子進程任何有一方對這片共享的物理內存發生了修改操作,這時候才會復制發生修改操作的物理內存。
brk,mmap
int *arr = malloc(100 * sizeof(int))
這里的動態分配內存malloc():
1.如果請求分配的內存<128KB,則通過brk()申請
2.如果請求分配的內存>128KB,則通過mmap()申請
-
brk:修改指針,向高地址移動,獲得新內存空間
-
mmap:從文件映射區申請一塊內存
如果物理內存不足
當使用malloc()申請虛擬內存時,如果虛擬內存還沒有映射到物理內存,CPU產生缺頁中斷,缺頁中斷函數查看是否有空閑物理內存,有則建立虛擬與物理內存的映射,如果沒有,則開始回收內存(頁面置換/丟棄/終止):
回收內存,包括:
- 頁面置換
- 將部分物理內存頁換出到磁盤(Swap空間),釋放物理內存供其他進程使用。
- 釋放可丟棄的干凈頁
- 直接丟棄未被修改的頁(如代碼段、文件緩存頁),無需寫回磁盤。
- 終止進程(OOM Killer)
- 強制終止占用內存過多的進程,釋放其所有內存(極端情況下的最后手段)。
1.后臺內存回收:喚醒 kswapd 內核線程來回收內存(異步,不阻塞進程執行)
2.直接內存回收:如果異步回收速度跟不上申請內存速度,則直接回收(同步,阻塞進程執行)
哪些內存可以被回收?
- 文件頁
內核緩存的磁盤數據(Buffer)和內核緩存的文件數據(Cache)都叫作文件頁。需要讀時,重新讀取磁盤即可。而如果修改過但還沒寫入磁盤的數據(臟頁)需要先寫進磁盤,才進行回收。
- 匿名頁
無實際載體,所以不能直接釋放內存。會把不常訪問的內存先寫到磁盤,再釋放這些內存。
如果經過上面的步驟物理內存仍不滿足,則觸發Out-Of-Memory Killer
- OOM根據算法殺死一個占用物理內存較高的進程,釋放其內存資源
- 如果依然不滿足,繼續殺死,直至釋放足夠物理內存
頁面置換算法
頁面置換,內存回收的核心手段之一。
選擇一個物理頁面換出到磁盤,然后把需要訪問的頁面換入到物理頁。
-
最佳頁面OPT
思想:置換出在未來長時間不會使用的頁面
-
先進先出FIFO
思想:換出在內存中占用時間最長的頁面
-
最近最久未使用LRU
思想:換出前面長時間沒有訪問過的頁面
-
時鐘頁面LOCK
思想:將所有頁面存在環形鏈表,每頁需要一個訪問位(0:未訪問1:已訪問)
初始:所有頁訪問位為0
缺頁時,指針移動,
? 訪問位為1:重置為0,指針后移
? 訪問位為0:選擇,換出內存
?
-
最不常用LFU
思想:對每個頁面增加一個訪問計數器,被訪問一次,計數器+1,發生缺頁中斷時,換出訪問次數最少的頁面,
中斷
計算機系統響應外部或內部事件的機制,允許 CPU 暫停當前任務,轉而處理緊急或異步事件(如鍵盤輸入)。處理完后回來繼續執行剛才的任務。
中斷機制是操作系統實現多任務、設備管理、錯誤處理等功能的基礎
中斷分類
外部中斷(硬件中斷)
- 觸發源:外部設備(如鍵盤、磁盤、定時器)。
- 類型:
- 可屏蔽中斷:可通過中斷屏蔽位(如
IF
標志)暫時關閉,可隨時處理,甚至不處理。 - 不可屏蔽中斷:必須立即處理(如硬件故障、內存校驗錯誤)。
- 可屏蔽中斷:可通過中斷屏蔽位(如
內部中斷(軟件中斷)
- 觸發方式:由程序執行特定指令或發生異常。
- 異常:同步觸發,由 CPU 執行指令時檢測到錯誤(如缺頁、除零)交由故障處理程序處理。
- 陷阱:一般是在編寫程序時故意設下的陷阱指令,而后執行到陷阱指令后,CPU將會調用特定程序進行相應的處理,處理結束后返回到陷阱指令的下一條指令。
- 終止:發生致命錯誤,不可修復,程序無法繼續運行,直接終止。
中斷流程
-
發生中斷:設備或程序觸發中斷,發送信號到 CPU 的中斷控制器
-
中斷響應:保存上下文,CPU 暫停當前任務,將程序計數器(PC)、寄存器等壓入內核棧,保存當前執行現場,以便處理完中斷后恢復執行。
-
中斷處理:CPU根據中斷向量表找到對應中斷處理程序入口地址,進行中斷處理
-
恢復上下文:繼續執行之前的程序
網絡I/O
I/O模型
網絡I/O模型定義了應用程序如何管理輸入和輸出操作,尤其是在處理多個并發連接時如何高效利用資源
- 阻塞I/O
- 工作原理:
應用程序發起I/O操作后,進程會被阻塞,直到數據就緒或操作完成。在此期間,進程無法執行其他任務。 - 優點:
實現簡單,代碼直觀,適合低并發場景。 - 缺點:
每個連接需獨立線程/進程,資源消耗大,無法支撐高并發。 - 適用場景:
簡單的客戶端應用或低負載服務(實時性要求不高)。
- 工作原理:
- 非阻塞I/O
- 工作原理:
I/O操作立即返回結果(成功或錯誤),不會被阻塞。若數據未就緒,返回錯誤,應用需輪詢檢查狀態。 - 優點:
單線程可管理多個連接,避免線程阻塞。 - 缺點:
輪詢消耗CPU資源,延遲較高。 - 適用場景:
需要同時處理少量連接且對延遲不敏感的場景(多路復用)。
- 工作原理:
- I/O多路復用
- 工作原理:
使用select
、poll
、epoll
(Linux)等系統調用,同時等待多個I/O操作,當任一I/O準備就緒時通知應用處理。 - 優點:
單線程處理高并發連接,資源利用率高。 - 缺點:
事件通知后仍需同步處理I/O,編程復雜度較高。 - 適用場景:
Web服務器(如Nginx)、即時通訊等高并發服務。
- 工作原理:
- 信號驅動I/O
- 工作原理:
發起I/O請求后繼續做其他事情,當I/O操作就緒時,內核發送信號通知處理,應用執行I/O操作。 - 優點:
避免輪詢,減少CPU占用。 - 缺點:
信號處理復雜,可能丟失事件,多線程中難以管理。 - 適用場景:
需要異步I/O通知的場景,提高系統并發能力。
- 工作原理:
- 異步I/O
- 工作原理:
應用發起I/O請求后立即做其他事情,內核完成整個I/O操作后通知應用。 - 優點:
無阻塞,資源利用最優,適合高吞吐場景。 - 缺點:
實現復雜,需操作系統和庫支持,調試困難。 - 適用場景:
高并發,高性能場景,減少系統調用,提高系統效率。
- 工作原理:
服務器處理并發請求
-
單線程web服務器
一次處理一個請求,性能低
-
多進程/多線程web服務器
服務器生成多個進程/線程并行處理多個用戶請求,消耗大量系統資源
-
I/O多路復用web服務器
只用一個線程處理多個用戶請求
-
多路復用多線程web服務器
避免一個進程服務于過多用戶請求,生成多個進程,一個進程生成多個線程,每個線程處理一個請求
考完操作系統不久,結合小林Coding寫了些筆記,感謝大家的點贊收藏>W<