淺談windows句柄表 windows
windows定義了不少內核對象:進程對象、線程對象、互斥量對象、信號量對象、事件對象、文件對象等等。在調用相應的函數建立這些對象後,咱們均可以經過HANDLE類型的句柄來引用它們。或許你在一些書上看到過說句柄至關於指針,它指向具體的對象。在某種程度上來講這是不錯的,可是進一步深刻探究時就會發現這樣的說法很不許確。說到句柄就不能不提句柄表,句柄必須經過句柄表才能找到所引用的內核對象,可是不少書中對句柄表倒是一帶而過,不加深究。這是由於Microsoft並未就句柄表發佈過任何官方的文檔,經過逆向工程得到結論又很難讓人信服。 函數
相信沒有什麼能比windows公佈的源代碼更有說服力了,接下來我會經過windows公佈的WRK(windows research kernel)源代碼,來爲你們深刻解析一下windows句柄表。雖然是基於WRK的,不能保證其餘版本的windows系統也採用相同的機制,可是我想它們之間或許都是大同小異的。 spa
windows使用句柄對進程中的各類對象進行引用。實際上windows句柄就是一個索引,它存儲了關聯對象在句柄表的索引值,每一個索引對應句柄表中的一個表項。經過句柄存儲的索引,就能夠很容易得到該句柄項對應的對象的指針。 .net
句柄表中存儲了不少了句柄表項,相似下面的結構: 線程
索引 指針 |
該索引對應對象指針 對象 |
1 blog |
0xfffffddf 索引 |
2 進程 |
0xkdkkdh |
3 |
0xkdkhkd |
4 |
0x3jdkkg |
上圖僅僅爲了演示用,並不表明實際意義。
句柄表是針對於進程而言的,在一個進程使用的句柄,直接在另外一個進程中使用是毫無心義的。換句話說,句柄僅在一個進程內有效。當一個進程的句柄傳遞給另外一個句柄後,句柄就再也不有效。舉例來講:在進程A中索引值爲8的句柄,用於引用a對象。在進程B的句柄表中,雖然索引爲8的句柄項可能不爲空,可是有多是引用的b對象。此對象非彼對象。
在windows Server 2003(與WRK有相同的內核)中,句柄表是一個多層次的結構。它的類型爲:HANDLE_TABLE。定義以下:
咱們看到該結構包含不少的成員,但此處咱們僅討論對咱們有幫助的幾個。
咱們看到HANDLE_TABLE的TableCode成員是一個指針,實際上它的高30位指向存儲句柄表的存儲結構,該存儲結構初始時爲4KB大小的一個頁面。低2位表明當前句柄表的層數。最多爲三層,可是初始時只有一層,之後隨着句柄數量的不斷增長會不斷擴展。
每一個句柄表項大小爲8Byte,其結構爲HANGLE_TABLE_ENTRY。
,因爲windows在爲句柄表分配內存時是按頁面大小4KB來申請內存的。所以每次爲句柄表申請一個新的頁面時,句柄表就增長了512項。
前面咱們曾提過,windows句柄表是層次結構的。下圖就是當句柄表爲三層時的結構圖:
若是低2位爲0,說明句柄表只有一層。TableCode指向的頁面直接存儲的句柄,因爲每一個表項佔8Byte,所以4KB頁面最多能夠存儲512個表項。
下圖爲當句柄表爲一層時的結構圖:
若是低2的值爲1,此時句柄表有兩層,TableCode指向最高層頁面。而最高層頁面用於存儲指向下一層頁面的指針。因爲32位系統下指針佔4Byte,所以最高層頁面能夠存儲1024個指針。每一個指針一樣指向4KB的存儲表項的頁面。能夠存儲512個句柄表項。此時整個windows句柄表能夠存儲1024*512個表項。以下圖所示:
若是低2位值爲2,此時句柄表有三層,以下圖所示:
最高層和次高層都是存儲的指針,最低層頁面存儲句柄項,所以此時能夠整個句柄表能夠存儲1024*1024*512個句柄表項。
可是window限定了每一個進程句柄表存儲的句柄表項不得超過:2^24=16777216。
實際上,在每一個最低層頁面的第一個表項都有特殊用途,因此每一個最低層頁面真正供進程使用的表項爲511個。
在進程建立時,系統會給新進程分配一個單層的句柄表。隨着進程中句柄數量的不斷增長,句柄表會由單層擴展爲二層,最後被擴展爲三層。
在HANDLE_TABLE結構中,FirstFree域記錄了當前句柄表中的空閒句柄單鏈表。說其是單鏈表,可是每一個元素之間不是經過指針而是經過句柄索引值來鏈接的。句柄值按HANDLE_VALUE_INC宏定義逐個遞增,windows定義該宏的值爲4,爲何是4?咱們能夠經過windbg的!handle命令咱們能夠查看一下windows自帶的計算器程序的句柄表的狀況:
能夠看到第一個表項對應的句柄值爲4,並且後面的句柄值都是4的倍數。這是爲何呢?這由於microsoft將句柄的低兩位用來存儲該索引對應的句柄表的層次號。在前面曾介紹過,TableCode成員的低兩位用以控制句柄表的層次。而此處句柄表的低兩位是用於指明該索引所處的層次,有助於快速的定位索引。所以全部的句柄必須右移兩位,也就是除以4才能獲得它的實際在句柄表中的索引。這也就是句柄值都是4的倍數的緣由。
FirstFree成員存儲鏈表頭句柄的索引值。在索引項HANDLE_TABLE_ENTRY結構(立刻會介紹)中,咱們看到有一個名爲NextFreeTableEntry的成員。該成員存儲下一個空閒句柄索引值。 當進程須要建立新的句柄,該句柄會被加入到句柄。這時就能夠從FirstFree取得第一個空閒句柄索引,假設該索引指向x表項,並將該x表項的NextFreeTableEntry成員賦值給FirstFree。此時,原來鏈表頭的NextFreeTableEntry就變成了如今的FirstFree,成爲鏈表頭。
僞代碼以下:
1:從FirstFree取出索引,該索引指向的句柄表項x。
2:FirstFree=x.NextFreeTableEntry;
在釋放x句柄項時,將x句柄索引賦值給FirstFree,並將賦值前的FirstFree的值賦值給x.NextFreeTableEntry。
僞代碼以下:
1:Index temp=FirstFree;
2:將x表項的索引賦值給FirstFree
3:x.NextFreeTableEntry=temp;
NextHandleNeedingPool成員記錄了下一次對句柄表進行擴展時,擴展頁面的第一個索引。也就是說當句柄表全部已分配的頁面都滿了以後,下一個頁面的其實索引值。所以,windows句柄表只是在確實不夠用的時候才進行簡單的線性增加。並不會一次分配多個頁面或擴展多層。
前面咱們曾屢次提到句柄表項,其實它是HANDLE_TABLE_ENTRY結構,定義以下:
[cpp] view plaincopy
該結構由兩個大的union組成,佔8字節。句柄表項存儲了該表項對應的對象的地址,以及其餘的一些屬性信息。
在第一個union中有一個Object指針,它指向了句柄所表明的內核對象。第二個union中,若是句柄表項指向了一個有效的對象,那麼GrantedAccess成員記錄了該句柄的訪問掩碼。當句柄爲空閒時,NextFreeTableEntry成員存儲下一空閒節點的索引值,用以鏈接句柄表的空閒鏈表。
window提供的API:GetCurrentThread和GetCurrentProcess,能夠返回當前線程和進程的句柄。其實在句柄表中是沒有存儲當前進程的句柄和當前線程句柄。GetCurrentThread返回句柄的值是-2,當咱們以此句柄做爲參數傳遞給windows其餘API函數時,在函數內部若是檢測到該句柄值爲-2,不會查找句柄表,當即返回到線程對象地址。相似的調用GetCurrentProcess時,其實返回的是-1,當使用此進程句柄時,一樣不須要查找句柄表,會當即返回指向當前進程對象的指針。
本文使用比較通俗的語言,對windows句柄表的結構作了簡單的介紹。目的是讓對句柄表存在疑問的童鞋對句柄表有個清晰的認識。有說的不對的地方,歡迎指正!