2019年1月27日 星期日

用BootServices配置記憶體

這個命題主要是讓APP用核心服務程式去配置指定型態的記憶體再遍訪LIST_ENTRY把指定型態的記憶體內容清為0, 也就是說如果不是指定型態的記憶體就不會去清它的內容, 範例已上傳, 路徑如下:

https://drive.google.com/open?id=1DfrQI2u3Q6fUteHrP2OnuwYltfKGNFd_

首先看到Pool.c, 當我在APP使用AllocatePool( )配置一塊記憶體時, 最後就會跑到CoreAllocatePoolI( )裡面, CoreAllocatePoolI( )前面都是一堆調整需要配置記憶體大小的code, 基本上就是將要配置記憶體的大小調整成4的倍數, 然後有一個判斷式是這樣的: if (IsListEmpty (&Pool->FreeList[Index])), 這也就是說如果free記憶體不夠了才會跑下面的code, 原則上我這個範例不考慮free記憶體不夠的問題, 所以我直接從Free = CR (Pool->FreeList[Index].ForwardLink, POOL_FREE, Link, POOL_FREE_SIGNATURE); 開始看, 總之為了清掉記憶體我必須把每一次配置記憶體的位址都存下來, 這記憶體位址就是下方的:Buffer = Head->Data; 是有一個方法就是完全都用二維陣列去記錄記憶體位址, 但我想多學東西所以我選擇了二維陣列搭配LIST_ENTRY, 在講解我自己寫的code之前先看我宣告的變數, 我宣告了mPoolHead, mRecord, mRecordIndex三個全域變數, 如果不宣告成全域變數則在其他的INF檔裡這些變數的值都會消失, mRecordIndex是可以累加的, 比如我在ABC.inf裡呼叫CoreAllocatePoolI( )則呼叫完後mRecordIndex變成1, 那我又在DEF.inf裡呼叫CoreAllocatePoolI()則呼叫完mRecordIndex會變成2, 如果宣告成區域變數那永遠都是0變1而已, 再注意mRecord, 事實上我這個mRecord原本是宣告成指標, 結果發現我的APP要存取mRecord時裡面的東西跑掉了, 所以再一次證明了跨INF的變數一定要宣告成全域, 這裡還有一個細節就是mRecord是宣告成一個EfiMaxMemoryType * 15000, 這個陣列明顯太大, 15000是我隨便安插的一個數字, 事實上在NT32微型作業系統常用的記憶體型態只有EfiBootServicesData, 我宣告這麼大的陣列明顯浪費記憶體空間, 有個叫陳鐘誠的長者寫說C語言可以用malloc這個運算子來動態宣告陣列, 而C++和JAVA都有new這個運算子來動態宣告陣列, 雖然我沒實測過速度但我預估new運算子應該會比malloc快, 但我是用NT32平台所以沒有C++和JAVA的運算子, 更何況我是C的死硬派擁護者更不可能接受C++和JAVA, 不過我最後還是沒用malloc因為我懶得看malloc的細節, 其實如果不想浪費記憶體可以把EfiBootServicesData另外寫一個陣列, 不過算了寫在一起看起來比較整齊

再來看我寫的code, 我在CoreInitializePool( )初始化mPoolRecord, 沒啥學問看看就好, mRecord[PoolType][mRecordIndex[PoolType]].Buffer= Buffer; 是重點中的重點, 它把每個型態所配置記憶體的位址都存起來, 其實我只要把mRecord傳到APP就可以達到遍訪配置過的記憶體的效果, 但我為了精進自己所以用鏈結串列, 所以我把mPoolRecord當成頭元素然後把mRecord都弄到鏈結串列裡

再來看到DxeMain.c, 因為我的目標是把mPoolRecord的位址傳到APP, 一開始我想用PCD, 但後來看code看註解才知道Dxe Core階段的PCD只能讀不能寫於是只好放棄, 再來我想到Library, 可是我又發現即使把mPoolRecord宣告成全域變數, 我傳進Library時也的確給Library正確的值, 但我在APP讀到的mPoolRecord又會變成Library的初值, 比如說我在Library裡mPoolRecord是NULL, 我傳給它123但我在APP讀到的仍是NULL, 那我又想到用Protocol, 可是這更搞笑因為Dxe Core階段建立Protocol的code根本還沒跑到, 那怎麼辦呢? 好險Dxe Core的BotServices有Reserved, 所以就有了這樣的一段code : gDxeCoreST->BootServices->Reserved = (VOID*)&mPoolRecord; 於是傳遞位址就成功了

最後看到我的CloudApp.c, 我宣告了四個Buffer其中三個是EfiPalCode而最後一個是EfiMemoryMappedIO, 程式的預期結果是把EfiPalCode的Buffer都清為0而我也的確做到, 改變參數就可以變成把EfiMemoryMappedIO清為0, Link = (pPoolRecord + Type)->UsedLink.BackLink還是必須提醒一下, 因為我把pPoolRecord宣告成POOL_RECORD, 所以每加1就是移動一個POOL_RECORD的偏移量, 其他沒甚麼重點了, 做出來結果像下圖:

各位不要看我寫部落格好像很簡單其實這個範例我搞很久, 寫完除了更熟悉NT32甚至全域變數的特性也有當頭棒喝的感覺, 雖然這個NT32研究最後一定會卡在編譯器, 畢竟編譯器是二進制檔不具可讀性, 但NT32是虛擬平台必定有用到許多VC2008的功能(至少畫線一定有), 我給自己訂的目標是能突破VC2008而在編譯器止步

沒有留言:

張貼留言