Linux內核完全剖析---math_emulate.c程序
math_emulate.c程序中的所有函數可分為3部分:第一類是設備不存在異常處理程序接口函數math_emulate(),只有這一個函數;第二類是浮點指令仿真處理主函數do_emu(),也只有一個函數;另外所有函數都是仿真運算輔助類函數,包括其余幾個C語言程序中的函數。
在一臺不包含80387協處理器芯片的PC中,如果內核初始化時在CR0中設置了仿真標志EM = 1,那么當CPU遇到一條浮點指令時就會引起CPU產生異常中斷int 7,并且在該中斷處理過程中調用本程序中第476行處的math_emulate(long ___false)函數。
在math_emulate()函數中,若判斷出當前進程還沒有使用過仿真的協處理運算時就會對仿真的80387控制字、狀態字和特征字(Tag Word)進行初始化操作,設置控制字中所有6種協處理器異常屏蔽位并復位狀態字和特征字。然后調用仿真處理主函數do_emu()。使用的參數是作為如下info結構的中斷處理過程中調用math_emulate()函數的返回地址指針。info結構實際上就是棧中自從CPU產生中斷int7后逐漸入棧的一些數據構成的一個結構,因此它與系統調用時內核棧中數據的分布情況基本相同。參見include/linux/math_emu.h文件第 11 行和kernel/sys_call.s開始部分。
do_emu()函數(第52行)首先根據狀態字來判斷有沒有發生仿真的協處理器內部異常。若有則設置狀態字的忙位B(位15),否則就復位忙位B。然后從上述info結構中EIP字段處取得產生協處理器異常的二字節浮點指令代碼code,并在屏蔽掉每條浮點指令碼中都相同的ESC碼(二進制11011)位部分后,根據此時的code值對具體的浮點指令進行軟件仿真運算處理。為便于處理,該函數按5種類型浮點指令碼分別使用了五個switch語句進行處理。例如,第一個switch語句(第75行)用于處理那些不涉及尋址內存操作數的浮點指令。而最后兩個switch語句(第419、432行)則專門用來處理操作數與內存相關的指令。對于后一種類型的指令,其處理過程的基本流程是首先根據指令代碼中的尋址模式字節取得內存操作數的有效地址,然后從該有效地址處讀取相應的數據(整型數、實數或BCD碼數值)。接著把讀取的值轉換成80387內部處理使用的臨時實數格式。在計算完畢后,再把臨時實數格式的數值轉換為原數據類型,最后保存到用戶數據區中。
另外,在具體仿真一條浮點指令時,若發現浮點指令無效,則程序會立刻調用放棄執行函數__math_abort()。該函數會向當前執行進程發送指定的信號,同時修改棧指針esp指向中斷過程中調用math_emulate()函數的返回地址(___math_ret),并立刻返回到中斷處理過程中去。
【編輯推薦】