//xk> 緣起
《深入Linux核心架構》P144頁。讀書存疑,繼而想通。
//xk> 鋪墊
虛擬位址空間一般按3:1劃分為進程地址空間和核心地址空間,32位機器4G的虛擬位址空間就有1G分為核心地址空間。
(1) 核心地址空間前896M是直接映射的物理頁幀,即實體記憶體上的896M能直接映射(通過線性位移0xC000000)到核心地址空間的這個地區。
(2) 然後是一小段記憶體間隙用於核心故障報錯。
(3) 然後是vmalloc地區,用於動態分配,核心自身會儘力避免非連續的物理地址,這段記憶體區主要用於動態載入模組時。
(4) 然後是2個頁的保護性間隔記憶體。
(5) 然後是持久映射區,用於將highmemory中的非持久頁映射到核心中。(存疑,暫不求甚解)
(6) 最後是固定映射區,一直到虛擬位址4GB處。對於上面所述的直接映射區,虛擬位址平移__PAGE_OFFSET(IA-32上即為0xC000000)即得到物理地址,而固定映射不同,這個地區中的虛擬位址指向隨機的物理地址,虛擬位址和物理地址的關聯可以自由定義但定義後不能改變。固定映射的優點是:1. 編譯時間對此類地址的處理類似常數,核心一啟動即為其分配了物理地址。 2. 此類地址的反引用比普通指標要快。 3. 核心確保在環境切換時,對應於固定映射的頁表項不會從TLB刷出,在訪問固定映射的記憶體時,總是通過TLB快取取得對應的物理地址。
//xk> 本文
對每個固定映射地址都會建立一個常數,加入到fixed_addresses枚舉值列表中,__end_of_fixed_addresses是最後一個枚舉成員,定義了最大的可能數字。 linux_kernel_2.6.27.62/include/asm-x86/fixmap_32.h :: fix_to_virt()函數用於根據固定映射的fixed_addresses常數計算虛擬位址
static __always_inline unsigned long fix_to_virt(const unsigned int idx){ if(idx >= __end_of_fixed_addresses) __this_fixmap_does_not_exist(); return __fix_to_virt(idx);}
如果idx比可能的最大值還大,那麼出錯,核心訪問了無效的地址,調用__this_fixmap_does_not_exist()函數。注意:這個函數是沒有定義的!在核心連結時會報錯:由於存在未定義符號而無法產生映像檔案。這麼做的好處是:把運行期錯誤提前到了連結期,這種核心故障在編譯連結時即可檢測,而不會在運行時出現。
問題來了,如果核心沒出錯呢?連結器並不知道if判斷的執行流程,那它怎麼知道不去連結__this_fixmap_does_not_exist()的定義呢?奧妙在編譯器最佳化機制,因為fix_to_virt()是內嵌函式,而其參數idx是枚舉值常數,在編譯期就可以算出來(這就是要整個fixed_addresses枚舉表而不是直接用固定映射區虛擬位址的原因)。在編譯器最佳化階段,if語句會被消除,如果idx值越界,__this_fixmap_does_not_exist()的調用會留在代碼中,從而引發連結錯誤;如果idx值有效,__this_fixmap_does_not_exist()調用就被最佳化掉了,核心編譯連結通過。
OK, GCC最佳化依然不求甚解,相關知識估計也是一大塊。不過下次遇到大牛使用這樣的技巧,通過未定義的偽函數將運行期錯誤提前到連結報錯,就不會一頭霧水了^_^