句柄的概念详解
1.這里將句柄所能標(biāo)識的所有東西(如窗口、文件、畫筆等)統(tǒng)稱為“對象”。
2.圖中一個(gè)小橫框表示一定大小的內(nèi)存區(qū)域,并不代表一個(gè)字節(jié),如標(biāo)有0X00000AC6的橫框表示4個(gè)字節(jié)。
??
程序運(yùn)行到某時(shí)刻的內(nèi)存快照 ??????????????程序往后運(yùn)行到另一時(shí)刻時(shí)的內(nèi)存快照
Windows是一個(gè)以虛擬內(nèi)存為基礎(chǔ)的操作系統(tǒng),很多時(shí)候,進(jìn)程的代碼和數(shù)據(jù)并不全部裝入內(nèi)存,進(jìn)程的某一段裝入內(nèi)存后,還可能被換出到外存,當(dāng)再次需要時(shí),再裝入內(nèi)存。兩次裝入的地址絕大多數(shù)情況下是不一樣的。也就是說,同一對象在內(nèi)存中的地址會變化。那么,程序怎么才能準(zhǔn)確地訪問到對象呢?為了解決這個(gè)問題,Windows引入了句柄。
系統(tǒng)為每個(gè)進(jìn)程在內(nèi)存中分配一定的區(qū)域,用來存放各個(gè)句柄,即一個(gè)個(gè)32位無符號整型值(32位操作系統(tǒng)中)。每個(gè)32位無符號整型值相當(dāng)于一個(gè)指針,指向內(nèi)存中的另一個(gè)區(qū)域(我們不妨稱之為區(qū)域A)。而區(qū)域A中存放的正是對象在內(nèi)存中的地址。當(dāng)對象在內(nèi)存中的位置發(fā)生變化時(shí),區(qū)域A的值被更新,變?yōu)楫?dāng)前時(shí)刻對象在內(nèi)存中的地址,而在這個(gè)過程中,區(qū)域A的位置以及對應(yīng)句柄的值是不發(fā)生變化的。這種機(jī)制,用一種形象的說法可以表述為:有一個(gè)固定的地址(句柄),指向一個(gè)固定的位置(區(qū)域A),而區(qū)域A中的值可以動(dòng)態(tài)地變化,它時(shí)刻記錄著當(dāng)前時(shí)刻對象在內(nèi)存中的地址。這樣,無論對象的位置在內(nèi)存中如何變化,只要我們掌握了句柄的值,就可以找到區(qū)域A,進(jìn)而找到該對象。而句柄的值在程序本次運(yùn)行期間是絕對不變的,我們(即系統(tǒng))當(dāng)然可以掌握它。
所以,我們可以這么理解句柄:
數(shù)值上,是一個(gè)32位無符號整型值(32位系統(tǒng)下);
邏輯上,相當(dāng)于指針的指針;
形象理解上,是Windows中各個(gè)對象的一個(gè)唯一的、固定不變的ID;
作用上,Windows使用句柄來標(biāo)識諸如窗口、位圖、畫筆等對象,并通過句柄找到這些對象。
關(guān)于句柄,再交代一些關(guān)鍵性細(xì)節(jié):
1.所謂“唯一”、“不變”是指在程序的一次運(yùn)行中。如果本次運(yùn)行完,關(guān)閉程序,再次啟動(dòng)程序運(yùn)行,那么這次運(yùn)行中,同一對象的句柄的值和上次運(yùn)行時(shí)比較,一般是不一樣的。
其實(shí)這理解起來也很自然,所謂“一把歸一把,這把是這把,那把是那把,兩者不相干”(“把”是形象的說法,就像打牌一樣,這里指程序的一次運(yùn)行)。
2.句柄是對象生成時(shí)系統(tǒng)指定的,屬性是只讀的,程序員不能修改句柄。
3.不同的系統(tǒng)中,句柄的大小(字節(jié)數(shù))是不同的,可以使用sizeof()來計(jì)算句柄的大小。
4.通過句柄,程序員只能調(diào)用系統(tǒng)提供的服務(wù)(即API調(diào)用),不能像使用指針那樣,做其它的事。
Windows系統(tǒng)中有許多內(nèi)核對象(這里的對象不完全等價(jià)于"面向?qū)ο蟪绦蛟O(shè)計(jì)"一詞中的"對象",雖然實(shí)質(zhì)上還真差不多),比如打開的文件,創(chuàng)建的線程,程序的窗口,等等。這些重要的對象肯定不是4個(gè)字節(jié)或者8個(gè)字節(jié)足以完全描述的,他們擁有大量的屬性。為了保存這樣一個(gè)"對象"的狀態(tài),往往需要上百甚至上千字節(jié)的內(nèi)存空間,那么怎么在程序間或程序內(nèi)部的子過程(函數(shù))之間傳遞這些數(shù)據(jù)呢?拖著這成百上千的字節(jié)拷貝來拷貝去嗎?顯然會浪費(fèi)效率。那么怎么辦?當(dāng)然傳遞這些對象的首地址是一個(gè)辦法,但這至少有兩個(gè)缺點(diǎn):
暴露了內(nèi)核對象本身,使得程序(而不是操作系統(tǒng)內(nèi)核)也可以任意地修改對象地內(nèi)部狀態(tài)(首地址都知道了,還有什么不能改的?),這顯然是操作系統(tǒng)內(nèi)核所不允許的;
操作系統(tǒng)有定期整理內(nèi)存的責(zé)任,如果一些內(nèi)存整理過一次后,對象被搬走了怎么辦?
所以,Windows操作系統(tǒng)就采用進(jìn)一步的間接(可以理解為進(jìn)一步的抽象的過程):在進(jìn)程的地址空間中設(shè)一張表,表里頭專門保存一些編號和由這個(gè)編號對應(yīng)一個(gè)地址,而由那個(gè)地址去引用實(shí)際的對象,這個(gè)編號跟那個(gè)地址在數(shù)值上沒有任何規(guī)律性的聯(lián)系,純粹是個(gè)映射而已。
在Windows系統(tǒng)中,這個(gè)編號就叫做"句柄"。?
Handle在Windows中的含義很廣泛,以下關(guān)于談到的Handle除非特別說明,將僅限于進(jìn)程、線程的上下文中。
1、先來談?wù)凥andle
Handle本身是一個(gè)32位的無符號整數(shù),它用來代表一個(gè)內(nèi)核對象。它并不指向?qū)嶋H的內(nèi)核對象,用戶模式下的程序永遠(yuǎn)不可能獲得一個(gè)內(nèi)核對象的實(shí)際地址(一般情況下)。那么Handle的意義何在?它實(shí)際上是作為一個(gè)索引在一個(gè)表中查找對應(yīng)的內(nèi)核對象的實(shí)際地址。那么這個(gè)表在哪里呢?每個(gè)進(jìn)程都有這樣的一個(gè)表,叫句柄表。該表的第一項(xiàng)就是進(jìn)程自己的句柄,這也是為什么你調(diào)用GetCurrentProcess()總是返回0x7FFFFFFF原因。
簡單地說,Handle就是一種用來"間接"代表一個(gè)內(nèi)核對象的整數(shù)值。你可以在程序中使用handle來代表你想要操作的內(nèi)核對象。這里的內(nèi)核對象包括:事件(Event)、線程、進(jìn)程、Mutex等等。我們最常見的就是文件句柄(file handle)。
另外要注意的是,Handle僅在其所屬的進(jìn)程中才有意義。將一個(gè)進(jìn)程擁有的handle傳給另一個(gè)進(jìn)程沒有任何意義,如果非要這么做,則需要使用DuplicateHandle(),在多個(gè)進(jìn)程間傳遞Handle是另外一個(gè)話題了,與這里要討論的無關(guān)。
2、進(jìn)程ID
首先,進(jìn)程ID是一個(gè)32位無符號整數(shù),每個(gè)進(jìn)程都有這樣的一個(gè)ID,并且該ID在系統(tǒng)范圍內(nèi)是唯一的。系統(tǒng)使用該ID來唯一確定一個(gè)進(jìn)程。
深入些說,系統(tǒng)可能使用進(jìn)程ID來計(jì)算代表該進(jìn)程的內(nèi)核對象的基地址(及EPROCESS結(jié)構(gòu)的基地址),具體的計(jì)算公式你可以去問微軟的OS開發(fā)人員。
3、HINSTANCE
HINSTANCE也是一個(gè)32無符號整數(shù),它表示程序加載到內(nèi)存中的基地址。
Windows是一個(gè)以虛擬內(nèi)存為基礎(chǔ)的操作系統(tǒng),在這種環(huán)境下,Windows內(nèi)存管理器經(jīng)常在內(nèi)存中來回移動(dòng)對象,以此來滿足各種應(yīng)用程序的需要。對象被移動(dòng)意味著它的地址變化了。由于地址總是如此變化,所以Windows操作系統(tǒng)為各應(yīng)用程序騰出一些內(nèi)存地址,用來專門登記各應(yīng)用對象在內(nèi)存中的地址變化,而這地址(存儲單元的位置)本身是不變的。Windows內(nèi)存管理器在移動(dòng)對象在內(nèi)存中的位置后,把對象新的地址告知這個(gè)句柄地址來保存。這樣我們只需記住這個(gè)句柄地址就可以間接地知道對象具體在內(nèi)存中的哪個(gè)位置。這個(gè)地址是在對象裝載(Load)時(shí)由系統(tǒng)分配給的,當(dāng)系統(tǒng)卸載時(shí)(Unload)又釋放給系統(tǒng)。
? ?因此,Windows程序中并不是用物理地址來標(biāo)識一個(gè)內(nèi)存塊,文件,任務(wù),或動(dòng)態(tài)裝入模塊的,相反,WINDOWS API給這些項(xiàng)目分配確定的句柄,并將句柄返回給應(yīng)用程序,然后通過句柄來進(jìn)行操作。
? ?在Windows編程中會用到大量的句柄,比如HINSTANCE(實(shí)例句柄),HBITMAP(位圖句柄),HDC(設(shè)備表述句柄),HICON(圖標(biāo)句柄)等。這當(dāng)中還有一個(gè)通用的句柄,就是HANDLE,比如下面的語句:
1 HINSTANCE hInstance ;
2 HANDLE?hInstance ;
? 句柄地址(穩(wěn)定)->記載著對象在內(nèi)存中的地址->對象在內(nèi)存中的地址(不穩(wěn)定)->實(shí)際對象。但是,必須注意注意的是,程序每次重新啟動(dòng),系統(tǒng)不能保證分配給這個(gè)程序的句柄還是原來的那個(gè)句柄,而且絕大多數(shù)情況的確是不一樣的。
? ?而指針對應(yīng)著一個(gè)數(shù)據(jù)在內(nèi)存中的地址,得到了指針就可以自由地修改數(shù)據(jù)。Windows并不希望一般程序修改其內(nèi)部數(shù)據(jù)結(jié)構(gòu),因?yàn)檫@樣太不安全。所以Windows給每個(gè)使用GlobalAlloc等函數(shù)聲明的內(nèi)存區(qū)域指定一個(gè)句柄,句柄是一種指向指針的指針。
? 句柄和指針都是地址,不同之處在于:
(1)句柄所指的可以是一個(gè)很復(fù)雜的結(jié)構(gòu),并且很有可能是與系統(tǒng)相關(guān)的,比如說線程的句柄,它指向的就是一個(gè)類或者結(jié)構(gòu),它和系統(tǒng)有很密切的關(guān)系。當(dāng)一個(gè)線程由于不可預(yù)料的原因而終止時(shí),系統(tǒng)就可以返回它所占用的的資料,如CPU ,內(nèi)存等。反過來想可以知道,這個(gè)句柄中的某一些項(xiàng)是與系統(tǒng)進(jìn)行交互的。由于Windows系統(tǒng)是一個(gè)多任務(wù)的系統(tǒng),它隨時(shí)都可能要分配內(nèi)存,回收內(nèi)存,重組內(nèi)存。
(2)指針也可以指向一個(gè)復(fù)雜的結(jié)構(gòu),但是通常是用戶定義的,所以必須的工作都要用戶完成,特別是在刪除的時(shí)候
---------------------
linux文件句柄數(shù)
1、問題闡述:
???too?many?open?files:顧名思義即打開過多文件數(shù)。
不過這里的files不單是文件的意思,也包括打開的通訊鏈接(比如socket),正在監(jiān)聽的端口等等,所以有時(shí)候也可以叫做句柄(handle),這個(gè)錯(cuò)誤通常也可以叫做句柄數(shù)超出系統(tǒng)限制。
當(dāng)你的服務(wù)器在大并發(fā)達(dá)到極限時(shí),就會報(bào)出“too many open files”。
查看線程占句柄數(shù)
ulimit -a
2、產(chǎn)生的原因:
經(jīng)常在使用linux的時(shí)候出現(xiàn),大多數(shù)情況是由于程序沒有正常關(guān)閉一些資源引起的,所以出現(xiàn)這種情況,請檢查io讀寫,socket通訊等是否正常關(guān)閉。
3、經(jīng)典案例:
很多項(xiàng)目上線不久運(yùn)行了一段時(shí)間后,服務(wù)突然宕了,經(jīng)檢查日志,出現(xiàn)了too?many?open?files?錯(cuò)誤。
4、解決方案:
前奏:其實(shí)Linux是有文件句柄限制的,而且默認(rèn)不是很高,一般都是1024,作為一臺生產(chǎn)服務(wù)器,其實(shí)很容易就達(dá)到?這個(gè)數(shù)量,因此我們需要把這個(gè)值改大一些。我們可以用ulimit?-n?來查看當(dāng)前用戶句柄數(shù)限制。那么這個(gè)1024是系統(tǒng)的限制,還是用戶的限制呢。其實(shí),這個(gè)是用戶限制來的,完整的說法,應(yīng)該是當(dāng)前用戶準(zhǔn)備要運(yùn)行的程序的限制。?
1、這個(gè)限制是針對單個(gè)程序的限制?
2、這個(gè)限制不會改變之前已經(jīng)運(yùn)行了的程序的限制?
3、對這個(gè)值的修改,退出了當(dāng)前的shell就會消失?
?因此出現(xiàn)這種問題有兩種解決方式:
第一:增大文件句柄數(shù)。這種方式能及時(shí)解決問題,但是不能夠徹底的解決問題,可以為徹底解決問題提供一定的時(shí)間保證。那么如何增大文件句柄數(shù)數(shù)呢??
如修改文件句柄數(shù)為65535,ulimit?-n?65535.此時(shí)系統(tǒng)的文件句柄數(shù)為65535.?
??2)將ulimit?值添加到/etc/profile文件中(適用于有root權(quán)限登錄的系統(tǒng))?
為了每次系統(tǒng)重新啟動(dòng)時(shí),都可以獲取更大的ulimit值,將ulimit?加入到/etc/profile?文件底部。?
??echo?ulimit?-n?65535?>>/etc/profile?????
??source?/etc/profile????#加載修改后的profile??
??ulimit?-n?????#顯示65535,修改完畢!?
??到此為止,你以為大功告成了么,其實(shí)不然,突然發(fā)現(xiàn)自己再次登錄進(jìn)來的時(shí)候,ulimit的值還是1024,這是為什么呢??用戶登錄的時(shí)候執(zhí)行sh腳本的順序:?
????/etc/profile.d/file?
????/etc/profile?
????/etc/bashrc?
????/mingjie/.bashrc?
????/mingjie/.bash_profile?
????由于ulimit?-n的腳本命令加載在第二部分,用戶登錄時(shí)由于權(quán)限原因在第二步還不能完成ulimit的修改,所以ulimit的值還是系統(tǒng)默認(rèn)的1024。所以想徹底改變這種問題,就必須做如下操作:修改/etc/security/limits.conf?
里面有很詳細(xì)的注釋,比如?
*?soft?nofile?2048?
*?hard?nofile?32768?
就可以將文件句柄限制統(tǒng)一改成軟2048,硬32768?
那么什么是軟限制,什么是硬限制?
硬限制是實(shí)際的限制,而軟限制,是warnning限制,只會做出warning?
這樣就實(shí)實(shí)際際的增大了文件句柄數(shù)。
第二:分析句柄數(shù),查找原因,這是解決問題最根本的辦法。那么如何分析那,就需要用到lsof這個(gè)命令了(關(guān)于這個(gè)命令大家可以在網(wǎng)上學(xué)習(xí)學(xué)習(xí))。
(1)統(tǒng)計(jì)各進(jìn)程打開句柄數(shù):lsof?-n|awk?'{print?$2}'|sort|uniq?-c|sort?-nr
?????(2)統(tǒng)計(jì)各用戶打開句柄數(shù):lsof?-n|awk?'{print?$3}'|sort|uniq?-c|sort?-nr
?????(3)統(tǒng)計(jì)各命令打開句柄數(shù):lsof?-n|awk?'{print?$1}'|sort|uniq?-c|sort?-nr
查看系統(tǒng)打開句柄最大數(shù)量
more /proc/sys/fs/file-max
1
查看打開句柄總數(shù)
lsof|awk '{print $2}'|wc -l
1
根據(jù)打開文件句柄的數(shù)量降序排列,其中第二列為進(jìn)程ID:
lsof|awk '{print $2}'|sort|uniq -c|sort -nr|more
1
根據(jù)獲取的進(jìn)程ID查看進(jìn)程的詳情
ps -ef |grep?
1
修改linux單進(jìn)程最大文件連接數(shù)
修改linux系統(tǒng)參數(shù)。vi /etc/security/limits.conf 添加
* soft nofile 65536
* hard nofile 65536
修改以后保存,注銷當(dāng)前用戶,重新登錄,執(zhí)行ulimit -a ,ok ,參數(shù)生效了:
總結(jié)
- 上一篇: java 并发包之 LongAdder
- 下一篇: 《西线无战事》:合上书的那一刻:只想痛哭