句柄是什么?Windows結(jié)構(gòu)體里面句柄的作用
句柄是什么?在Windows中,句柄的存在就像指針的標(biāo)識(shí)一樣,但這樣的答案顯示不是你們需要的。閑暇之余,筆者摘錄以下Windows句柄的解釋。用戶可以端著咖啡再看Windows結(jié)構(gòu)體里面句柄的作用表述。
這里我列舉詞條中的關(guān)于句柄的敘述不當(dāng)之處,至于如何不當(dāng)先不管,繼續(xù)往下看就會(huì)明白:
句柄是什么?
Windows 之所以要設(shè)立句柄,根本上源于內(nèi)存管理機(jī)制的問(wèn)題—虛擬地址,簡(jiǎn)而言之?dāng)?shù)據(jù)的地址需要變動(dòng),變動(dòng)以后就需要有人來(lái)記錄管理變動(dòng),(就好像戶籍管理一樣),因此系統(tǒng)用句柄來(lái)記載數(shù)據(jù)地址的變更。
如果想更透徹一點(diǎn)地認(rèn)識(shí)句柄,我可以告訴大家,句柄是一種指向指針的指針。
通常我們說(shuō)句柄是Windows用來(lái)標(biāo)識(shí)被應(yīng)用程序所建立或使用的對(duì)象的唯一整數(shù)。這句話是沒有問(wèn)題的,但是想把這句話對(duì)應(yīng)到具體的內(nèi)存結(jié)構(gòu)上就做不到了。下面我們來(lái)詳細(xì)探討一下Windows中的句柄到底是什么。
一、虛擬內(nèi)存結(jié)構(gòu)
我們知道,CPU是通過(guò)尋址來(lái)訪問(wèn)內(nèi)存的。32位CPU的尋址寬度是 0~0xFFFFFFFF ,計(jì)算后得到的大小是4G,也就是說(shuō)可支持的物理內(nèi)存最大是4G。但在實(shí)踐過(guò)程中,碰到了這樣的問(wèn)題,程序需要使用4G內(nèi)存,而可用物理內(nèi)存小于4G,導(dǎo)致程序不得不降低內(nèi)存占用。
為了解決此類問(wèn)題,現(xiàn)代CPU引入了 MMU(Memory Management Unit 內(nèi)存管理單元)。
MMU 的核心思想是利用虛擬地址替代物理地址,即CPU尋址時(shí)使用虛址,由 MMU 負(fù)責(zé)將虛址映射為物理地址。MMU的引入,解決了對(duì)物理內(nèi)存的限制,對(duì)程序來(lái)說(shuō),就像自己在使用4G內(nèi)存一樣。
內(nèi)存分頁(yè)(Paging)是在使用MMU的基礎(chǔ)上,提出的一種內(nèi)存管理機(jī)制。它將虛擬地址和物理地址按固定大小(4K)分割成頁(yè)(page)和頁(yè)幀(page frame),并保證頁(yè)與頁(yè)幀的大小相同。這種機(jī)制,從數(shù)據(jù)結(jié)構(gòu)上,保證了訪問(wèn)內(nèi)存的高效,并使OS能支持非連續(xù)性的內(nèi)存分配。在程序內(nèi)存不夠用時(shí),還可以將不常用的物理內(nèi)存頁(yè)轉(zhuǎn)移到其他存儲(chǔ)設(shè)備上,比如磁盤,這就是大家耳熟能詳?shù)奶摂M內(nèi)存。
1、虛擬地址與物理地址需要通過(guò)映射,才能使CPU正常工作。
而映射就需要存儲(chǔ)映射表。在現(xiàn)代CPU架構(gòu)中,映射關(guān)系通常被存儲(chǔ)在物理內(nèi)存上一個(gè)被稱之為頁(yè)表(page table)的地方。
如下圖:
從這張圖中,可以清晰地看到CPU與頁(yè)表,物理內(nèi)存之間的交互關(guān)系。
進(jìn)一步優(yōu)化,引入TLB(Translation lookaside buffer,頁(yè)表寄存器緩沖)。
由上一節(jié)可知,頁(yè)表是被存儲(chǔ)在內(nèi)存中的。我們知道CPU通過(guò)總線訪問(wèn)內(nèi)存,肯定慢于直接訪問(wèn)寄存器的。
為了進(jìn)一步優(yōu)化性能,現(xiàn)代CPU架構(gòu)引入了TLB,用來(lái)緩存一部分經(jīng)常訪問(wèn)的頁(yè)表內(nèi)容。
如下圖:
在中間加入了TLB。
2、為什么要支持大內(nèi)存分頁(yè)?
TLB是有限的,這點(diǎn)毫無(wú)疑問(wèn)。當(dāng)超出TLB的存儲(chǔ)極限時(shí),就會(huì)發(fā)生 TLB miss,之后,OS就會(huì)命令CPU去訪問(wèn)內(nèi)存上的頁(yè)表。如果頻繁的出現(xiàn)TLB miss,程序的性能會(huì)下降地很快。
為了讓TLB可以存儲(chǔ)更多的頁(yè)地址映射關(guān)系,我們的做法是調(diào)大內(nèi)存分頁(yè)大小。
如果一個(gè)頁(yè)4M,對(duì)比一個(gè)頁(yè)4K,前者可以讓TLB多存儲(chǔ)1000個(gè)頁(yè)地址映射關(guān)系,性能的提升是比較可觀的。
簡(jiǎn)而言之,虛擬內(nèi)存將內(nèi)存邏輯地址和物理地址之間建立了一個(gè)對(duì)應(yīng)表,要讀寫邏輯地址對(duì)應(yīng)的物理內(nèi)存內(nèi)容,必須查詢相關(guān)頁(yè)表(當(dāng)然現(xiàn)在有還有段式、段頁(yè)式內(nèi)存對(duì)應(yīng)方式,但是從原理上來(lái)說(shuō)都是一樣的)找到邏輯地址對(duì)應(yīng)的物理地址做相關(guān)操作。我們常見的對(duì)程序員開放的內(nèi)存分配接口如malloc等分配的得到的都是邏輯地址,C指針指向的也是邏輯地址。
這種虛擬內(nèi)存的好處是很多的,這里以連續(xù)內(nèi)存分配和可移動(dòng)內(nèi)存為例來(lái)講一講。
首先說(shuō)一說(shuō)連續(xù)內(nèi)存分配,我們?cè)诔绦蛑薪?jīng)常需要分配一塊連續(xù)的內(nèi)存結(jié)構(gòu),如數(shù)組,他們可以使用指針循環(huán)讀取,但是物理內(nèi)存多次分配釋放后實(shí)際上是破碎的,如下圖
圖中白色為可用物理內(nèi)存,黑色為被其他程序占有的內(nèi)存,現(xiàn)在要分配一個(gè)12大小的連續(xù)內(nèi)存,那么顯然物理內(nèi)存中是沒有這么大的連續(xù)內(nèi)存的,這時(shí)候通過(guò)頁(yè)表對(duì)應(yīng)的方式可以看到我們很容易得到邏輯地址上連續(xù)的12大小的內(nèi)存。
再說(shuō)一說(shuō)可移動(dòng)內(nèi)存,我們使用GlobalAlloc等函數(shù)時(shí),經(jīng)常會(huì)指定GMEM_MOVABLE和GMEM_FIXED參數(shù),很對(duì)人對(duì)這兩個(gè)參數(shù)很頭疼,搞不明白什么意思。
實(shí)際上這里的MOVABLE和FIXED都是針對(duì)的邏輯地址來(lái)說(shuō)的。GMEM_MOVABLE是說(shuō)允許操作系統(tǒng)(或者應(yīng)用程序)實(shí)施對(duì)內(nèi)存堆(邏輯地址)的管理,在必要時(shí),操作系統(tǒng)可以移動(dòng)內(nèi)存塊獲取更大的塊,或者合并一些空閑的內(nèi)存塊,也稱“垃圾回收”,它可以提高內(nèi)存的利用率,這里的地址都是指邏輯地址。同樣以分配12大小連續(xù)的內(nèi)存,在某種狀態(tài)時(shí),內(nèi)存結(jié)構(gòu)如下
顯然這時(shí)候是無(wú)法分配12連續(xù)大小的內(nèi)存,但是如果這里的邏輯地址都指明為GMEM_MOVABLE的話,操作系統(tǒng)這時(shí)候會(huì)對(duì)邏輯地址做管理,得到如下結(jié)果:
這時(shí)候就實(shí)現(xiàn)了邏輯地址的MOVE,相對(duì)比實(shí)現(xiàn)物理內(nèi)存的移動(dòng),這樣的代價(jià)當(dāng)然要小得多撒,但是聰明的小伙伴們是不是要問(wèn),這樣在邏輯地址中移動(dòng)了內(nèi)存,那么實(shí)際訪問(wèn)數(shù)據(jù)不都亂套了嗎,還能找到自己分配的實(shí)際物理內(nèi)存數(shù)據(jù)嗎,等等,不要心急,這就是等下要講的句柄做的事情了。
GMEM_FIXED是說(shuō)允許在物理內(nèi)存中移動(dòng)內(nèi)存塊,但是必須保證邏輯地址是不變的,在早期16位Windows操作系統(tǒng)不支持在物理內(nèi)存中移動(dòng)內(nèi)存,所以禁止使用GMEM_FIXED,現(xiàn)在的你估計(jì)體會(huì)不到了。
事實(shí)上用GlobalAlloc分配內(nèi)存時(shí)指定GMEM_FIXED參數(shù)返回的句柄就是指向內(nèi)存分配的內(nèi)存塊的指針,不理解???接著看下面的句柄結(jié)構(gòu),你就明白了。
二、句柄結(jié)構(gòu)
在上面講解虛擬內(nèi)存結(jié)構(gòu)的過(guò)程中,我們就引出了幾個(gè)問(wèn)題:MOVABLE的內(nèi)存訪問(wèn)為什么不會(huì)亂,F(xiàn)IXED的內(nèi)存為什么說(shuō)就是指向分配內(nèi)存塊的指針。
事實(shí)上我們盡管Windows沒有給出源碼,但是從一些頭文件、MSDN和Windows早期內(nèi)存分配函數(shù)中我們還是可以一窺端倪。
在Winnt.h頭文件中做了通用句柄的定義:
01#ifdef STRICT02typedef void *HANDLE;03#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name04#else05typedef PVOID HANDLE;06#define DECLARE_HANDLE(name) typedef HANDLE name07#endif08typedef HANDLE *PHANDLE;復(fù)制代碼#ifdef STRICTtypedef void *HANDLE;#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name#elsetypedef PVOID HANDLE;#define DECLARE_HANDLE(name) typedef HANDLE name#endiftypedef HANDLE *PHANDLE;在Windef.h做了特殊句柄的定義:
01#if !defined(_MAC) || !defined(GDI_INTERNAL)02DECLARE_HANDLE(HFONT);03#endif04DECLARE_HANDLE(HICON);05#if !defined(_MAC) || !defined(WIN_INTERNAL)06DECLARE_HANDLE(HMENU);07#endif08DECLARE_HANDLE(HMETAFILE);09DECLARE_HANDLE(HINSTANCE);10typedef HINSTANCE HMODULE; /* HMODULEs can be used in place of HINSTANCEs */11#if !defined(_MAC) || !defined(GDI_INTERNAL)12DECLARE_HANDLE(HPALETTE);13DECLARE_HANDLE(HPEN);14#endif15DECLARE_HANDLE(HRGN);16DECLARE_HANDLE(HRSRC);17DECLARE_HANDLE(HSTR);18DECLARE_HANDLE(HTASK);19DECLARE_HANDLE(HWINSTA);20DECLARE_HANDLE(HKL);復(fù)制代碼#if !defined(_MAC) || !defined(GDI_INTERNAL)DECLARE_HANDLE(HFONT);#endifDECLARE_HANDLE(HICON);#if !defined(_MAC) || !defined(WIN_INTERNAL)DECLARE_HANDLE(HMENU);#endifDECLARE_HANDLE(HMETAFILE);DECLARE_HANDLE(HINSTANCE);typedef HINSTANCE HMODULE; /* HMODULEs can be used in place of HINSTANCEs */#if !defined(_MAC) || !defined(GDI_INTERNAL)DECLARE_HANDLE(HPALETTE);DECLARE_HANDLE(HPEN);#endifDECLARE_HANDLE(HRGN);DECLARE_HANDLE(HRSRC);DECLARE_HANDLE(HSTR);DECLARE_HANDLE(HTASK);DECLARE_HANDLE(HWINSTA);DECLARE_HANDLE(HKL);這里微軟把通用句柄HANDLE定義為void指針,顯然啦,他是不想讓人知道句柄的真實(shí)類型,但是和他以往的做法一樣,微軟空有一個(gè)好的想法結(jié)果沒有實(shí)現(xiàn)。馬上,如果定義了強(qiáng)制類型檢查STRICT,他又定義了特殊類型句柄宏DECLARE_HANDLE,這里用到了##,這是比較偏僻的用法,翻譯過(guò)來(lái),對(duì)于諸如DECLARE_HANDLE(HMENU)定義其實(shí)就是
01typedef struct HMENU__02{03int unused;04} *HMENU;復(fù)制代碼typedef struct HMENU__{int unused;} *HMENU;到這里,你是不是覺得有一點(diǎn)眉目了呢,對(duì),句柄是一種指向結(jié)構(gòu)體的指針,結(jié)合這里的int unused定義很容易猜到結(jié)構(gòu)體的第一個(gè)字段就是我們的邏輯地址(指針)。那么,是不是僅僅如此呢,當(dāng)然不是!!!由于指向結(jié)構(gòu)體指針可以強(qiáng)制截?cái)嘀猾@取第一個(gè)字段,這里的struct結(jié)構(gòu)體絕對(duì)不止一個(gè)字段,聯(lián)系我們?cè)赪indows中的編程經(jīng)驗(yàn),對(duì)于線程HANDLE有計(jì)數(shù)那么必須有計(jì)數(shù)段,對(duì)于事件HEVENT等內(nèi)核對(duì)象會(huì)要求指定屬性那么必須有屬性段,對(duì)于內(nèi)存分配HANDLE有可移動(dòng)和不可移動(dòng)之說(shuō)那么必須有內(nèi)存可移動(dòng)屬性段,等等。基于此我們可以大膽猜測(cè)Windows的句柄指向的結(jié)構(gòu)類似如下
01struct02{03int pointer;//指針段04int count; //內(nèi)核計(jì)數(shù)段05int attribute; //文件屬性段:SHARED等等06int memAttribute; //內(nèi)存屬性段:MOVABLE和FIXED等等07...08};復(fù)制代碼struct{int pointer;//指針段int count; //內(nèi)核計(jì)數(shù)段int attribute; //文件屬性段:SHARED等等int memAttribute; //內(nèi)存屬性段:MOVABLE和FIXED等等...};事實(shí)上,Windows內(nèi)存管理器管理的其實(shí)都是句柄,通過(guò)句柄來(lái)管理指針,Windows的系統(tǒng)整理內(nèi)存時(shí)檢測(cè)內(nèi)存屬性段,如果是可以移動(dòng)的就能夠移動(dòng)邏輯地址,移動(dòng)完后將新的地址更新到對(duì)應(yīng)句柄的指針段中,當(dāng)要使用MOVABLE地址時(shí)的時(shí)候必須Lock住,這時(shí)候計(jì)數(shù)加1,內(nèi)存管理器檢測(cè)到計(jì)數(shù)》0便不會(huì)移動(dòng)邏輯地址,這時(shí)候才能獲得固定的邏輯地址來(lái)操作物理內(nèi)存,使用完后Unlock內(nèi)存管理器又可以移動(dòng)邏輯地址了,到此MOVABLE的內(nèi)存訪問(wèn)為什么不會(huì)亂這個(gè)問(wèn)題就解決了。
下面再說(shuō)一說(shuō),F(xiàn)IXED的內(nèi)存為什么說(shuō)就是指向分配內(nèi)存塊的指針。我們看上面的通用句柄定義,可以發(fā)現(xiàn)HANDLE的句柄定義一直是void指針,其他的特殊句柄在嚴(yán)格類型檢查的時(shí)候定義為結(jié)構(gòu)體指針,為什么不把二者定義為一樣的呢。查看MSDN關(guān)于GlobalAlloc的敘述對(duì)于GMEM_FIXED類型“Allocates fixed memory. The return value is a pointer.”,這里返回的是一個(gè)指針,為了驗(yàn)證這個(gè)說(shuō)法,我寫了一小段程序
01//GMEM_FIXED02hGlobal = GlobalAlloc(GMEM_FIXED, (lstrlen(szBuffer)+1) * sizeof(TCHAR));03pGlobal = GlobalLock(hGlobal);04lstrcpy(pGlobal, szBuffer);05_tprintf(TEXT("pGlobal和hGlobal%sn"), pGlobal==hGlobal ? TEXT("相等") : TEXT("不相等"));06GlobalUnlock(hGlobal);07_tprintf(TEXT("使用句柄當(dāng)做指針訪問(wèn)的數(shù)據(jù)為:%sn"), hGlobal);08GlobalFree(hGlobal);復(fù)制代碼//GMEM_FIXEDhGlobal = GlobalAlloc(GMEM_FIXED, (lstrlen(szBuffer)+1) * sizeof(TCHAR));pGlobal = GlobalLock(hGlobal);lstrcpy(pGlobal, szBuffer);_tprintf(TEXT("pGlobal和hGlobal%sn"), pGlobal==hGlobal ? TEXT("相等") : TEXT("不相等"));GlobalUnlock(hGlobal);_tprintf(TEXT("使用句柄當(dāng)做指針訪問(wèn)的數(shù)據(jù)為:%sn"), hGlobal);GlobalFree(hGlobal);運(yùn)行結(jié)果為
01pGlobal和hGlobal相等02使用句柄當(dāng)做指針訪問(wèn)的數(shù)據(jù)為:Test text復(fù)制代碼pGlobal和hGlobal相等使用句柄當(dāng)做指針訪問(wèn)的數(shù)據(jù)為:Test text對(duì)比使用GMEM_MOVABLE程序?yàn)?/p>01//GMEM_MOVABLE02hGlobal = GlobalAlloc(GMEM_MOVEABLE, (lstrlen(szBuffer)+1) * sizeof(TCHAR));03pGlobal = GlobalLock(hGlobal);04lstrcpy(pGlobal, szBuffer);05_tprintf(TEXT("pGlobal和hGlobal%sn"), pGlobal==hGlobal ? TEXT("相等") : TEXT("不相等"));06_tprintf(TEXT("使用句柄當(dāng)做指針訪問(wèn)的數(shù)據(jù)為:%sn"), hGlobal);07GlobalUnlock(hGlobal);08GlobalFree(hGlobal);復(fù)制代碼//GMEM_MOVABLEhGlobal = GlobalAlloc(GMEM_MOVEABLE, (lstrlen(szBuffer)+1) * sizeof(TCHAR));pGlobal = GlobalLock(hGlobal);lstrcpy(pGlobal, szBuffer);_tprintf(TEXT("pGlobal和hGlobal%sn"), pGlobal==hGlobal ? TEXT("相等") : TEXT("不相等"));_tprintf(TEXT("使用句柄當(dāng)做指針訪問(wèn)的數(shù)據(jù)為:%sn"), hGlobal);GlobalUnlock(hGlobal);GlobalFree(hGlobal);
運(yùn)行結(jié)果為
01pGlobal和hGlobal不相等02使用句柄當(dāng)做指針訪問(wèn)的數(shù)據(jù)為:?pGlobal和hGlobal不相等使用句柄當(dāng)做指針訪問(wèn)的數(shù)據(jù)為:?顯然,使用GMEM_FIXED和使用GMEM_MOVABLE得到的數(shù)據(jù)類型不是一樣的,我們有理由相信Windows在調(diào)用GlobalAlloc使用GEM_FIXED的時(shí)候返回的就是數(shù)據(jù)指針,使用Windows在調(diào)用GMEM_MOVABLE的時(shí)候返回的是指向結(jié)構(gòu)體的句柄,這樣操作的原因相信是為了使用更加方便。那么這里我們就要修正一下前面的說(shuō)法了:通用句柄HANDLE有時(shí)候是邏輯指針,大多數(shù)時(shí)候是結(jié)構(gòu)體指針,特殊句柄如HMENU等是結(jié)構(gòu)體指針。這樣第二個(gè)問(wèn)題也解決了。
三、總結(jié):
下面,我們?cè)倩仡^看一看博文開頭說(shuō)的敘述不當(dāng)之處,說(shuō)他們不當(dāng)是因?yàn)椴皇峭耆e(cuò)誤:第一點(diǎn),確實(shí)句柄有管理內(nèi)存地址變動(dòng)之用,但是并不只是這個(gè)作用,內(nèi)核對(duì)象訪問(wèn)級(jí)別、文件是否打開都是和他相關(guān)的;第二點(diǎn),指向指針的指針,看得出來(lái)作者也是認(rèn)真思考了的,但是他忽略了句柄包含的其他功能和管理內(nèi)存地址的作用。
那么到這里對(duì)于句柄你應(yīng)該非常理解了,在此基礎(chǔ)我們?cè)赪indows編程上是不是可以有一些啟發(fā):
1、通用句柄HANDLE和特殊句柄一般情況下是可以相互轉(zhuǎn)換的,但是有時(shí)候會(huì)出錯(cuò)。
2、如果不考慮跨平臺(tái)移植的話,應(yīng)該多采用Windows SDK提供的內(nèi)存管理函數(shù),這樣可以獲得更好的內(nèi)存管理。
3、C語(yǔ)言的內(nèi)存分配函數(shù)的實(shí)現(xiàn)都是依靠使用GMEM_FIXED調(diào)用Windows SDK的內(nèi)存分配函數(shù)的。
注意可能在新的VS2005等系列編譯器中看不到本文說(shuō)的一些內(nèi)容,因?yàn)樵赩C6時(shí)候有些代碼還不是那么完善,所以給了我們機(jī)會(huì)去挖掘潛在的內(nèi)容。至于微軟苦心積慮不讓我們看到句柄的真實(shí)定義那是必然的,試想一下主要的內(nèi)存對(duì)象結(jié)構(gòu)都被摸清楚了,那么黑客們還不反了天了。
至此,用戶應(yīng)該明白句柄是什么了吧,其實(shí)在不同的領(lǐng)域中,句柄的作用都是差不多的,可以作為一個(gè)標(biāo)識(shí)的作用。
相關(guān)文章:
1. deepin20時(shí)間顯示不準(zhǔn)確怎么調(diào)整? deepin時(shí)間校正方法2. Win7電腦沒有找到quartz.dll怎么辦?Win7電腦沒有找到quartz.dll解決方法3. 微軟Win10系統(tǒng)升級(jí)助手怎么用?微軟Win10升級(jí)工具在哪里下載?4. UOS文檔查看器怎么添加書簽? UOS添加書簽的三種方法5. 啟動(dòng)Autodesk Desktop Licensing Service時(shí)出現(xiàn)錯(cuò)誤1067:進(jìn)程意外終止怎么辦6. CentOS7.0怎么設(shè)置屏幕分辨率?7. 如何在電腦PC上啟動(dòng)Windows11和Linux雙系統(tǒng)8. 統(tǒng)信uos系統(tǒng)怎么進(jìn)行打印測(cè)試頁(yè)和刪除打印機(jī)?9. Win10強(qiáng)制更新怎么解決?Win10強(qiáng)制更新的解決方法10. Win10提示自動(dòng)修復(fù)無(wú)法修復(fù)電腦怎么辦?Win10提示自動(dòng)修復(fù)無(wú)法修復(fù)電腦的解決方法
