
switch_mm是進(jìn)程切換context_switch中負(fù)責(zé)切換虛擬內(nèi)存地址空間的核心函數(shù)。它的本質(zhì)工作就是把新進(jìn)程的頁表基地址加載到硬件寄存器x86 上是CR3并處理好 TLB 刷新的相關(guān)事宜。核心職責(zé)加載新頁表并管理 TLB當(dāng)調(diào)度器決定運行一個新進(jìn)程時switch_mm(prev, next, tsk)會被調(diào)用。它的核心邏輯圍繞幾個關(guān)鍵點展開加載next-pgd到CR3這是最根本的一步。next-pgd是next進(jìn)程頁全局目錄的虛擬地址。執(zhí)行l(wèi)oad_cr3()后CPU 的地址翻譯單元就開始使用新進(jìn)程的頁表了。維護(hù)mm_cpumask這是一個位掩碼記錄了當(dāng)前哪些 CPU 正在使用這個mm。switch_mm會把自己所在的 CPU 加入到next的掩碼中并從prev的掩碼中移除。這確保了當(dāng)頁表發(fā)生變化時能準(zhǔn)確地通知到所有正在使用該頁表的 CPU 去刷新 TLB。處理 TLB 的“懶惰”策略如果prev next比如內(nèi)核線程切換時借用同一個mmswitch_mm會走快速路徑只更新 CPU 的 TLB 狀態(tài)而不會重新加載CR3從而避免了昂貴的 TLB 刷新開銷。代碼邏輯與關(guān)鍵細(xì)節(jié)雖然不同內(nèi)核版本的實現(xiàn)細(xì)節(jié)有差異但核心骨架是清晰的。一個經(jīng)典的switch_mm實現(xiàn)以較新版本為例包含以下關(guān)鍵步驟void switch_mm(struct mm_struct *prev, struct mm_struct *next, struct task_struct *tsk) { unsigned cpu smp_processor_id(); if (likely(prev ! next)) { // 1. 設(shè)置當(dāng)前CPU的TLB狀態(tài)為OK并記錄活躍的mm this_cpu_write(cpu_tlbstate.state, TLBSTATE_OK); this_cpu_write(cpu_tlbstate.active_mm, next); // 2. 將當(dāng)前CPU加入next的mm_cpumask cpumask_set_cpu(cpu, mm_cpumask(next)); // 3. 加載新頁表到CR3這是關(guān)鍵的同步點 // load_cr3 是序列化指令充當(dāng)了全屏障的作用 load_cr3(next-pgd); // 4. 從prev的mm_cpumask中移除當(dāng)前CPU cpumask_clear_cpu(cpu, mm_cpumask(prev)); // 5. 加載與mm相關(guān)的CR4狀態(tài)如PCID、SMEP等 load_mm_cr4(next); } }這里有一個微妙的并發(fā)同步問題體現(xiàn)在mm_cpumask和load_cr3的順序上。注釋中詳細(xì)解釋了原因switch_mm必須保證cpumask_set_cpu的寫入先于任何可能從新頁表加載 TLB 條目的操作。否則另一個 CPU 在修改頁表并檢查mm_cpumask時可能看不到當(dāng)前 CPU從而漏發(fā) TLB 刷新 IPI導(dǎo)致當(dāng)前 CPU 的 TLB 中殘留過期的映射。幸運的是load_cr3()指令本身具有序列化效果天然充當(dāng)了所需的全內(nèi)存屏障。與其他核心機(jī)制的聯(lián)動switch_mm并非孤立存在它和你之前問過的許多概念都緊密相連與context_switchswitch_mm是context_switch的第一步負(fù)責(zé)“換地址空間”之后的switch_to才負(fù)責(zé)“換寄存器?!?。與active_mm/ 惰性 TLB當(dāng)切換到內(nèi)核線程next-mm NULL時調(diào)度器不會調(diào)用switch_mm而是借用前一個進(jìn)程的active_mm并進(jìn)入“惰性 TLB”模式。與 PCID/ASID現(xiàn)代 x86 的switch_mm不會簡單地往CR3里寫next-pgd而是通過choose_new_asid等邏輯結(jié)合kern_pcid計算出包含 PCID 的完整CR3值并決定是否需要刷新 TLB。你之前看到的load_new_mm_cr3就是實際執(zhí)行這一步的函數(shù)。與 TLB 刷新 IPImm_cpumask的維護(hù)是 TLB shootdown 的基礎(chǔ)。當(dāng)一個 CPU 修改了next的頁表后它會遍歷mm_cpumask找到所有正在使用該頁表的 CPU并向它們發(fā)送 IPI 進(jìn)行 TLB 刷新。