1.這里將句柄所能標識的所有東西(如窗口、文件、畫筆等)統稱為“對象”。
2.圖中一個小橫框表示一定大小的內存區域,並不代表一個字節,如標有0X00000AC6的橫框表示4個字節。
程序運行到某時刻的內存快照 程序往后運行到另一時刻時的內存快照
Windows是一個以虛擬內存為基礎的操作系統,很多時候,進程的代碼和數據並不全部裝入內存,進程的某一段裝入內存后,還可能被換出到外存,當再次需要時,再裝入內存。兩次裝入的地址絕大多數情況下是不一樣的。也就是說,同一對象在內存中的地址會變化。那么,程序怎么才能准確地訪問到對象呢?為了解決這個問題,Windows引入了句柄。
系統為每個進程在內存中分配一定的區域,用來存放各個句柄,即一個個32位無符號整型值(32位操作系統中)。每個32位無符號整型值相當於一個指針,指向內存中的另一個區域(我們不妨稱之為區域A)。而區域A中存放的正是對象在內存中的地址。當對象在內存中的位置發生變化時,區域A的值被更新,變為當前時刻對象在內存中的地址,而在這個過程中,區域A的位置以及對應句柄的值是不發生變化的。這種機制,用一種形象的說法可以表述為:有一個固定的地址(句柄),指向一個固定的位置(區域A),而區域A中的值可以動態地變化,它時刻記錄着當前時刻對象在內存中的地址。這樣,無論對象的位置在內存中如何變化,只要我們掌握了句柄的值,就可以找到區域A,進而找到該對象。而句柄的值在程序本次運行期間是絕對不變的,我們(即系統)當然可以掌握它。
所以,我們可以這么理解句柄:
數值上,是一個32位無符號整型值(32位系統下);
邏輯上,相當於指針的指針;
形象理解上,是Windows中各個對象的一個唯一的、固定不變的ID;
作用上,Windows使用句柄來標識諸如窗口、位圖、畫筆等對象,並通過句柄找到這些對象。
關於句柄,再交代一些關鍵性細節:
1.所謂“唯一”、“不變”是指在程序的一次運行中。如果本次運行完,關閉程序,再次啟動程序運行,那么這次運行中,同一對象的句柄的值和上次運行時比較,一般是不一樣的。
其實這理解起來也很自然,所謂“一把歸一把,這把是這把,那把是那把,兩者不相干”(“把”是形象的說法,就像打牌一樣,這里指程序的一次運行)。
2.句柄是對象生成時系統指定的,屬性是只讀的,程序員不能修改句柄。
3.不同的系統中,句柄的大小(字節數)是不同的,可以使用sizeof()來計算句柄的大小。
4.通過句柄,程序員只能調用系統提供的服務(即API調用),不能像使用指針那樣,做其它的事。
參考鏈接:
https://www.cnblogs.com/zpcdbky/p/4652151.html
Windows系統中有許多內核對象(這里的對象不完全等價於"面向對象程序設計"一詞中的"對象",雖然實質上還真差不多),比如打開的文件,創建的線程,程序的窗口,等等。這些重要的對象肯定不是4個字節或者8個字節足以完全描述的,他們擁有大量的屬性。為了保存這樣一個"對象"的狀態,往往需要上百甚至上千字節的內存空間,那么怎么在程序間或程序內部的子過程(函數)之間傳遞這些數據呢?拖着這成百上千的字節拷貝來拷貝去嗎?顯然會浪費效率。那么怎么辦?當然傳遞這些對象的首地址是一個辦法,但這至少有兩個缺點:
暴露了內核對象本身,使得程序(而不是操作系統內核)也可以任意地修改對象地內部狀態(首地址都知道了,還有什么不能改的?),這顯然是操作系統內核所不允許的;
操作系統有定期整理內存的責任,如果一些內存整理過一次后,對象被搬走了怎么辦?
所以,Windows操作系統就采用進一步的間接(可以理解為進一步的抽象的過程):在進程的地址空間中設一張表,表里頭專門保存一些編號和由這個編號對應一個地址,而由那個地址去引用實際的對象,這個編號跟那個地址在數值上沒有任何規律性的聯系,純粹是個映射而已。
在Windows系統中,這個編號就叫做"句柄"。
Handle在Windows中的含義很廣泛,以下關於談到的Handle除非特別說明,將僅限於進程、線程的上下文中。
1、先來談談Handle
Handle本身是一個32位的無符號整數,它用來代表一個內核對象。它並不指向實際的內核對象,用戶模式下的程序永遠不可能獲得一個內核對象的實際地址(一般情況下)。那么Handle的意義何在?它實際上是作為一個索引在一個表中查找對應的內核對象的實際地址。那么這個表在哪里呢?每個進程都有這樣的一個表,叫句柄表。該表的第一項就是進程自己的句柄,這也是為什么你調用GetCurrentProcess()總是返回0x7FFFFFFF原因。
簡單地說,Handle就是一種用來"間接"代表一個內核對象的整數值。你可以在程序中使用handle來代表你想要操作的內核對象。這里的內核對象包括:事件(Event)、線程、進程、Mutex等等。我們最常見的就是文件句柄(file handle)。
另外要注意的是,Handle僅在其所屬的進程中才有意義。將一個進程擁有的handle傳給另一個進程沒有任何意義,如果非要這么做,則需要使用DuplicateHandle(),在多個進程間傳遞Handle是另外一個話題了,與這里要討論的無關。
2、進程ID
首先,進程ID是一個32位無符號整數,每個進程都有這樣的一個ID,並且該ID在系統范圍內是唯一的。系統使用該ID來唯一確定一個進程。
深入些說,系統可能使用進程ID來計算代表該進程的內核對象的基地址(及EPROCESS結構的基地址),具體的計算公式你可以去問微軟的OS開發人員。
3、HINSTANCE
HINSTANCE也是一個32無符號整數,它表示程序加載到內存中的基地址。
Windows是一個以虛擬內存為基礎的操作系統,在這種環境下,Windows內存管理器經常在內存中來回移動對象,以此來滿足各種應用程序的需要。對象被移動意味着它的地址變化了。由於地址總是如此變化,所以Windows操作系統為各應用程序騰出一些內存地址,用來專門登記各應用對象在內存中的地址變化,而這地址(存儲單元的位置)本身是不變的。Windows內存管理器在移動對象在內存中的位置后,把對象新的地址告知這個句柄地址來保存。這樣我們只需記住這個句柄地址就可以間接地知道對象具體在內存中的哪個位置。這個地址是在對象裝載(Load)時由系統分配給的,當系統卸載時(Unload)又釋放給系統。
因此,Windows程序中並不是用物理地址來標識一個內存塊,文件,任務,或動態裝入模塊的,相反,WINDOWS API給這些項目分配確定的句柄,並將句柄返回給應用程序,然后通過句柄來進行操作。
在Windows編程中會用到大量的句柄,比如HINSTANCE(實例句柄),HBITMAP(位圖句柄),HDC(設備表述句柄),HICON(圖標句柄)等。這當中還有一個通用的句柄,就是HANDLE,比如下面的語句:
1 HINSTANCE hInstance ;
2 HANDLE hInstance ;
句柄地址(穩定)->記載着對象在內存中的地址->對象在內存中的地址(不穩定)->實際對象。但是,必須注意注意的是,程序每次重新啟動,系統不能保證分配給這個程序的句柄還是原來的那個句柄,而且絕大多數情況的確是不一樣的。
而指針對應着一個數據在內存中的地址,得到了指針就可以自由地修改數據。Windows並不希望一般程序修改其內部數據結構,因為這樣太不安全。所以Windows給每個使用GlobalAlloc等函數聲明的內存區域指定一個句柄,句柄是一種指向指針的指針。
句柄和指針都是地址,不同之處在於:
(1)句柄所指的可以是一個很復雜的結構,並且很有可能是與系統相關的,比如說線程的句柄,它指向的就是一個類或者結構,它和系統有很密切的關系。當一個線程由於不可預料的原因而終止時,系統就可以返回它所占用的的資料,如CPU ,內存等。反過來想可以知道,這個句柄中的某一些項是與系統進行交互的。由於Windows系統是一個多任務的系統,它隨時都可能要分配內存,回收內存,重組內存。
(2)指針也可以指向一個復雜的結構,但是通常是用戶定義的,所以必須的工作都要用戶完成,特別是在刪除的時候
---------------------
linux文件句柄數
1、問題闡述:
too many open files:顧名思義即打開過多文件數。
不過這里的files不單是文件的意思,也包括打開的通訊鏈接(比如socket),正在監聽的端口等等,所以有時候也可以叫做句柄(handle),這個錯誤通常也可以叫做句柄數超出系統限制。
當你的服務器在大並發達到極限時,就會報出“too many open files”。
查看線程占句柄數
ulimit -a
2、產生的原因:
經常在使用linux的時候出現,大多數情況是由於程序沒有正常關閉一些資源引起的,所以出現這種情況,請檢查io讀寫,socket通訊等是否正常關閉。
3、經典案例:
很多項目上線不久運行了一段時間后,服務突然宕了,經檢查日志,出現了too many open files 錯誤。

4、解決方案:
前奏:其實Linux是有文件句柄限制的,而且默認不是很高,一般都是1024,作為一台生產服務器,其實很容易就達到 這個數量,因此我們需要把這個值改大一些。我們可以用ulimit -n 來查看當前用戶句柄數限制。那么這個1024是系統的限制,還是用戶的限制呢。其實,這個是用戶限制來的,完整的說法,應該是當前用戶准備要運行的程序的限制。
1、這個限制是針對單個程序的限制
2、這個限制不會改變之前已經運行了的程序的限制
3、對這個值的修改,退出了當前的shell就會消失
因此出現這種問題有兩種解決方式:
第一:增大文件句柄數。這種方式能及時解決問題,但是不能夠徹底的解決問題,可以為徹底解決問題提供一定的時間保證。那么如何增大文件句柄數數呢?
如修改文件句柄數為65535,ulimit -n 65535.此時系統的文件句柄數為65535.
2)將ulimit 值添加到/etc/profile文件中(適用於有root權限登錄的系統)
為了每次系統重新啟動時,都可以獲取更大的ulimit值,將ulimit 加入到/etc/profile 文件底部。
echo ulimit -n 65535 >>/etc/profile
source /etc/profile #加載修改后的profile
ulimit -n #顯示65535,修改完畢!
到此為止,你以為大功告成了么,其實不然,突然發現自己再次登錄進來的時候,ulimit的值還是1024,這是為什么呢? 用戶登錄的時候執行sh腳本的順序:
/etc/profile.d/file
/etc/profile
/etc/bashrc
/mingjie/.bashrc
/mingjie/.bash_profile
由於ulimit -n的腳本命令加載在第二部分,用戶登錄時由於權限原因在第二步還不能完成ulimit的修改,所以ulimit的值還是系統默認的1024。所以想徹底改變這種問題,就必須做如下操作:修改/etc/security/limits.conf
里面有很詳細的注釋,比如
* soft nofile 2048
* hard nofile 32768
就可以將文件句柄限制統一改成軟2048,硬32768
那么什么是軟限制,什么是硬限制
硬限制是實際的限制,而軟限制,是warnning限制,只會做出warning
這樣就實實際際的增大了文件句柄數。
第二:分析句柄數,查找原因,這是解決問題最根本的辦法。那么如何分析那,就需要用到lsof這個命令了(關於這個命令大家可以在網上學習學習)。
(1)統計各進程打開句柄數:lsof -n|awk '{print $2}'|sort|uniq -c|sort -nr
(2)統計各用戶打開句柄數:lsof -n|awk '{print $3}'|sort|uniq -c|sort -nr
(3)統計各命令打開句柄數:lsof -n|awk '{print $1}'|sort|uniq -c|sort -nr
查看系統打開句柄最大數量
more /proc/sys/fs/file-max
1
查看打開句柄總數
lsof|awk '{print $2}'|wc -l
1
根據打開文件句柄的數量降序排列,其中第二列為進程ID:
lsof|awk '{print $2}'|sort|uniq -c|sort -nr|more
1
根據獲取的進程ID查看進程的詳情
ps -ef |grep
1
修改linux單進程最大文件連接數
修改linux系統參數。vi /etc/security/limits.conf 添加
* soft nofile 65536
* hard nofile 65536
修改以后保存,注銷當前用戶,重新登錄,執行ulimit -a ,ok ,參數生效了: