C++Windows核心編程讀書筆記

轉自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtmlhtml

"C++Windows核心編程讀書筆記":node

 

關鍵詞c++windows 核心 編程 讀書筆記c++

 

 

  這篇筆記是我在讀《windows核心編程》第5版時作的記錄和總結(部分章節是第4版的書),沒有摘抄原句,包含了不少我我的的思考和對實現的推斷,所以很多條款和windows實際機制可能有出入,但應該是合理的。開頭幾章因爲我追求簡潔,每每是不少單獨的字句,後面的內容更爲連貫。web

  海量細節。算法

 

 

第1章    錯誤處理

1.         getlasterror返回的是最後的錯誤碼,即更早的錯誤碼可能被覆蓋。shell

2.         getlasterror可能用於描述成功的緣由(createvent)。編程

3.         vs監視窗口err,hr。windows

4.         formatmessage。api

5.         setlasterror。數組

第2章    字符和字符串處理

1.         ansi版本的api所有是包裝unicode版原本的,在傳參和返回是多了一次編碼轉換。

2.         ms的c庫的ansi和unicode版本之間也是沒有互相調用關係的,沒有編碼轉換開銷。

3.         寬字符函數:_tcscpy,_tcscat,_tcslen。

4.         unicode宏是windows api使用的,而ms的c庫中,對於非標準的東西用_前綴區分,因此_unicode宏是ms的c api使用的。

5.         ms提供的避免緩衝區溢出攻擊的函數在<strsafe.h>文件中,包括stringcbcat和stringcchcat等函數(其中cb表示count of byte,cch表示count of character,都用於表示衡量目標緩衝大小的單位);另外<tchar.h>中有_tcscpy_s等_s後綴的函數。在源串太短時,<strsafe.h>的函數截斷,<tchar.h>的函數斷言。

6.         要想接管crt的錯誤處理(好比assert),使用_set_invalid_parameter_handler設置本身的處理函數,而後使用_crtsetreportmode(_crt_assert, 0);來禁止crt彈出對話框。

7.         windows也提供了字符串處理函數,但lstrcat、lstrcpy(針對t字符的)已通過時了,由於沒考慮緩衝區溢出攻擊。考慮使用strformatkbsize、strformatbytesize、comparest ring(有不少比較選項)、comparest ringordinal(至關於_tcscmp)。

8.         getthreadlocale返回線程的語言信息:lcid(locale id),供不少函數使用(包括使用comparest ring針對語言來比較的時候)。

9.         寬字節轉多字節widechartomultibyte,反之multibytetowidechar。其中,在寬字節轉多字節的時候,若是有unicode字符在多字節編碼中沒有對應項,那寬字節會被替換成參數lpdefaultchar,而且lpuseddefaultchar會被標記爲true。當用這兩個函數計算結果串的大小時,返回的是字符數。

10.     istextunicode。

第3章    內核對象

1.         簡單區份內核對象和其餘對象的開發方法 :建立須要安全信息的多半是內核對象。

2.         每一個進程有一個內核對象表,表的每一項是一個簡單結構,包括真實內核對象地址和訪問權限等。用戶代碼持有的內核對象句柄實際上是對象表中對應項的索引。所以若是closehandle關閉一個對象後沒有清空變量,且在對象表的一樣位置剛好又建立了一個新的內核對象,對以前沒清空的無效變量的訪問會形成bug。(好比對同一個句柄多調用了一次closehandle致使另外一個內核對象被關閉。)

3.         進程退出時,會釋放各類內存、內核對象、gdi對象等。

4.         跨進程使用內核對象的理由:跨進程傳輸:用文件映像對象實現共享內存、郵件槽和命名管道實現數據通訊、信號量和互斥量進行同步等。

5.         跨進程使用內核對象的三種方式:對象句柄繼承、命名內核對象、複製對象句柄。

6.         對象句柄繼承:建立內核對象的時候能夠指定security_attributes. binherithandle表示可繼承(任什麼時候候能夠使用sethandleinformation修改可繼承性等屬性),建立子進程時指定createprocess的參數binherithandles爲true,則子進程從父進程的對象表中拷貝全部可繼承的對象到本身的對象表的相同表位置中(並增長引用計數),由於表項結構被徹底拷貝且內核對象實際地址在地址空間後2g的內核地址段中,因此拷貝過來的表項徹底有效,進而父子進程的可繼承內核對象的句柄值徹底相同,因而只要以任何方式將要繼承的對象的句柄值跨進程交給子進程(建立子進程時的命令行參數、環境變量、共享內存、消息等手段),則後者能夠使用。

7.         命名內核對象:要訪問已經存在的命名內核對象,能夠使用createxxx或者openxxx,後者在對象不存在的時候返回null。若是打開了一個已經存在的命名對象,在打開時爲api指定的對象名之外的參數被忽略。注意,一個進程打開同一對象兩次,除了增長引用兩次外,返回的句柄值是不一樣的,須要分別關閉一次,即打開和關閉徹底對稱(很合理的行爲)。在vista及以上的系統開發,對象名能夠包括在命名空間下,避免被低受權用戶訪問。

8.         複製對象句柄:duplicatehandle。

第4章    進程

1.         進程是執行文件的運行時形態。包括兩部分:內核數據(對應內核對象)、地址空間(包括執行文件代碼和棧堆等動態內存)。

2.         把vc的「系統開發-子系統開發」值刪除掉,即不指定控制檯或gui,則編譯器會根據代碼中存在main或者winmain來自動選擇子系統開發(這裏不談unicode了),很方便。

3.         啓動程序:根據子系統開發執行maincrtstartup/winmaincrtstartup,在該函數中幹幾件事(1)準備命令行和環境變量(用於char *argv[]和char *env[])(2)初始化crt的全局變量(包括_osver、_winmajor、_winver、__argc、_environ等)(3)初始化crt運行庫的內存分配(malloc、free)、io函數等(4)初始化全局對象調用c++構造函數。

4.         退出程序:main返回後maincrtstartup會調用exit,exit幹如下幾件事:(1)執行經過_onexit註冊的函數(2)執行全局對象的c++析構函數(經過atexit註冊的)(3)判斷_crtdumpmemoryleaks設置的內存泄漏檢測標誌,嘗試檢測內存泄漏(4)調用exitprocess。

5.         hinstance和hmoudle徹底相同,都是表示映像文件加載到內存後的基址(連接器中能夠配置)。getmodulehandle傳入文件名能夠得到模塊基址;傳入null能夠獲得執行文件的hinstance(即便調用者位於某個模塊中一樣返回應用開發程序基址);getmodulehandleex能夠根據函數地址獲得模塊基址

6.         訪問環境變量:char *env[]參數、getenvironmentstrings、getenvironmentvariable、expandenvironmentstrings(將一個使用了相似」%userprofile%」環境變量的字符串中的變量替換成值)。

7.         系統開發環境變量:hkey_local_machine\system\currentcontrolset\control\session manager\enviroment。用戶環境變量:hkey_current_user\enviroment。

8.         修改環境變量後能夠通知相關的系統開發窗口(如控制面板等):sendmessage(hwnd_broadcast, wm_settingchange, 0, (lparam) 「enviroment」)。

9.         能夠設置特定線程在一個cpu核心集合上執行。

10.     seterrormode。設置該進程如何響應各類錯誤。

11.     關於相對路徑:在經過getenvironmentstrings返回的環境變量中,有一部分不是真正的環境變量,好比「=c:=c:\windows」「 =f:=f:\projects\test05」,他們表示一種進程相關配置「本進程在特定驅動開發器下對應的當前文件夾」。一個進程除了有以上配置外,還有一個當前驅動開發 器,最終getcurrentdirectory返回的當前路徑就是當前驅動開發 器+當前驅動開發 器對應的當前文件夾。使用setcurrentdirectory會改變該驅動開發 器的當前文件夾,還會改變進程的當前驅動開發 器(但這個api的改變並不會在getenvironmentstrings上體現出來,使用c函數_chdir能夠同時改變二者,故c函數更優)。進程剛啓動時,若是不考慮從父進程繼承的環境,則只有進程當前驅動開發 有當前文件夾,其餘驅動開發 都無配置。使用相對路徑訪問文件的時候,其絕對路徑能夠用getfullpathname獲得。」文件名」這樣的相對路徑的絕對路徑是getcurrentdirectory() + 「文件名」;」驅動開發 器盤符:文件名」(注意不是」驅動開發符:/文件名」)這樣的相對路徑的絕對路徑就是」該驅動開發 器的當前文件夾」(若是無配置,則是根目錄) + 「文件名」。

看以下代碼:

_chdir("d:/downloads"); // 修改d:的當前路徑爲downloads,且進程當前驅動開發 器爲d:

_chdir("f:/projects"); // 修改f:的當前路徑爲projects,且進程當前驅動開發 器爲f:

std::ofstream("1.txt"); // 當前驅動開發 器是f:,因此絕對路徑是f:/projects/1.txt

std::ofstream("d:1.txt"); // d:的當前路徑是downloads,因此絕對路徑是d:/downloads/1.txt

這種行爲從cmd的cd命令也能夠看得出點端倪。

概括:相對路徑訪問文件的時候,首先將相對路徑展開成絕對路徑,使用getfullpathname,後者分兩步:首先判斷是否包含驅動開發 器(以x:開頭),若是沒有,則在開頭添加進程當前驅動開發 器;而後檢查是否以」x:/」開頭,若是沒有,則將」x:」展開成」x:/」 + 「對應驅動開發 的當前文件夾」。兩步事後獲得絕對路徑。

12.     getversionex獲取系統開發版本信息。verifyversioninfo檢測當前系統開發是否知足版本須要。

13.     createprocess的參數:關於lpapplicationname和lpcommandline,有兩種用法:(1)前者指定應用開發程序路徑,後者指定參數(第一個參數前面要有一個空格,彷佛底層會直接鏈接兩個串)(2)前者爲null,後者指定路徑和參數,空格隔開。經常使用第二種開發方法 。注意,lpcommandline中因爲是用空格分隔參數的,因此對其中含有空格的路徑必定要用內層引號括起來。另外createprocessw有一個奇怪的行爲,它會修改參數lpcommandline(彷佛只在lpapplicationname爲空的時候會修改),因此使用unicode版本的時候傳入的該參數不能是常字符串(如l」nodepad 1.txt」),而應該另外準備緩衝傳給該api供其修改,由於ansi版本是調用unicode版本的且在編碼轉換的時候內置了緩衝,因此createprocessa的lpcommandline參數能夠是常串(最終api會修改轉換編碼的臨時緩衝)。默認狀況下,cui的cui型子進程會和父進程共享控制檯,在參數dwcreationflags中添加detached_process或create_new_console標誌能夠阻止這種行爲。在dwcreationflags中添加create_new_process_group標誌,能夠控制進程組的組織,用戶按下ctrl+c的時候同一進程組的全部進程獲得通知。lpenvironment指定爲null的時候,底層爲用getenvironmentstrings來填充。lpcurrentdirectory爲null的時候,子進程繼承父進程的當前目錄。lpstartupinfo不能爲空,至少要初始化結構爲0並將cb賦爲sizeof。使用startupinfoex結構做爲lpstartupinfo參數,還能夠具體指定子進程要繼承哪些父進程的可繼承內核對象(即便binherithandles參數爲false)。

14.     cmd進程輸入命令行前顯示的路徑,就是其當前路徑(getcurrentdirectory)。在createprocess時,cmd沒有設置子進程當前路徑,而資源管理器將路徑設置成子進程鏡像目錄。由於cmd的子進程會繼承cmd的當前路徑(lpcurrentdirectory爲空的結果),所以最好在用cmd啓動程序的時候先將cmd的當前路徑設置爲新進程的鏡像路徑。

15.     進程和線程結束後,句柄對象被標記爲激活, waitforsingleobject會返回。

16.     createprocess後,能夠使用waitforinputidle或相似函數來等待新進程初始化環境完畢開始運行。

17.     wow64:windows 32 on windows 64。全部64位windows運行着這個虛擬機,用來執行32位程序。判斷一個32位程序是不是運行在64位系統開發的32位虛擬機中:iswow64process。

18.     父進程建立子進程時使用的lpstartupinfo,在子進程中能夠使用getstartupinfo來查詢。

19.     建立一個子進程時,進程和主線程自己的存在就有了引用1,而調用createprocess的父進程又會有他們的引用因此計數到了2。要徹底銷燬進程和線程,須要計數爲0,因此除了須要進程自己結束外,引用的該進程的其餘線程也要釋放引用。固然,createprocess事後父進程立刻closehandle並不會結束子進程,只是釋放本身的引用,使其計數爲1,這是正常的行爲。要確保某個進程或線程不被銷燬,不調用closehandle便可。若是進程自己已經退出了,但還有其餘進程引用它,則它的地址空間被回收,只有內核對象還存在(好比這時再對句柄使用api查看內存,則內存信息爲空),這也是爲何能夠查看已經退出的進程的退出碼的緣由(退出碼保存在內核對象中)。

20.     進程和線程的id位於同一個系統開發頂層名空間。即任意進程的任意線程id毫不可能和任意進程id相同。這個id會被系統開發循環利用。

21.     getprocessidofthread。

22.     進程只有在它全部線程都結束後纔會結束。exitprocess會殺死全部線程,因此能夠直接結束進程,在主線程中調用exitthread只會結束主線程(即,主線程建立一個死循環線程後本身_exitthreadex,這個進程不會退出。)。main返回後crt調用exit後者再調用exitprocess,因此在main中return能夠直接結束進程。

23.     經過exitprocess或exitthread(單線程時)結束進程,因爲這些api比crt更底層,他們只能保證正確的釋放windows資源(內存、內核對象引用),並不保證釋放c++資源(crt底層資源、全局對象的析構函數),故必定要從main中返回天然的結束進程(其餘緣由在後面章節說明)。terminateprocess也出於相同的緣由應該避免使用。

24.     createprocess建立的子進程會繼承父進程的security token權限,而shellexecuteex能夠提升子進程的權限(令lpverb參數爲」runas」)。資源管理器使用前者建立子進程,因此經過它開打的程序都具備和資源管理器相同的權限。

25.     關於vista及更高系統開發的uac(user account control):vista之前的系統開發若是以管理員帳號登錄,資源管理器(explorer)會得到一個管理員權限的security token,而後從資源管理器打開的子進程都會繼承這個最高權限,這種行爲很是危險。vista之後,即便以管理員帳號登錄,資源管理器仍然只持有一個通常權限的token(filtered token),子進程若是想提高權限,有兩種途徑:(1)用戶「以管理員身份運行」啓動該進程(2)子進程本身提出請求要求用戶提高權限(子進程是安裝程序、或者子進程配置有.manifest文件說明權限需求)。另外,在不少軟件開發中出現有小盾牌圖標的按鈕,也是要求提升權限,點擊事後會結束當前進程,重啓一個高權限進程(如資源管理器中「顯示全部用戶的進程」按鈕)。其實這三種提升權限都是父進程調用了shellexecuteex。

26.     isuseranadmin判斷當前用戶是不是管理員。在vista及以上的系統開發中,即便是管理員,進程也有可能由於篩選token而不具有最高權限。

27.     枚舉全部進程:process32first、process32next、enumprocesses。

28.     能夠從hmoudle中讀取image_dos_header和image_nt_headers,進而從這些pe頭中取得模塊的推薦加載地址等信息。

29.     peb(process enviroment block)包含了進程的啓動命令行、當前路徑等數據。該字段能夠經過ntqueryinformationprocess的process_basic_information參數取得。

30.     能夠經過windbg的dt命令,查看一些結構的具體成員佈局,如peb等。

31.     windows完整性機制(windows integrity mechanism):這是uac以外的另外一套安全機制,windows經過在系統開發訪問控制表(sacl, system access control list)中增長訪問控制項(ace, access control entry)實現,每一種受保護的資源都有對應的完整性級別(integrity level),每一個進程都有一個基於token計算的完整性級別,若是進程的級別小於資源的級別,則不能訪問資源。提高token權限以前的進程級別爲中,提高後爲高,而像ie這樣能夠能執行網絡代碼的進程爲低。能夠經過gettokeninfomation查看一些和完整性級別相關的策略。窗口系統開發也根據完整性級別,拒絕低級別者向高級別使用postmessage、sendmessage等api。

32.     vista以上有一些進程是特殊的受保護進程,toolhelp api對他們無效,所以沒法查看進程信息。

33.     getprocesstime查看進程時間,getprocessiocounters查看io次數。

34.     getprocessimagefilename返回內核格式的文件名。

第5章               做業

1.         job(做業),也就是進程組的概念,添加進同一個做業的進程可以經過做業內核對象來集中控制,設置一些額外的屬性等。添加進一個做業就不能再移出。

2.         isprocessinjob、createjobobject、openjobobject。

3.         做業內核對象在它內部的全部進程都結束後纔會被銷燬。

4.         細節:當客戶的做業句柄變量都被關閉後,即便做業對象還存在

5.         vista以上,經過任務管理器建立的進程,都被添加進了一個獨立的做業;從命令行(cmd)建立的進程則否則。

6.         可以對做業添加的限制:基本限制(限制進程時間、優先級、物理內存佔用等)、擴展限制(基礎限制之上,還能限制內存使用總量,以及查看峯值內存使用)、ui限制(限制關機/重啓、訪問剪切板、切換桌面、改變顯示器設置、訪問做業外進程的句柄等)、安全限制(安全限制一旦設置,則不能修改)。setinformationjobobject、queryinformationjobobject用於設置和查詢限制。

7.         assignprocesstojobobject添加進程到做業。

8.         父進程位於某一做業中,子進程建立後也自動加入同一做業。除非做業的基本限制中包含job_object_limit_breakaway_ok(容許進程時脫離做業),而且createprocess時指定create_breakaway_from_job標記。

9.         terminatejobobject強制結束做業,同時結束做業內全部進程(等價於對做業內每一個進程調terminateprocess)。

10.     queryinformationjobobject除了查看做業限制外,也能夠查看做業信息,包括總進程數、活躍進程數、總時間、總io次數、進程id列表等。

11.     做業結束後(全部內部進程結束),內核對象處於激活態,waitforsingleobject返回。

12.     做業通知機制:將做業對象和io完成端口綁定,做業中的事件(進程結束、時間到期、內存達到限制等)將經過完成端口事件來通知。

第6章               線程基礎

1.         像進程同樣,線程在數據上也分爲兩個部分:線程內核對象(包括統計信息)、棧。(進程的兩個部分是,內核對象和地址空間)。­­­

2.         比起exitthread和terminatethread,應該讓線程的主函數返回來結束線程,不然一些棧對象不能正常析構(這裏再也不考慮crt函數)。

3.         在c/c++編程中不要使用createthread、exitthread,應該使用編譯器廠商提供的包裝函數,如ms的_beginthreadex、_endthreadex。由於使用前者,c/c++的crt不能正常初始化和釋放線程相關資源(c/c++中有一些全局變量如errno和一些有內部狀態的函數strtok、asctime都須要經過tls來正確實現,畢竟c庫函數的誕生早於多線程)。事實上,若是在c/c++中使用了createthread和endthread,部分有內部狀態的函數仍是能夠正常使用的,由於這些函數內部會嘗試取得tls,發現還未分配的話會自動分配,crt的dll版本庫也會在獲得線程退出通知時嘗試釋放tls,只是由於這份tls是中途分配的信息不夠全面,部分狀態函數仍是會有問題,所以在c/c++中仍是要儘可能使用後者。

4.         線程棧最大爲createthread的dwstacksize參數和/stack連接選項(vc中默認爲1mb)二者中的較大值。

5.         terminatethread的一些細節:該函數是異步的,函數返回時,線程尚未結束,須要waitforsingleobject;dllmain不會收到被terminate線程的結束通知。

6.         只有當線程函數結束(正常返回或exit掉)後,該線程的棧空間纔會被回收(也就是說terminatethread函數剛返回時被殺死線程棧空間還在,直到線程對象處於激活態)。

7.         對進程中的各個線程來講,exitprocess和terminateprocess都將致使對線程的terminatethread調用,所以進程的main函數結束前,儘可能確保工做線程都正常退出。

8.         大部分的資源都是進程相關的,窗口句柄和hook句柄是線程相關的,線程退出時會釋放他們(在c/c++中還有crt的tls變量)。

9.         getcurrentprocess、getcurrentthread返回的都是僞句柄,若是想要把這個句柄保存下來在其餘線程、進程中使用的話,是有歧義的,能夠用保存id來代替,若是必定要保存句柄的話,兩種開發方法 :(1)duplicatehandle(2)先getcurrentthreadid,再openthread。

第7章   線程調度、優先級和關聯性

1.         windows線程調度的時間間隔(發生上下文切換的時間片)大概是15毫秒(getsystemtimeadjustment的lptimeincrement參數)。

2.         每一個線程都有一個掛起計數,當計數非0的時候,該線程不參與線程調度。createthread、createprocess傳入特定的參數能夠使計數初始化爲1。suspendthread能夠增長計數,resumethread能夠減小計數,二者都返回新的掛起計數。顯然線程沒法對自身調用resumethread。

3.         調試進程的waitfordebugevent返回後,被調試進程的全部線程被掛起,直到調試進程調用continuedebugevent。

4.         sleep的休眠時間可能不精確,取決於線程調度時間片大小(通常是15毫秒左右)以及其餘線程的運行狀況。

5.         sleep(0)和switchtothread的區別在於:若是存在另外一個更低優先級的線程,前者不會將cpu讓出,然後者會。即若是存在多個線程,switchthread老是讓出cpu。

6.         yieldprocessor用於支持超線程技術的cpu切換超線程。

7.         getthreadtimes、getprocesstimes返回指定線程或進程的內核代碼時間和用戶代碼時間(二者都是絕對的cpu執行代碼時間,不包括調度開發過程 中的中斷時間以及主動的sleep或者wait時間)。所以在對代碼段計時的時候,使用getthreadtimes明顯優於gettickcount等,由於後者得出的時間包括了其餘線程的時間片。

8.         用於計時,最基本的有clock、gettickcount、timegettime等;爲了地提升精度,能夠使用queryperformancecounter;爲了去掉因線程調度中斷的時間和sleep、wait的時間,能夠使用getthreadtimes、getprocesstimes等。在vista以上的系統開發中,有新的機制,能夠使用readtimestampcounter(對應gettickcount)、querythreadcycletime(不考慮中斷休眠,對應getthreadtimes)、queryprocesscycletime等。對於沒有考慮線程調度影響的函數,能夠先用setthreadpriority提升優先級儘可能獨佔時間片。應該確保每次調用queryperformancecounter的時候在同一cpu核心上,使用setthreadaffinitymask。

9.         線程上下文(context)保存在線程的內核對象數據中,主要包括線程相關的cpu寄存器狀態等。上下文有兩份,分別記錄內核和用戶模式,getthreadcontext只能返回用戶模式上下文,在調用該函數前應該確保用戶上下文再也不改變了,即線程正處於內核態或者雖然在用戶態但已經調用過suspendthread。

10.     先suspendthread、再setthreadcontext改變線程上下文,能夠改變執行流等,通常用於調試器 「跳到指定位置執行」 的功能等。

11.     高優先級線程能夠被調度時(沒有sleep、wait等),低優先級線程得不到時間片;即便低優先級線程正在執行,一旦有高優先級線程能夠調度,前者會被中斷並讓出cpu資源。

12.     setpriorityclass設置進程的優先級類,setthreadpriority設置線程的相對優先級(相對於進程優先級類),兩者共同決定線程的實際優先級(這個映射根據windows版本不一樣而異,是一個0~31的整數,用戶不可訪問)。將線程的實際優先級設置爲最高(31)是危險的,由於它將搶佔系統開發資源,致使io不能響應等。

13.     當線程有io事件或消息到來時,操做系統會暫時提升線程的優先級;或者線程可調度但長時間(數秒)都得不到時間片的時候,系統開發也會暫時提升線程優先級。能夠設置是否容許系統開發自動提高優先級:setprocesspriorityboost、setthreadpriorityboost。

14.     特定類型計算機的幾個相關cpu核心之間能夠共享內存緩存等,所以windows支持設置線程關聯cpu核心setprocessaffinitymask、setthreadaffinitymask。固然這組api也能夠用於爲特定線程提供專用cpu資源以提高性能開發。子進程默認繼承父進程的核心關聯設置。

15.     setthreadidealprocessor設置線程最多能夠使用的閒置cpu數量。該設置會覆蓋affinitymask。

16.     進程的默認affinitymask能夠在鏡像文件頭中設置(由於沒有連接選項只有手工寫文件):imageload->getimageconfiginformation->ilcd.processaffinitymask->setimageconfiginformation->imageunload。

第8章   用戶模式下的線程同步

1.         interlocked系列函數:interlockedincrement(對應++)、interlockedexchangeadd(對應+=)、interlockedexchange(對應=)、interlockedcompareexchange(cas)。

2.         _aligned_malloc能夠指定分配內存的對齊邊界。

3.         spinlock(自旋鎖)是cas的應用開發。使用自旋鎖的時候由於有while(true) { …; sleep(0); }這樣的循環,所以線程優先級不能過高,使用setthreadpriorityboost來禁用優先級提高,避免被自動提高後不會讓出cpu(或者使用switchtothread)。自旋鎖適用於單個線程不會佔用資源過久的狀況(由於一個線程佔有資源期間,其餘線程在循環檢測浪費cpu)。

4.         cas(interlockedcompareexchange)必須是原語!必須!用c++編寫的cas是不行的。

5.         initializeslisthead、interlockedpushentryslist、querydepthslist等api能夠以interlocked的方式操做一個單鏈表。

6.         cacheline:是cache和內存通訊的基本單位,多是32/64字節等,cpu讀寫內存的時候會先將對應的cacheline加載進cache,修改完成後flush到內存上。所以數據組織爲cacheline size對齊、以及將只讀和讀寫數據分別組織到不一樣的cacheline都能提升效率。多個cpu(或者具備獨立cache的多個cpu核心)訪問同一地址時,該地址附近的數據會被多個cache映射成各自的cacheline,若是其中某個cpu修改了其cacheline的數據,該cpu會通知其餘cpu更新各自的cacheline,這種行爲會影響性能,故儘可能避免跨線程共享數據以及利用affinitymask儘可能使用同一個cpu。

7.         getlogicalprocessorinformation提供cpu描述信息(好比可以查詢到包括4個cpu核心,3級cache,一、2級cache爲各個核心獨有,3級cache爲共享cache,其cache line size爲64字節等)。

8.         全部線程都處於等待狀態數分鐘後,電源管理器介入。

9.         volatile的做用:編譯器不會將變量優化成寄存器變量,即每次讀寫都會訪問內存。對struct應用開發該關鍵字會影響每一個字段。

10.     critical_section內部記錄了擁有訪問權的線程以及引用次數。tryentercriticalsection若是返回true,則已經增長了計數須要對稱調用leavecriticalsection。

11.     critical_section在實現上結合了spinlock(自旋鎖),調用entercriticalsection時發現資源正被佔用須要切換到內核態休眠以前(切換到內核態開銷很大,高達數千cpu週期),能夠嘗試進行必定次數的循環判斷。使用initializecriticalsectionandspincount能夠啓用結合自旋鎖功能(做爲參考,用於保護進程堆的cs的spincount爲4000),使用setcriticalsectionspincount能夠修改旋轉次數。當spincount爲1的時候,關鍵段內部用於休眠和喚醒的事件對象會第一時間建立,而不是等到entercriticalsection的時候才建立。建議老是啓用自旋鎖。

12.     slim reader/writer lock是性能比關鍵段更好的選擇,相比後者,它的缺陷是不能遞歸加鎖、且沒有trylock。initializesrwlock、acquiresrwlockshared(申請讀鎖)、acquiresrwlockexclusive(申請寫鎖)。

13.     在都能完成任務的狀況下,性能從高到底依次是:無鎖、volatile、interlocked、srw、critical_section、內核對象(由於切換到內核態開銷很大)。

14.     sleepconditionvariablecs、sleepconditionvariablesrw用法:已經得到鎖(cs、srw)的線程開始在一個conditionvariable對象上睡眠,同時釋放鎖;若是其餘線程wakeup這個conditionvariable對象,則函數返回true,且再度得到鎖;若是超時,返回false,不會得到鎖。應用開發:消費者得到鎖後發現沒有產品因而開始休眠等待生產者產出產品後喚醒。

15.     技巧:按資源的邏輯個數而不是對象個數來組織鎖;須要加多層鎖的時候,老是按固定順序,好比按鎖的地址大小來依次加鎖,避免死鎖;經過拷貝資源等方式來減少鎖粒度。

第9章   用內核對象進行線程同步

1.         內核對象用於線程同步更靈活好比能夠設置等待時間以及跨進程等,但開銷更大(須要切換到內核模式)。

2.         內核對象中都有一個表示觸發狀態的boolean值。

3.         進程和線程對象在結束前是非觸發,結束後是觸發狀態,其餘時候不會再改變。

4.         文件對象有正在處理的異步io請求時處於非觸發,其餘時候觸發。

5.         控制檯輸入句柄在沒有輸入的時候非觸發。

6.         內核對象觸發後,wait在上面的線程被喚醒,決定哪個線程首先被喚醒的規則基本上就是等待順序的先入先出,和線程的優先級等無關。

7.         pulseevent會在event對象上產生一個觸發脈衝。近似於setevent(h);resetevent(h);兩句。

8.         waitabletimer在平時處於非觸發,第一次時間到或者以後週期性時間到都會處於觸發狀態。另外在setwaitabletimer的時候能夠傳入回調指定在觸發的時候往apc(asynchronous procedure call)隊列中加入回調,但必須定時器觸發時線程正處於alertable(使用sleepex等帶ex的api)狀態下才會入隊列(避免由於回調處理太慢及其餘因素致使過量入隊)。通常定時器的apc和waitfor兩種模式不混用。setwaitabletimer指定第一次的時間時,正數表示絕對時間(systemtimetofiletime獲得),負數表示相對時間。每次調用setwaitabletimer會自動取消上次調用的設置,故兩次調用間沒必要cancelwaitabletimer。該定時器和基於消息的settimer定時器建議適時選用。

9.         semaphore的當前計數非0時處於觸發。releasesemaphore增長計數發現達到最大時會返回false,waitfor減小計數到0的時候會休眠。

10.     mutex和criticalsection在使用上徹底相同,都記錄了owner線程和遞歸次數。因爲criticalsection和mutex記錄了owner線程,所以須要該線程來釋放計數,若是在計數減小到0前線程退出了,則同步對象處於abandoned(遺棄)狀態。對於abandoned的狀況,系統開發能檢測到發生在mutex上的問題,並在底層自動釋放計數,只是waitfor會返回wait_abandoned表示mutex對象的計數是由系統開發自動回收的,該mutex保護的資源可能處在未定義狀態。而cs的計數不會被自動釋放,一旦abandoned則cs永遠的失效了。

11.     waitforinputidle:進程中建立第一個窗口的線程的消息隊列中沒有須要處理的輸入消息後返回。

12.     msgwaitformultipleobjects:等待的內核對象觸發後或者線程的消息隊列中有相應消息後返回。

13.     signalobjectandwait增長一個對象計數的同時原子地等待另外一個對象。可以增長計數的對象只限於event(setevent)、mutex(releasemutex)、semaphore(releasesemaphore),而等待的對象類型不限。使用:客戶端填充好請求因而通知服務端準備處理並等待服務端處理完畢。

14.     在vista以上能夠經過wct(等待鏈遍歷,wait chain traversal)相關api來追蹤死鎖。openthreadwaitchainsession、getthreadwaitchain。

第10章  同步設備i/o與異步設備i/o

1.         打開設備的方式:文件-createfile,參數時路徑名或unc路徑名。目錄-createfile,參數爲路徑名或unc路徑名,另外指定file_flag_backup_semantics容許改變目錄屬性。邏輯磁盤驅動開發 器-createfile,參數爲」」」 \\.\x:」,打開後能夠格式化和檢測大小等。物理磁盤驅動開發 器-createfile,參數爲」」」 \\.\physicaldrivex」,(其中x爲012等)。串口-createfile,參數爲」」 comx」。並口-createfile,參數爲」」 lptx」。郵件槽服務器開發-createmailslot,參數爲」\\.\mailslot\abcd」。郵件槽客戶端-createfile,參數爲」」\\servername\mailslot\abcd」」。命名管道服務器開發-createnamedpipe,參數爲」\\.\pipe\abcd 「。命名管道客戶端-createfile,參數爲」」\\servername\pipe\abcd 「。匿名管道-createpipe。套接字-socket、accept、acceptex。控制檯-createconsolescreenbuffer、getstdhandle。前面的設備路徑規則:」」」」\\服務器開發\設備」,其中若是在本機的話,服務器開發就是」」 .」。

2.         setcommconfig能夠設置串口波特率等屬性。

3.         setmailslotinfo能夠設置超時。

4.         通常用closehandle關閉設備。closesocket關閉套接字。

5.         getfiletype能夠返回設備的類型:file_type_disk-磁盤文件;file_type_char-字符文件,包括控制檯和打印機等;file_type_pipe-命名管道或匿名管道。

6.         屢次createfile打開同一個文件獲得的是不一樣的內核對象,各自維護本身的文件指針等數據; duplicatehandle獲得的多個句柄仍然標誌的是同一個對象。

7.         createfile的dwsharemode參數:0表示獨佔,若是文件已經被打開,則本次打開失敗;若是本次打開成功,在關閉前不能在其餘地方打開同一個文件。file_share_read,若是本次打開前已經有寫句柄,本次打開失敗;若是本次打開成功,在關閉前在其餘地方不能打開寫句柄。file_share_write也相似。file_share_delete表示,若是本次打開成功,其餘地方又刪除了文件,則刪除時只是打上刪除標記,待這裏的句柄關閉後才真正刪除。

8.         createfile的dwflagsandattributes參數:(1)關於內置緩衝。內置緩衝至少有兩個做用,首先,加速,頻繁的小字節塊訪問會被緩衝爲少數大字節塊的設備讀寫;其次,最底層設備訪問須要按必定的字節塊對齊(文件無緩衝讀寫須要按磁盤扇區大小對齊),緩衝屏蔽了這個限制,方便上層使用。file_flag_no_buffering,底層不提供緩衝,須要上層本身提供緩衝,緩衝區首地址、文件讀寫偏移/指針、讀寫字節數三者都必須按磁盤扇區大小對齊(扇區大小能夠經過getdiskfreespace得到,好比512字節)。文件太大有可能打開失敗,也須要指定這個標記。當有緩衝時,file_flag_sequential_scan承諾會連續訪問(不會用setfilepointer),所以底層能夠嘗試緩衝更多連續內容;file_flag_random_acess表示會隨機訪問,所以底層會盡可能不要緩衝太多(緩衝的做用還剩下避免要求扇區對齊)。file_flag_write_through,表示寫文件不使用緩衝,這樣避免在數據flush到文件前對象就被非法關閉致使數據丟失。(2)其餘標誌。(1)file_flag_delete_on_close,關閉文件的時候刪除,適合臨時文件。file_flag_overlapped異步io。

9.         createfile的dwflagsandattributes參數:只在建立文件的時候有效,用於指定archive、encrypted(加密)、hidden、readonly、system、temporary等屬性

10.     createfile的hfiletemplate參數:只在建立新文件時有效,傳入另外一個文件句柄的話,系統開發會忽略dwflagsandattributes參數和直接使用該句柄對應的dwflagsandattributes。

11.     file_attribute_temporary和file_flag_delete_on_close標記結合適用於臨時文件,前者會讓系統開發儘可能將文件維護在內存而不是磁盤中,後者會在關閉句柄時刪除文件。

12.     獲取文件大小:getfilesizeex、getcompressedfilesize(尤爲針對壓縮屬性的文件)分別返回邏輯大小和磁盤上的實際大小。

13.     setfilepointerex能夠超出文件實際大小,超出後,除非寫文件或者setendoffile不然文件不會變大。

14.     setendoffile是減少文件的惟一手段。

15.     flushfilebuffers。

16.     在vista以上,能夠用cancelsynchronousio來停止一個線程的同步io。

17.     異步io的實際訪問設備順序不必定和請求順序(api調用順序)相同(好比驅動開發 會根據磁盤磁頭位置選擇先處理距離最近的io請求)。

18.     對異步io的文件發出io請求有多是同步操做,由於可能數據正好在底層緩衝中能夠當即完成。

19.     關於取消異步io請求:(1)cancelio取消調用線程在指定設備上的異步io請求。(2)線程結束會取消該線程的全部異步請求。(3)關閉設備會取消全部該設備的請求。(4)cancelioex能取消調用線程之外線程在指定設備上的特定請求。(5)cancelioex能取消特定設備的全部請求。

20.     overlapped結構的internal表示錯誤碼,internalhigh表示傳輸的字節。因爲異步io跟文件指針無關(文件指針來不及修改),因此偏移存儲在該結構中。

21.     getoverlappedresult函數實現爲,訪問結構的internal、internalhigh字段,另外若是結構的hevent爲空嘗試wait設備不然wait事件(函數參數bwait爲true的時候)。

22.     queueuserapc向線程的apc隊列拋出一個用戶自定義函數。

23.     queueuserworkitem向線程池拋出任務。

24.     異步io有四種方式獲得完畢通知:(1)設備內核對象觸發。(2)overlapped的hevent內核對象觸發。(3)apc回調(readfileex)。(4)io完成端口。

25.     異步io-設備內對象觸發:對file_flag_overlapped的文件使用readfile,將overlapped的hevent設置爲空,io完成時設備句柄將觸發,所以只能同時進行一次io(瓶頸)。能夠一個線程請求,另外一線程響應完成。

26.     異步io-事件內核對象的觸發:將overlapped的hevent設置爲事件以得到通知。能夠用setfilecompletionnotificationmodes來避免io完成時去觸發設備對象。能夠一個線程請求,另外一線程響應完成。

27.     異步io-apc隊列:readfileex後使用sleepex等讓線程進入alertable狀態。同一個線程發出請求和響應完成(瓶頸)。

28.     異步io-io完成端口:步驟(1)createiocomplitionport建立完成端口,指定活躍線程數(建議爲cpu核心數)。(2)用createiocomplitionport向完成端口添加異步設備。(3)建立完成端口服務線程(建議爲cpu核心*2個,或者動態估計),初始化後使用getqueuedcompletionstatus使線程和完成端口綁定並休眠。(4)執行異步io,io完成後底層會用postqueuedcompletionstatus令正在getqueuedcompletionstatus上休眠的服務線程甦醒響應。細節:能夠在overlapped的hevent指定一個值爲hevent | 1的數,令io完成後不發出完成通知(即不post)。能夠使用getqueuedcompletionstatusex來一次響應多個請求。完成端口服務線程中,使用getqueuedcompletionstatus休眠的線程叫等待線程,從getqueued…返回的線程叫釋放線程(活躍線程),活躍線程若是因其餘緣由(如sleep、wait)再掛起叫暫停線程,完成端口可以檢測到各個線程的數量,會控制getqueuedcompletionstatus的返回以使活躍線程儘可能逼近建立完成端口時指定的數目。默認狀況下異步io即便同步完成,也會post…,能夠使用setfilecompletionnotificationmodes來禁用post…。對於完成事件的響應是先入先出的,但服務線程的激活倒是後入先出的(儘可能激活相同線程,其餘線程長期休眠其棧內存能夠換出到頁面文件提高性能開發)。

第11章  線程池的使用(第4版)

1.         messagebox彈出的對話框是可用修改的,findwindow找到後,0x0000ffff是靜態文本框的控件id等,所以很容易實現倒計時自動關閉的消息框。

2.         從win2000開始提供的線程池主要有4種用法:(1)異步調用函數(queueuserworkitem)。(2)定時器回調(createtimerqueuetimer)。(3)內核對象觸發後回調(regis地理信息系統 terwaitforsingleobject)。(4)內置iocp實現(bindiocompletioncallback)。

3.         線程池模塊下有幾種底層線程:(1)可變數量的長任務線程,用於執行標記爲wt_executelongfunction的長時間回調。(2)1個timer線程。全部createtimerqueuetimer調用都被轉發爲在timer線程上建立以apc方式通知的waitabletimer,這個線程除了刪除和建立waitabletimer外,就是在alertable態下休眠等待定時器的apc。因爲這個線程一旦建立就貫穿進程生命期不會銷燬,所以wt_executeinpersistentthread標誌的線程池回調也由本線程執行。(3)多個wait線程。服務於regis地理信息系統 terwaitforsingleobject,每一個線程用waitformultipleobjects等待最多63(maximum_wait_objects減去一個用於維護對象數組的工做對象)個內核對象,對象觸發後執行回調。(4)可變數量的io線程。因爲發出異步io請求(readfileex)後,一旦請求線程結束,請求將被撤銷,所以請求被驅動開發 執行完畢以前io請求線程必定要存在,而線程池內的線程大都會根據cpu繁忙狀況動態建立和刪除,所以線程池中有一部分線程被賦予了特殊行爲,他們會檢測本身執行回調時發出的異步io請求是否完成,若是沒有,就不會結束運行,這些追蹤自身發起的異步io請求執行狀況的特殊線程叫作io線程。所以只能在線程池的io線程上執行異步io調用。(5)可變數量的非io線程。線程池內部實現了一個io完成端口,服務於bindiocompletioncallback,其中iocp的服務線程(在getqueuedcompletionstatus上休眠)因爲數量會根據cpu狀況動態調整,不應用開發於執行異步io,故叫非io線程。

4.         四種用法中,若是flags參數指定的回調執行線程與默認線程不符,底層能夠使用queueuserworkitem來切換線程。好比createtimerqueuetimer用法的默認線程確定是timer線程,發現wt_executelongfunction標記後,使用queue…來切換到專門執行長任務的線程避免阻塞timer線程影響定時器功能。

5.         用法1-異步函數調用:queueuserworkitem 。flags參數爲0(wt_executedefault)的時候回調交給非io線程執行(經過postqueuedcompletionstatus通知非io線程)。還能夠指定wt_executeiniothread交給io線程、指定wt_executeinpersistentthread交給timer線程、指定wt_executelongfunction交給長任務線程等。

6.         用法2-定時器回調:createtimerqueue-建立專用timerqueue。deletetimerqueueex-刪除專用timerqueue,參數completionevent是用於接受刪除queue完畢通知的事件對象,若是設置爲null表示不接受通知,設置爲invalid_handle_value表示阻塞等待刪除完成。注意不能在timer線程上的回調中以invalid_handle_value爲參數調用deletetimerqueueex,由於後者實現爲向timer線程拋出一個要求維護timer列表的apc,在線程的apc回調中拋出新的apc而且還阻塞等待,結果就是死鎖。createtimerqueuetimer-建立具體的timer對象,timerqueue參數指定爲null表示在默認的queue上建立對象,適用於timer對象很少的用法。使用wt_executeintimerthread標記即要求在timer線程上執行回調,因沒必要切換線程效率較高,注意回調不能過長影響timer線程的功能。changetimerqueuetimer-改變timer對象的一些參數。deletetimerqueuetimer-刪除timer對象,注意使用invalid_handle_value參數形成死鎖的可能。

7.         用法3-等待內核對象觸發回調:regis地理信息系統 terwaitforsingleobject-在內核對象觸發或超時後執行回調。標記wt_executeinwaitthread表示在wait線程上執行,效率較高。wt_executeonlyonce只執行一次回調,適用於進程/線程句柄這種觸發後再也不重置的對象。pulseevent的脈衝可能不會被wait線程檢測到(線程恰好在幹其餘事)。unregis地理信息系統 terwaitex-取消回調,注意invalid_handle_value參數可能的死鎖。

8.         用法4-內置iocp實現:bindiocompletioncallback。將異步io設備和內置的io完成端口管理起來,異步完成後執行回調。標誌只能爲0,默認在非io線程(iocp的服務線程)上執行,若是須要切換線程,手工queueuserworkitem。

第11章  windows線程池

1.         visita以上的新線程池框架下四種用法:(1)異步調用函數(trysubmitthreadpoolcallback、createthreadpoolwork)。(2)定時器回調(createthreadpooltimer)。(3)內核對象觸發後回調(createthreadpoolwait)。(4)內置iocp實現(createthreadpoolio)。

2.         新線程池的實現包括iocp。

3.         用法1-異步函數調用:trysubmitthreadpoolcallback-經過iocp的post…提交一個回調到線程池。使用work對象容許一次建立屢次提交效率更高:createthreadpoolwork、submitthreadpoolwork、waitforthreadpoolworkcallbacks、closethreadpoolwork。其中waitfor能夠等待全部提交項被執行完畢,或者取消掉進入隊列但還沒開始執行的項。注意不該該在回調中waitfor,可能死鎖。

4.         用法2-定時器回調:createthreadpooltimer、closethreadpooltimer -建立/刪除。setthreadpooltimer-設置timer參數。起始時間爲-1表示當即開始。若是將起始時間設置爲null,表示中止timer,中止後用isthreadpooltimerset判斷返回false。另外mswindowlength表示容許回調觸發時間有一個向後的波動(0~mswindowlength),這樣底層能夠在這個波動範圍內將多個回調連續執行,避免屢次wait和wakeup(好比timer a、b分別在五、6秒後執行,a的波動爲2秒,這樣系統開發能夠連續執行a、b回調,沒必要在二者之間插入sleepex致使額外的線程切換開銷)。

5.         用法3-等待內核對象觸發回調:createthreadpoolwait、closethreadpoolwait、waitforthreadpoolwaitcallbacks相似前面。setthreadpoolwait指定要等待的內核對象,每次調用只會致使執行一次回調,除非再set…(即若是wait進程句柄,進程結束後只會執行一次回調,想要多執行須要再調用set…)。pulseevent的脈衝有可能不會觸發回調。

6.         用法4-內置iocp實現:createthreadpoolio、closethreadpoolio同前面。每次異步io請求以前(readfileex)須要調用startthreadpoolio。發出io請求後中止回調用cancelthreadpoolio。

7.         對於新線程池回調中的參數ptp_callback_instance,能夠執行一些操做:leavecriticalsectionwhencallbackreturns、releasemutextwhencallbackreturns、releasesemaphorewhencallbackreturns、seteventwhencallbackreturns-這些函數都近似等價於在回調的最後一行釋放相關資源(模仿raii?),不過以上api只有最後一次調用有效(即只能註冊一個資源)。freelibrarywhencallbackreturns-回調返回後釋放某個dll,當回調代碼自己位於要釋放的dll中時有價值。callbackmayrunlong-通知線程池回調可能執行較長時間,返回true表示當前線程池有空閒線程,不然表示線程池緊張,建議將剩餘執行任務拆分以減小回調時間。disassociatecurrentthreadfromcallbacks-通常回調返回後,回調就和執行線程解除關係了,那些waitforthreadpool…callbacks就能返回,而這個disassociate函數就是爲了在回調結束前提早打上脫離關係的標記,影響包括waitforthreadpool…的函數等。

8.         定製私有線程池:createthreadpool、closethreadpool、setthreadpoolthreadmaximum、setthreadpoolthreadminimum-建立線程池對象,設置線程數量範圍。注意若是數量上下界相同,那麼在線程池中的線程一旦建立就不會銷燬,能夠用來進行異步io調用等。initializethreadpoolenviroment、destroythreadpoolenviroment-構建環境。setthreadpoolcallbackpool-將線程池對象置入環境。setthreadpoolcallbackrunslong-標記環境對應的線程池用於執行長任務。setthreadpoolcallbacklibrary-標記環境對應的線程池中有任務執行期間,該dll一直在內存中。

9.         線程池清理組(cleanupgroup):一個waitforthreadpool…callbacks+closethreadpool…的可選替代方案。createthreadpoolcleanupgroup、closethreadpoolcleanupgroup-建立/刪除。setthreadpoolcallbackcleanupgroup-將清理組置入環境。closethreadpoolcleanupgroupmembers-用來在線程池關閉前清理資源,一旦調用該函數就沒必要再 「遍歷每種資源(work、timer、wait、io)依次調用waitforthreadpool…callbacks、closethreadpool…」,即該函數調用後,全部之前的線程池組件都被銷燬了,句柄也失效。若是該函數的bcancelpendingcallbacks參數爲true,那些還在線程池中排隊的任務直接取消再也不執行,但會經過setthreadpoolcallbackcleanupgroup註冊的函數通知每一個被直接取消掉的任務。

第12章  纖程

1.         纖程其實就是windows在用戶模式實現的協程(coroutine)。

2.         將線程自身轉化爲纖程:convertthreadtofiber-它會建立相應的結構保存當前線程的各類寄存器等數據。convertthreadtofiberex-默認的結構中是不包含浮點寄存器的,使用這個api傳入fiber_flag_float_switch能夠保證浮點運算正確。convertfibertothread-當不使用纖程後,應該用這種方式還原爲線程。

3.         createfiber、createfiberex:建立一個包括獨立棧和寄存器記錄結構的新纖程,後一個函數可以指定初始化的棧物理內存、虛擬內存以及浮點寄存器支持標誌。不使用這種纖程後,在其餘纖程中使用deletefiber來結束create出來的纖程。

4.         從纖程函數中返回會結束當前線程(固然也結束該線程上全部其餘纖程)。

5.         switchtofiber-切換纖程。

6.         fls支持(fiber local storage):flsalloc-能夠指定一個回調,這個回調在flsfree或纖程銷燬時以flsgetvalue的返回值爲參數被執行,可用於清理等。flsgetvalue、flssetvalue。

7.         isthreadafiber-判斷當前是否在某個纖程的上下文中執行。getcurrentfiber-返回當前纖程上下文。getfiberdata-返回當前纖程主函數的參數。

第13章  windows內存體系結構

1.         在32位系統開發上,虛擬地址空間大體分爲4段(64位系統開發也分爲4段,只是大小不一樣):(1)0x00000000~0x0000ffff,空指針賦值區,輔助調試,禁止任何方式的訪問。(2)0x00010000~0x7ffeffff,用戶模式分區,各進程單獨維護,同一地址值在不一樣的進程能夠有不一樣解釋,各類映像文件(dll、exe)和內存映射文件也載入本區,近2g。(3)0x7fff0000~0x7fffffff,64k禁入分區。(4)0x80000000~0xffffffff,內核模式分區,系統開發存放內核代碼、設備驅動開發 代碼、輸入輸出高速緩存、進程頁表等,2g。

2.         32位系統開發能夠配置系統開發參數讓進程用戶模式分區達到3g,內核減少爲1g。內核內存減少,會影響能夠建立的總線程、內核對象數量等。(visita系統開發以上,使用bcdedit /set increaseuserva 3072;xp使用…)。

3.         連接選項-啓用大地址(/largeaddressaware):由於過去32位系統開發用戶地址空間固定爲2g(直到能夠設置用戶地址最大到3g),因此有慣用法依賴於這種行爲(系統開發對地址參數會先&0x7fffffff的行爲)擅自將地址最高位用於其餘目的,爲了兼容大量的這種用法而且又容許選擇使用3g用戶內存,ms增長了這個連接選項。若是開啓,表示承諾不使用最高位,想要訪問超過2g的用戶地址;關閉,表示只使用2g內存,最高位可能有其餘解釋(在實際的系統開發實現上,若是用戶地址最高位非0會報錯)。64位系統開發中,爲了便於大量32位程序向64位移植(32位程序中有大量用法如:int i = (int)p; …; int *p = (int)i;),系統開發默認程序只使用2g用戶空間,因此分配的用戶地址老是小於2g,直到開啓該鏈接選項。總之,不管32位或64位系統開發,若是隻使用2g,關閉選項,不然開啓。

 

4.         virtualalloc的mem_reserve參數表示要預約一段空間(如線程棧,即便大部分時候棧都很小,但也須要預留1m左右),叫區域(region)。用戶代碼申請預留的起始地址必須按allocation granularity(分配粒度,因cpu而異,但當前cpu大都爲64kb)對齊,系統開發的預留申請無限制(如peb佔用的內存是系統開發申請的)。預留的大小必須按頁面大小對齊(x8六、x64cpu的頁面大小爲4kb)。virtualalloc的mem_commit參數表示將區域commit給虛擬存儲器系統開發會在使用時將對應的頁緩存到物理存儲器

5.         在操做系統內存管理模型中,虛擬地址用於訪問虛擬存儲器,後者存放於磁盤上,主存做爲虛擬存儲器和cpu之間的緩存(dram)被叫作物理存儲器。當cpu要訪問內存時,首先,檢查該虛擬地址是否對應合法的虛擬存儲器(是否commit),若是不然報錯表示無效地址,若是是,而後判斷該虛擬頁(vp,virtual page)是否被緩存到內存,便是否有對應的物理頁(pp, physical page),若是不然產生缺頁錯誤(page fault)進而判斷主存中是否有閒置頁面,若是沒有閒置頁面,則嘗試釋放一個物理頁,先判斷要釋放的物理頁是否被修改,若是被修改了則flush到對應的虛擬頁上而後釋放物理頁,有了閒置的物理頁後,將虛擬地址對應的虛擬頁緩存到空閒的物理頁上進而更新虛擬地址到物理地址的映射表,而後cpu的mmu(memory management unit,內存管理單元)將虛擬地址翻譯爲物理地址,再判斷該地址對應的內容是否已經在cache上,若是不然cache miss而後再將對應的cache line緩存到cache中,最後讀取到cpu寄存器中。在windows中,虛擬存儲器對應的磁盤空間進一步細分到頁交換文件(page file)、映像文件(exe、dll)、內存映射文件(mapped file)中,後二者被當作虛擬存儲器的時候還能夠在多個進程間共享(寫時拷貝),因爲存在共享機制於是windows的虛擬存儲器佔的磁盤空間遠小於全部進程提交的用戶模式內存之和。

6.         virtualalloc、virtualprotect等函數能夠設置頁保護屬性:page_execute(只能運行代碼不能讀寫)、page_execute_read(只讀和運行代碼)、page_noaccess等。其中page_writecopy、page_execute_writecopy屬性表示頁面能夠被多個進程共享,直到被修改,修改時是先拷貝到進程私有頁中再修改私有頁,這是copy-on-write。reserve狀態下的保護屬性會被commit下的屬性覆蓋,但二者均可以在virtualquery中查詢到。

7.         在cpu體系結構中,cpu要訪問的數據須要按數據大小對齊(word 地址按2對齊,dword 地址按4對齊),不然會產生異常。修復數據未對齊異常有幾種途徑:(1)x86 cpu會自動進行其餘硬件 修復,訪問沒對齊的數據只是更慢。(2)seterrormode傳入sem_noalignmentfaultexcept參數,通知windows經過軟件開發修復未對齊問題。(3)編譯選項__unaligned會自動產生額外代碼修復問題。綜上,後兩種軟件開發修復方案適用於非x86 cpu速度更慢,最好仍是按數據大小對齊內存。

第14章  探索虛擬內存

1.         工做集(working set):緩存到主存中的那些頁面。

2.         32位系統開發中的32位程序和64位系統開發中的64位程序,都用getsysteminfo來獲取系統開發信息,而64位系統開發中的32位程序(iswow64process返回true)用getnativesysteminfo。獲取處理器信息用getlogicalprocessorinformation,獲取內存信息用globalmemorystatus。

3.         system_info(getsysteminfo)各字段的解釋:dwpagesize-頁面大小。lpminimumapplicationaddress、lpmaximumapplicationaddress-用戶模式內存大小,32位系統開發中是0x0001000到0x7ffeffff。dwactiveprocessormask-cpu掩碼,同affinitymask。dwnumberofprocessors-處理器個數。wprocessorarchitecture、wprocessorlevel、wprocessorrevision-決定cpu型號。

4.         memorystatus(globalmemorystatus)各字段的解釋:dwmemoryload-內存管理系統開發負載的大體估計,0~100,能夠忽略。dwtotalphys、dwavailphys-系統開發總的物理內存和剩餘物理內存。dwtotalpagefile、dwavailpagefile-系統開發總的頁交換文件和剩餘頁交換文件。dwtotalvirtual-系統開發各進程最大用戶模式內存(32位是2g-128k)。dwavailvirtual-當前進程剩餘用戶模式內存。

5.         process_memory_counters_ex(getprocessmemoryinfo)各字段的解釋:pagefaultcount-缺頁錯誤數。workingsetsize-工做集,即當前進程物理內存佔用。pagefileusage-當前進程的頁交換文件佔用(包括所有的類型爲private內存塊和部分的image、mapped塊,後者在寫拷貝後其虛擬存儲器才轉移到page file中)。privateusage-當前進程私有的內存佔用,其虛擬存儲器位於頁交換文件中,虛擬存儲器中除去這部分其餘的都位於共享文件中了(通常值等於pagefileusage)。

6.         numa(非統一內存訪問,non-uniform memory access。一種分佈式計算系統開發內存模型)機器中的內存管理:globalmemorystatusex獲取各節點總內存。getnumahighestnodenumber-獲取系統開發中總的節點個數。getnumaavailablememorynode-獲取某節點的內存。getnumanodeprocessormask-獲取某節點的cpu掩碼。getnumaprocessornode-判斷某cpu位於的哪一個節點。

7.         virtualquery能夠查詢某地址所在的內存塊(內存塊是具備相同狀態、保護屬性和類型的連續頁面),也提供了一些信息指出該內存塊在reserve的時候其virtualalloc起始地址和保護屬性等。

8.         memory_basic_information(virtualquery)各字段解釋:baseaddress-內存塊起始地址。regionsize-內存塊長度(jeffrey把reserve的叫區域把這兒的叫內存塊,而windows只把這兒叫region,我姑且同前者的概念)。state-塊狀態,能夠是free、commited、reserved。protect-保護屬性,狀態是commited時有效。type-類型,能夠是private(私有內存,虛擬存儲器在頁交換文件)、image(在寫拷貝以前,其虛擬存儲器就是映像文件(exe、dll),寫拷貝(修改dll代碼或全局變量等)以後虛擬存儲器轉移到page file)、mapped(相似image,寫拷貝以前虛擬存儲器是內存映射文件),狀態是commited時有效。allocationbase、allocationprotect-reserve時候的基地址和保護屬性,狀態非free的時候字段有效。

9.         windows進程內存佈局:分紅不少內存塊,其中部份內存塊屬於同一個區域(reserve的region)。若是要實現內存搜索的功能,能夠用virtualquery遍歷各塊,在commited的塊中搜索。

10.     線程棧的內存塊具備page_guard保護屬性。

11.     一個進程內存使用的統計分析:輸出見最後(單位均爲kb)。第一部分是用virtualquery遍歷各塊進行統計,可見進程commit了5.8m內存到虛擬存儲器,其中4.5m是映像文件(部分在pagefile中),1.1m是內存映射文件(部分在pagefile中),159k是私有內存(所有在pagefile中)。第二部分使用getprocessmemoryinfo,可見進程佔用主存(物理存儲器)1.6m,虛擬存儲器中有425k在pagefile中(包括第一部分中所有的private和部分的image、mapped),也說明進程使用的5.8m內存中有5.4m是共享的(5.8-425)。第三部分用globalmemorystatus看出該進程可用虛擬內存爲2g。

virtualquery :

commitedbytes = 5865.47

readallowedbytes = 5865.47

imagebytes = 4517.89

mappedbytes = 1187.84

privatebytes = 159.744

 

getprocessmemoryinfo :

workingsetsize = 1658.88

pagefileusage = 425.984

privateusage = 425.984

 

globalmemorystatus :

dwavailvirtual = 2.13826e+006

第15章  在應用開發程序中使用虛擬內存

1.         用virtualalloc來reserve區域的時候:pvaddress爲空表示由系統開發分配區域起始地址,同時使用mem_top_down標誌,提示系統開發優先選擇高地址,適用於長時間佔用的內存。自定義起始地址的時候,實際reserve到的區域會包含自定義的範圍(自定義的起始地址+長度),即返回的地址可能比自定義起始地址小,同時保證該區域起始地址與系統開發的分配粒度對齊,長度與分頁大小對齊。若是找不到這樣長的閒置連續空間,返回null。reserve和commit的保護屬性相同,性能更好。

2.         用virtualalloc來commit內存塊的時候:實際提交的塊會包含自定義範圍,而且起始地址和長度都與頁面大小對齊。提交的塊不該該跨兩個區域。

3.         virtualallocexnuma,適用於numa機器。

4.         在visita以上的系統開發中,能夠分配大頁面,大頁面是常駐內存的須要有鎖定頁面的權限(lock pages in memory),同時要求在virtualalloc時知足三個條件:(1)大小必須與getlargepageminimum對齊(天然該函數必須返回非0)。(2)同時reserve和commit。(3)保護屬性必須是page_readwr ite。

5.         在須要使用有空洞的大段連續內存的時候,有一個技巧:reserve一大段,根據須要commit。由於只有commit了才佔用虛擬存儲器,所以很節省內存。

6.         virtualfree能夠反提交和釋放內存,其中mem_release時的長度參數必須爲0,表示釋放整個區域。

7.         virtualprotect改變保護屬性,注意一次調用不要跨多個區域。

8.         virtualalloc的mem_reset標誌,表示願意暫時放棄一段內存的當前內容,若是系統開發的物理內存使用緊張,reset的這段內存對應的物理內存可能會被挪用,直到再次訪問這段內存。

9.         即便經過virtualalloc來commit了,只要沒有訪問過這段地址,系統開發也不會分配內存。即若是commit的1.5g內存不讀寫,開銷很小。

10.     地址窗口擴展(awe,address windowing extension):能夠指定一段地址直接映射到物理內存,具備常駐內存和增長可用內存量的優勢。以mem_physic調用virtualalloc來指定要用於映射的虛擬地址段,而後allocateuserphysicalpages分配物理頁面,再mapuserphysicalpages將虛擬地址段和分配的物理頁面關聯,以後隨意讀寫,使用完畢後以null做爲參數調用mapuserphysicalpages解除關聯,最後freeuserphysicalpages、virtualfree釋放物理頁面和地址段。一段虛擬地址能夠經過map和unmap輪流訪問多段物理內存,明顯增長了進程可訪問的內存總量。awe也要求用戶有鎖定頁面的權限。

第16章  線程棧

1.         連接選項「/statck:reserve[,commit]」能夠在pe文件中記錄默認的線程棧保留大小和提交大小,實際棧大小還要結合_beginthreadex時的參數。

2.         page_guard屬性的做用:第一次訪問具備該屬性的頁面,會觸發一個status_guard_page_violation異常,同時該屬性被自動抹除,因而後續的訪問正常。即該屬性用於首次訪問的通知。

3.         默認條件下,線程棧建立時先reserve一塊1mb的內存,棧底的兩塊頁面被提交,其中較低地址的那塊頁面具備page_guard屬性,被稱爲保護頁面(guard page)。當棧的調用層次變深須要更多內存時,系統開發去掉當前保護頁面的page_guard屬性並提交下一個頁面做爲保護頁面(實現方式見條款4)。這個開發過程 進行下去,棧頂所在的提交頁面以後始終有一塊被提交的保護頁面,直到棧的調用層次足夠深,當倒數第二個頁面被提交併須要標記爲保護頁面的時候,這個標記行爲終止並拋出exception_stack_overflow異常。棧最低地址的一個頁面始終處於reserve狀態,用來隔離棧和棧下方的內存空間,避免非法的棧操做訪問越界。捕獲了棧溢出結構化異常的線程因爲沒有了保護頁面,須要調用_resetstkoflw來從新標記保護頁,不然下次調用層次太深的時候會由於沒有保護頁不觸發棧溢出異常直接訪問到最低地址的reserve頁,形成非法訪問錯誤。

4.         棧上reserve頁被從高到底依次commit的方式:當位於棧頂的函數幀在保護頁面中時,訪問保護頁內存會觸發異常,系統開發捕獲異常,提交下一頁,並判斷下一頁是不是倒數第二頁,是的話拋出棧溢出異常,不然將一下頁標記爲保護頁。若是棧頂函數幀很大(好比包含大數組),跨越多個分頁,因爲函數內部可能先訪問函數幀中最低地址的reserve頁的內存,引發非法訪問錯誤,因而c++編譯器對這種棧幀大於1個分頁的函數進行了特殊處理:編譯器會在大棧幀函數的開始插入_chkstk,後者會沿大棧幀的底部向頂部依次訪問每一個分頁,連續推進保護頁,保證後來函數體中的隨機訪問都做用在commit分頁上。

5.         debug版本程序在調用函數前,會備份當前棧的上下文,在函數返回後對比新的棧數據和備份數據,判斷是否有棧上的越界錯誤。release版本程序開啓/gs開關後能起到相似的效果。

第17章  內存映射文件

1.         內存映射文件的主要應用開發場合:(1)映射到映像文件(exe、dll),加速進程啓動。(2)映射到數據文件,代替標準的文件io。(3)共享內存。

2.         當dll被loadlibray時若是發現預約基地址已經被佔用時,可能會加載失敗(構建dll時指定了/fixed連接選項),至少也會重定位,後者會佔用額外存儲空間和增長dll載入時間。

3.         段的大小都按頁大小對齊。

4.         使用dumpbin.exe /headers能夠查看pe文件的各類段。常見段:.bss-未經初始化的全局變量等數據。.crt-只讀的c運行時數據。.data-已初始化的全局變量。.debug-調試信息。.didata-延遲導入名字表(delay imported names table)。.idata-導入名字表。.edata-導出名字表。.rdata-只讀的運行時數據。.reloc-重定位表信息。.rsrc-資源。.text-代碼段。.textbss-啓用增量連接(incremental linking)時c++編譯器生成。.tls-線程本地存儲。.xdata-異常處理表。

5.         默認狀況下.data段的頁面具備寫拷貝屬性,所以pe文件的一個實例修改全局變量並不會影響其餘進程實例。

6.         使用#pragma data_seg(「mydataseg1」); #pragma data_seg();能夠聲明一個新的數據段,其中初始化的變量會自動加入該段。沒有初始化的變量能夠經過__declspec(allocate(「mydataseg1」)) int g_i;來加入數據段。用#pragma comment(linker, 「/section:mydataseg1, rws」)來爲段指定屬性,」s」表示shared,它經過去掉段頁面的寫拷貝保護屬性,來達到多進程共享的效果。

7.         createfilemapping:參數fdwprotect的page_readonly、page_writecopy等很容易理解,另外還有幾種屬性:sec_commit-默認值。sec_image-表示該文件是映像文件,該文件被映射到內存時,系統開發會對其中不一樣的段添加對應的保護屬性。sec_nocache-無cache,驅動開發 開發開發人員用。sec_large_pages-大頁面支持,相似virtualalloc那邊。sec_reserve-經過這個標記映射的內存沒有是沒有被提交的,直到再調用virtualalloc來commit才能訪問這些頁面。參數dwmaximumsizehigh、dwmaximumsizelow表示要求的最大文件大小,尤爲在共享內存對應的虛擬存儲器在頁交換文件中時特別有意義(hfile參數爲invalid_handle_value的狀況),若是映射的可寫磁盤文件自己的大小沒有達到這個值,文件也會被自動擴大。若是最大大小爲0,表示使用磁盤文件自己大小。

8.         mapviewoffile:建立映射對象的一個視圖,多個視圖之間的數據是嚴格同步的,由於同一個映射對象的多個視圖儘管虛擬地址段不一樣,但都映射到同一個虛擬存儲器上。該函數返回後,內存已經被commit(除非createfilemapping時指定sec_reserve參數)。參數dwfileoffsethigh、dwfileoffsetlow、dwnumberofbytestomap共同決定要把文件的哪部分映射到內存,offset必須與分配粒度對齊,size爲0的時候表示範圍從offset直到文件尾。對返回的地址virtualquery會獲得map的區域。

9.         unmapviewoffile:釋放映射的內存區域。

10.     flushviewoffile:將緩存中已修改的數據flush到文件中,若是沒修改被直接丟棄。注意若是映射頁面具備寫保護屬性,緩衝中的數據最多被flush到page file中。若是是映射到遠程文件,該函數只保證數據被flush到網上,而遠程的文件不必定會被修改,除非createfile時指定了file_flag_write_through。

11.     注意,雖然createfilemapping會增長文件對象計數,mapviewoffile會增長映射對象的計數(也就是說,在unmapviweoffile以前這兩個內核對象就能夠被closehandle了),可是若是太早關閉映射對象,其餘地方要打開映射對象時會失敗(即openfilemapping失敗或者createfilemapping的lasterror不是error_already_exists),也就是說,內核經過視圖對映射對象的引用,不能被用戶模式代碼檢測到,所以最好仍是按傳統順序先unmapviewoffile再closehandle。

12.     numa支持:createfilemappingnuma、mapviewoffileexnuma。

13.     打開同一個磁盤文件的多個文件內核對象,因爲各自擁有獨立緩衝區,所以文件內容在不一樣對象間不保證明時同步。

14.     映射到同一文件的多個映射對象的視圖不保證數據的實時同步。

15.     mapviewoffileex:參數pvbaseaddress非空的時候能夠指定映射內存的起始地址。系統開發映射exe和dll的時候就這麼幹的。

16.     各類跨進程通信手段的通信雙方都位於本機時,這些通信方式最終都實現爲內存映射文件。

17.     要映射到磁盤文件時,必定要判斷createfile的返回值,由於若是打開文件失敗,invalid_handle_value句柄會讓createfilemapping建立映射到pagefile的對象,沒有報錯倒是歧義。

18.     對應virtualalloc那「reserve一大段內存再小塊commit」的用法,內存映射文件中實現以下:以sec_reserve爲參數createfilemapping,以後mapviewoffile獲得reserve的區域,最後確保訪問前要先用virtualalloc來commit。注意這樣commit的共享內存不能virtualfree。

第18章  堆

1.         堆適合分配小內存塊,不須要按分配粒度或者頁大小對齊。堆在最初只是預約了一塊區域,在客戶分配時將預約的區域提交,在客戶釋放後可能反提交。

2.         關於默認堆:getprocessheap返回,用戶模式代碼沒法銷燬它,在進程結束後由系統開發銷燬。進程能夠經過連接選項「/heap:reserve[,commit]」來設置默認堆大小。由於默認堆屬於進程,因此在dll中不該設置該連接選項。windows的ansi版api向unicode版轉化的時候從默認堆分配字符串緩存,localalloc、globalalloc也從默認堆分配內存。默認堆對外界訪問進行了同步,即沒有使用heap_no_serialize標記。

3.         使用獨立堆的一些好處:(1)寫堆內存出錯後,不會影響其餘堆的數據。(2)對特定類型數據使用獨立堆的話,因爲分配塊大小相同,具備速度快、無碎片的優勢。(3)相關數據使用獨立的堆,在訪問這些數據時訪問的頁面更集中,減小pagefault。(4)對特定線程上的邏輯結構使用獨立堆,沒必要加鎖,提高性能開發

4.         heapcreate:參數fdwoption,若是在建立堆的時候指定了部分標誌(如heap_no_serialize標誌等),之後每次訪問堆這些標誌都生效;若是建立的時候沒有指定,那後續的每次訪問能夠單獨指定標誌。 heap_no_serialize-訪問堆的時候不加鎖。heap_generate_exceptions-分配內存失敗的時候拋出異常,默認行爲是返回null。heap_create_enable_execute-能夠在堆內存上放置代碼來執行。參數dwinitalsize-初始堆大小。參數dwmaximumsize-若是非0,表示若是堆內存使用量達到這個值後再分配會失敗;爲0,表示堆會自動增大,直到內存用盡。

5.         heapalloc、heapsize、heapfree、heapdestroy,容易理解。

6.         heaprealloc:heap_zero_memory-增大內存時,增長的字節初始化爲0。heap_realloc_in_place_only-要求不移動開發其餘 起始地址的狀況下改變大小,須要增大時若是當前位置剩餘空間不足會返回null。

7.         heapsetinformation:標記heapenableterminationoncorruption-visita以上使用。默認狀況下,堆內存被破壞後只在調試器中觸發一個斷言而後繼續執行,這個標記容許發現堆破壞就拋出異常。該標記影響進程中全部堆,沒法清空標記。標記heapcompatibilityinformation-值爲2的時候,表示啓用低碎片堆(lowfragmentation heap)算法,啓用該算法的堆針對內存碎片問題優化有更好的性能。

8.         heap32listfirst、heap32listnext-遍歷快照(createtoolhelp32snapshot)中的堆。heap32first、heap32next-遍歷指定堆中的塊。getprocessheaps-得到包括默認堆在內的全部堆句柄。heapvalidate-檢查指定堆中全部塊或者單個塊的有效性。heapcompact-將堆中閒置塊合併,並反提交。heaplock、heapunlock-鎖定堆。heapwalk-遍歷指定堆中的塊,建議先鎖堆。

第19章  dll基礎

1.         kernel32.dll-管理內存、線程、進程。user32.dll-窗口和消息。gdi32.dll-繪製圖像文字。comdlg32.dll-經常使用對話框。comctl32.dll-經常使用控件。

2.         dll函數分配的內存應該由dll本身提供的函數釋放:主要是針對經過c/c++函數(malloc、new)分配的內存,由於當dll和dll的使用者都在引用靜態庫版本的crt時(或有一方在引用靜態庫crt),多個靜態庫版crt中有多份crt堆的管理數據(全局變量),若是從一個管理器分配資源交給另外一個管理器釋放,顯然會錯誤。所以,若是全部模塊都使用dll版crt就不會有錯(由於只有一份全局crt堆管理數據),或者改用heapalloc(getprocessheap(),…)也不會錯(顯然dll中和exe中訪問到的默認堆是同一個),固然最佳作法仍是dll同時提供匹配的釋放函數。

3.         .lib文件中只包含函數、變量和類型的符號名。因爲模塊中只包含要引用的模塊名而沒有路徑,因此主模塊被載入後須要按必定的搜索順序搜索被引用模塊再載入,同時這也意味着修改.lib中的符號名,搜索dll時也會搜索新名稱。

4.         dll的導出段中按符號名順序列出了導出項,每一項包括符號名和rva(relative virtual address,用於指出該符號在dll模塊中相對於模塊基址的地址)。模塊能夠包含多個導入段,每一個導入段指出該段要依賴的dll名以及須要的符號,導入符號對應的實際地址在dll被載入後填充,其值爲dll基址+rva。

5.         在爲dll的導出函數指定名稱的時候,最好使用.def文件,其次能夠選擇連接選項#pragma comment(linker, 「/export:myfunc=_myfunc@」)。

6.         dumpbin.exe的/exports可以查看導出段,/imports可以查看導入段。

7.         關於msvc編譯器對符號更名的策略:c語言下默認不改變函數名,所以c++下使用了extern 「c」的__cdecl也不會更名。

第20章  dll高級技術

1.         加載一個dll,系統開發至少會幹幾件事:(1)將不一樣段的分頁分別映射並賦予不一樣的保護屬性。(2)檢查dll依賴的其餘dll依次加載。(3)執行dllmain。

2.         loadlibraryex:dwflags參數-don’t_resolve_dll_references-將dll映射到內存後,對於條款1中的三件事,只作按段分配保護屬性這件。load_library_as_datafile-比起上個標誌,連三件事中僅剩的一件也省了,只是映射文件,用作數據文件。能夠加載exe而後讀取其中的資源。load_library_as_datafile_exclusive-以獨佔方式映射數據文件。load_library_image_source-在as_datafile的基礎上,將導出段的全部rva轉換成va。load_with_altered_search_path-能夠調整dll路徑的搜索方式。load_ignore_code_authz_level-安全相關,該安全方案被後來的uac取代。

3.         setdlldirectory:設置加載dll時的搜索路徑,dll在搜索進程的當前路徑事後就會搜索這裏。當路徑爲空串(」」\0」)的時候,表示搜索的時候跳過當前路徑,當路徑爲null的時候恢復默認搜索方式。

4.         freelibraryandexitthread適用於一個場合:要調用freelibrary的代碼正是位於dll中。

5.         loadlibrary和loadlibraryex返回的地址不等價,不能混用。如先以load_library_as_data_file作參數調用loadlibraryex,再用loadlibrary加載同一個dll,返回值是不一樣的。

6.         getprocaddress。

7.         名爲dllmain的函數不存在的時候,系統開發會使用默認入口。

8.         dllmain的fdwr eason參數:dll_process_attach-dll第一次被加載的時候傳入,對於隱式加載的dll是主線程執行,而顯式加載的dll由loadlibrary線程執行,用於執行dll初始化操做。返回false,程序會報錯表示加載dll失敗。dll_process_detach-隱式卸載的時候由主線程執行,顯示卸載的時候由freelibrary線程執行,負責清理資源。dll_thread_attach-線程在建立時,會檢查進程已經加載的dll,而後依次通知每一個dll的dllmain函數。進程啓動時會先建立主線程,再加載各個dll,所以這時主線程調用dllmain只會傳入dll_process_attach而不是dll_thread_attach。dll_thread_detach-線程退出的時候檢測全部已經加載的dll依次調用dllmain。

9.         disablethreadlibrarycalls:聲明線程在建立和退出的時候不用通知指定dll的dllmain函數。

10.     全部dll的dllmain的調用被加載鎖(loader lock,進程惟一的)序列化了。避免同時建立多個線程以dll_thread_attach調用dllmain時產生競爭。

11.     對於c++編寫的dll,實質上系統開發通知的是__dllmaincrtstartup,當fdwr eason是dll_process_attach和dll_process_detach時,它會調用全局變量的構造或析構函數,而後再調用dllmain。

12.     延遲載入是指直到使用dll導出的函數時系統開發才加載dll和查找函數。優勢:加速進程啓動、讓爲高版本系統開發設計的程序在低版本系統開發中也能使用部分功能、特殊的設計用途等。部分dll不能延遲加載:導出了數據的dll(由於延遲加載利用的是getprocaddress等功能)、kernal32.dll。另外在dllmain中也不應使用延遲載入的dll函數。

13.     延遲加載的使用:在linker-input-delay loaded dlls中指定要延遲載入的dll。若是要hook延遲加載開發過程 以及停用延遲加載的dll,須要再導入delayimp庫和開啓linker-advanced-delay loaded dll的support unload。

14.     延遲加載的細節:模塊引用的dll要延遲加載的話,會刪除該dll的idata段,改成包含didata段,對延遲加載函數的調用會跳轉到__delayloadhelper2函數中,該函數會確保該dll已經被加載,而後檢查didata中對應函數的表項是否非空,爲空的話用getprocaddress查找並填充didata項,下次使用就不用再查找。用__funloaddelayloadeddll2卸載延遲加載dll,以便以後再次使用延遲函數可以保證正常,該函數會清空didata中已經填充的各項。__pfndlinotifyhook二、__pfndilfailurehook2是延遲加載開發過程 的hook函數指針。

15.     函數轉發器:#pragma comment(linker, 「/export:somefunc=dlla.someotherfunc」)。

16.     hkey_local_machine\system\currentcontrolset\control\session manager\knowndlls包括一些影響loadlibrary路徑查找的信息。

17.     關於模塊基地址重定位:dll中的代碼訪問dll中的全局變量時用的是絕對地址,同時會增長一個reloc段(重定位段)記錄全部引用絕對地址的代碼,若是dll最終加載的位置不是默認基址,以前使用的絕對地址須要根據reloc的記錄被修正,這就是重定位開發過程 。可見若是進程加載的時候,多個dll基址發生衝突,須要被重定位,修復絕對地址的操做增長了加載時間,同時也會由於修改image內存頁形成寫拷貝,增長了系統開發的虛擬內存佔用。最理想的狀況下,全部使用dll的進程都不須要重定位,這就須要安排合理的基址,能夠使用rebase.exe開發工具或者rebaseimage函數。使用dumpbin.exe /headers命令能夠查看包括基址的信息。使用/fixed開關刪除reloc段,禁用重定位。

18.     關於模塊的綁定:默認狀況下,模塊的引入段,會在進程加載模塊後被填入導入函數的絕對地址,所以包含引入段的模塊會發生內存頁的寫拷貝。使用bind.exe開發工具,能夠在映像文件中的idata段填入絕對地址和對應dll的時間戳,當進程加載時,發現被依賴dll沒有被重定位(即基址和默認基址相同)且時間戳和綁定的dll相同,那麼idata段就能夠不用修改直接使用綁定值,避免了寫拷貝。能夠使用bind.exe開發工具或bindimageex函數來綁定模塊。綁定操做應該在軟件開發每次升級後執行。

19.     其餘綜合 討論重定位和綁定:一個dll中,引入段最終包含所依賴的dll的函數地址,若是所依賴的dll沒有被重定位,那引入段不用被修改避免了寫拷貝;dll內部的全局變量是用絕對地址訪問,若是dll自己沒有被重定位,這些絕對地址不用被修改也避免了寫拷貝。所以用rebase.exe開發工具合理安排全部dll的基址,而後在用bind.exe開發工具寫入導入函數地址,能提高性能和減小內存佔用。

第21章  線程局部存儲區

1.         動態tls:每一個線程都有一個內部dword 數組用於存放用戶數據,ms保證數組至少有tls_minimum_available(64)個元素。用tlsalloc申請一個空閒索引,調用tlssetvalue、tlsgetvalue時傳入這個索引能夠訪問每一個線程上的用戶數組,用tlsfree釋放索引,windows會保證被釋放的索引在各個線程上的數據都被清零。dll中使用動態tls的標準方式:dll_process_attach的時候tlsalloc一個索引;在dll_process_detach的時候用tlsfree釋放;在dll功能函數內部檢測tlsgetvalue返回的指針是否爲空,爲空的話分配一塊內存包含dll要使用的全部線程相關數據;在dll_thread_detach中檢測tlsgetvalue返回值非空則釋放掉。

2.         靜態tls:聲明爲__declspec(thread)的靜態變量會保存在模塊的tls段中, 每一個線程在建立的時候會根據當前全部模塊的tls段總大小分配一塊內存與線程對象關聯,這塊線程相關內存的大小也會隨loadlibrary、freelibrary增刪包含tls段的dll進行調整。靜態tls只在vista以上才被完美實現。考慮這樣一種實現:每一個模塊都有一個動態tls索引(__tls_index),每一個線程的該索引下保存的是malloc出來的特定模塊的tls段數據,能夠認爲系統開發是經過1節中描述的慣用法實現靜態tls的。

第22章  dll注入和api攔截

1.         利用註冊表注入dll:hkey_local_machine\software\microsoft\windows nt\currentversion\windows\下的appinit_dlls能夠填一系列要加載的dll,僅當loadappinit_dlls爲1的時候。工做原理是:任何gui程序在加載user32.dll時,它的dllmain會先嚐試加載註冊表項appinit_dlls中的dll。所以這種開發方法 會影響全部的gui程序。

2.         利用鉤子來注入dll:以參數wh_getmessage來調用setwindowshookex,線程id爲0的時候會注入系統開發中全部有消息循環的進程。

3.         利用遠程線程注入dll:用createremotethread在目標進程中建立一個線程,線程函數就是loadlibrary,線程函數的參數是要注入的dll名。具體步驟:(1)用virtualallocex在目標進程中分配內存,用writeprocessmemory寫入要注入dll名的字符串,這樣就在目標進程地址空間中準備好了參數(2)經過getprocaddress獲得loadlibrary的地址。能夠利用一般全部進程中kernel32.dll的基地址相同這個實時,把本進程的loadlibrary地址當作目標進程中該函數的地址。(3)調用createremotethread,線程函數是loadlibrary。(4)waitforsingleobject來等待遠程線程結束,表示加載完畢,而後virtualfreeex釋放裝有模塊名的地址。(5)最後以任何方式在目標進程中釋放注入的dll。好比以 freelibrary爲線程函數調用createremotethread。

4.         利用轉發器替換dll:要用本身的a.dll替換合法的b.dll,先用轉發器在a.dll中轉發全部的函數到b.dll,再實現本身的功能,最後將a.dll更名爲b.dll,本來的b.dll改爲其餘名。也能夠在a.dll中轉發後,修改依賴b.dll的模塊的引入表,將它依賴的dll名改成a.dll,這種方式避免了更名。

5.         利用createprocess注入dll:父進程用createprocess建立子進程時暫停子進程的主線程,而後查詢子進程的入口函數(main),將入口函數的頭幾個字節改成跳到注入代碼,而注入代碼的末尾會跳轉回入口函數開頭。

6.         api鉤子的兩種實現方式:(1)將原函數入口處的代碼改成「跳轉到掛鉤函數;原代碼b」,這樣原函數的調用都會跳轉到掛鉤函數;爲了可以訪問原函數的功能,另外準備一個可執行緩衝區,內容爲「原代碼a;跳轉到原代碼b」。(2)修改進程中全部模塊的引入表,將全部引入表中指定dll的指定函數項地址改成掛鉤函數地址。注意進程調用loadlibrary後會引入新的dll及其依賴dll,所以須要再遍歷一次全部dll修改引入表。有必要的話也掛鉤getprocaddress返回僞造地址。用到的函數有imagedirectoryentrytodata,能夠查詢指定dll的引入表地址。

第23章  終止處理程序

1.         __finally塊可能因爲三種緣由被執行:(1)正常執行完__try塊。(2)控制流中斷__try塊(這種情形叫局部展開,local unwind)。包括continue、break、return、goto、longjump等。(3)發生異常中斷__try塊,系統開發正在進行全局展開。包括seh中的其餘硬件 異常(除0、寫非法內存)和軟件開發異常(raiseexception)。在__finally塊中要區分是(1)或者(2)(3),能夠用abnormaltermination,該函數返回true表示是(2)或(3)。(1)沒有額外開銷;(2)的開銷較大,建議用__leave替代;(3)是正經常使用途。

2.         當 __finally塊是因爲__try塊中的return被執行時,若是再在__finally塊中調用return,最終函數會返回後一個return的值。

3.         __finally中的return能夠中斷全局棧展開(global unwind)。即線程不會從__except的處理代碼塊繼續執行(儘管exception filter返回的是exception_execute_handler),而是從return的__finally的上一級函數繼續執行。

4.         不建議在__try或__finally塊中使用return、goto等控制流語句。

第24章  異常處理程序與軟件開發異常

1.         __try、__except組合主要有filter塊(__except後括號中的語句)和handler塊(__except後的{}塊),它們只在發生結構化異常的時候有可能被執行。發生結構化異常後,系統開發首先將異常信息(getexceptioninformation返回值)壓入棧頂,而後調用veh註冊函數(見25章),再執行最近的filter塊,若是filter塊返回exception_continue_search,則繼續查找下一個filter,這個開發過程 中包括異常發生點和異常信息的整個棧一直無缺(即getexceptioninformation返回值有效),直到某個filter返回非exception_continue_search。若是filter塊返回exception_continue_execution則從異常點繼續執行,若是返回exception_execute_handler則先進行全局展開再執行handler塊。全局展開會形成異常信息失效和從內到外的__finally塊逐個執行,即handler中getexceptioninformation返回值會失效,且若是某個__finally經過return中斷全局展開,handler塊將不執行。若是用戶編寫的全部__except都返回exception_continue_search,最終系統開發將執行ms編寫在系統開發線程函數中的最頂層__except的filter塊,即unhandledexceptionfilter,這個過濾函數25章講。

2.         關於exception_continue_execution:對於其餘硬件 異常(除0、內存非法訪問等cpu異常),會從觸發異常的那句彙編語句開始執行。對於軟件開發異常,會從raiseexception的下一句彙編開始執行。即filter返回該值可能讓其餘硬件 異常循環觸發,而軟件開發異常只會觸發一次。

3.         getexceptioncode只能出如今__except後的filter塊或者handler塊中,而不能出如今filter函數中,這由編譯器保證。getexceptioninformation一樣不能出如今filter函數中,但也不能出如今handler塊中,根據條款1中的描述,當系統開發執行handler塊時,全局展開已經結束,異常觸發點到handler點之間的棧幀已經失效,固然異常信息也已經失效。

第25章  未處理異常、向量化異常處理與c++異常

1.         c++異常機制是由seh實現的,c++的全部異常都是以exception_noncontinuable爲參數調用raiseexception拋出的軟件開發結構化異常。因爲exception_noncontinuable只限制__except的filter塊,因此veh函數返回exception_continue_execution來忽略c++異常是合法的。

2.         不管是c/c++線程(_beginthreadex)仍是windows線程(createthread),它們的內部線程函數都將用戶線程函數放在一組__try、__except中,當異常發生後全部的用戶filter都返回exception_continue_search時,系統開發將執行最外層的filter即unhandledexceptionfilter(一個系統開發api),若是該函數發現當前進程正在被調試則將控制權交給調試器然後者會中斷進程;非調試狀態下它會嘗試取出用戶經過setunhandledexceptionfilter註冊的頂層過濾函數,若是用戶頂層函數返回exception_execute_handler或exception_continue_execution,則unhandledexceptionfilter再也不進一步處理。顯然用戶能夠經過返回前者來記錄日誌並沒有聲退出,而返回後者能夠實現相似棧內存經過guard page自動commit的功能。若是用戶頂層過濾函數也返回exception_continue_search,則進程再嘗試調用經過addvectoredcontinuehandler註冊的veh函數,全部veh函數都返回exception_continue_search的話,系統開發就建立一個子進程並等待,子進程顯示對話框詢問用戶要結束進程仍是附加調試器,等待結束後異常的進程要麼退出要麼已經被調試器附加。

3.         seh實現的理解:有一個叫seh棧的容器被用來維護相關數據,棧的每一項多是一個__try/__except或__try/__fianlly組合;線程進入一個__try塊就往seh棧中壓項,退出__try塊就從seh棧彈出一項。若是發生異常,系統開發判斷離棧頂最近的__except項的filter返回值,若是其返回值爲exception_continue_search,則系統開發繼續從棧頂往棧底查找__except項並執行其filter。若是找到一個filter返回exception_continue_execution則流程結束,同時seh棧保持不變;若是某個filter返回exception_execute_handler,則系統開發將seh棧棧頂到該__except項的每一項都出棧,彈出的開發過程 中若是發現__finally項則執行其中的代碼塊。

4.         veh(vectored exception handler,向量化異常處理),做爲seh的補充,能夠經過addvectoredexceptionhandler、removevectoredexceptionhandler管理一組異常過濾函數,這組函數將在異常發生以後到用戶filter被執行以前的這段時間被調用,它能夠返回exception_continue_search讓系統開發執行下一個veh函數或者用戶filter;也能夠返回exception_continue_execution起到忽略異常的效果。這組回調的特殊調用時機能夠用於實現異常hook等。另外還能夠經過addvectoredcontinuehandler、removevectoredcontinuehandler管理一組過濾函數,由unhandledexceptionfilter在用戶頂層過濾函數(setunhandledexceptionfilter)以後調用。

5.         兩種狀況下調試器會通知用戶發生異常:(1)打開ide相應開關後,一拋出異常就觸發斷點。另外,不管是否打開開關,調試器都在輸出窗口打印異常相關信息。調試器顯然經過addvectoredexceptionhandler註冊了veh函數。能夠模仿調試器來記錄異常。(2)對於用戶沒有處理的異常,被調試狀態下的unhandledexceptionfilter內部會通知調試器。

6.         對異常發生時調試器彈出框的解釋:(1)中斷。保持中斷的狀態,便於調試。(2)繼續。若是對話框在veh函數中彈出,即異常剛拋出,這個選項會讓veh函數返回exception_continue_search,繼續查找下個處理函數。若是對話框是在unhandledexceptionfilter中彈出,繼續選項等價於忽略。(3)忽略。veh過濾函數或unhandledexceptionfilter中代碼返回exception_continue_execution,所以該選項用於忽略包括c++異常在內的軟件開發異常(raiseexception)。

第26章  錯誤報告與應用開發程序恢復

1.         本章介紹的wer(windows error reporting,windows錯誤報告)內容主要在vista以上可用。

2.         %systemroot%\system32\wercon.exe能夠顯示系統開發中出現過的錯誤。

3.         wersetflags能夠影響wer的行爲,好比要求不dump堆、發送報告到ms網站等。weraddexcludedapplication能夠指定一些程序崩潰後跳過wer機制,適合正在調試的程序等。

4.         webregis地理信息系統 termemroyblock-指定wer的dump數據中要包括指定位置的內存。werregis地理信息系統 terfile-要求將指定文件加入報告中。

5.         定製wer報告:werreportcreate、werreportsetparameter、werreportadddump、。werreportaddfile、werreportsetuioption、werreportsubmit、werreportclosehandle。

6.         regis地理信息系統 terapplicationrest art-能夠指定在何種錯誤狀況下wer以特定參數重啓程序。

7.         regis地理信息系統 terapplicationrecoverycallback-註冊一個回調,進程將要非正常結束的時候被調用,以便用戶自由備份一些狀態等。用戶能夠在回調中以applicationrecoveryinprogress、applicationrecoveryfinished來通知ui進度。

附錄

1.         要在輸出窗口中打印調試信息,區別#pragma message和outputdebugstring,前者是在編譯期打印,後者是運行時打印。

2.         因爲ms提供的函數debugbreak會斷點在kernel32.dll中,須要兩次才能單步到下一行(第一次跳出kernel32.dll);而__asm int 3;是斷點在用戶代碼中,更容易使用。二者都只適合調試器存在時,非調試狀態這個斷點異常沒法捕獲會崩掉程序。

3.         本身編寫發佈版本也有效的斷言:verify。

4.         #pragma comment(linker, "/manifestdependency:\"type='win32' name='microsoft.windows.common-controls'…」) 使gui程序可以自動查找正確版本的comctrl32.dll來自繪,達到自適應系統開發主題(theme)的效果。

5.         windowsx.h中包含一些簡單函數便於操做窗口,分別是消息處理宏、子控件宏和api宏。

第26章  窗口消息(第4版)

1.         一個進程能夠建立上萬個用戶對象(user object)。內核對象屬於內核,能夠跨進程使用,不會隨任何進程自動刪除;圖符、光標、窗口類、菜單、加速鍵表等用戶對象屬於進程,容許跨線程訪問,進程結束後自動刪除;窗口、掛鉤兩種用戶對象屬於線程,擁有者線程結束後自動刪除。

2.         線程的內部數據結構threadinfo中至少包括如下內容:post來的消息隊列、send來的消息隊列、send的應答隊列、exitcode、激活標誌、消息隊列狀態標誌(queuestatus)、虛擬輸入隊列(viq)、局部輸入狀態(鼠標/鍵盤焦點窗口、光標外形和可見性等)。

3.         postmessage、postthreadmessage、postquitmessage、getwindowthreadprocessid。

4.         sendmessage發送消息時,若是目標窗口位於發送線程,則sendmessage內部直接調用窗口開發過程 並返回,若是目標線程不是當前線程甚至位於其餘進程,sendmessage往目標線程的send消息隊列內添加項事後(並設置qs_sendmessage),用msgwaitformultipleobjects等待處理完成通知(同時還處理本線程消息)。而sendmessagetimeout則包含等待send處理完畢、處理本線程消息、檢測超時三項功能,傳入smto_block參數後只進行有超時的等待而不處理消息。

5.         sendmessagecallback的目標線程是當前線程時,直接調用窗口開發過程 並用回調通知;若是目標線程是其餘線程,send消息後直接返回,以後本線程應該用getmessage來響應其餘線程post回來的send處理完畢的通知,該通知的處理函數會調用註冊的回調。sendnotifymessage至關於回調爲空的sendmessagecallback,它不關心完成通知,相比postmessage它仍是具備send消息的一些特色:比post消息優先處理、目標線程是當前線程時直接調用窗口開發過程 。一種獲取全部窗口句柄的開發方法 :以hwnd_broadcast 爲參數調用sendmessagecallback,而後getmessage、dispatchmessage處理回調,在回調中搜集全部的窗口句柄。

6.         sendmessage會在目標線程不是當前線程時阻塞等待,爲避免沒必要要的阻塞發送線程,消息處理函數一旦肯定處理結果就能夠立刻調用replaymessage傳入結果值來激活send線程,處理函數後半段即便進行費時操做也再也不干擾send線程(處理函數的返回值也被忽略)。

7.         insendmessage能夠在消息處理開發過程 中判斷當前線程是不是send線程(線程不一樣返回true)。insendmessageex還能夠判斷拋出消息的具體函數,以及當前是否已經reply告終果。

8.         getqueuestatus,檢測當前線程的消息隊列狀態,是否有post消息、send消息、虛擬輸入、以及qs_quit、qs_timer等特殊標誌。

9.         translatemessage在遇到wm_keydown/wm_syskeydown時,會post一個wm_char/wm_syschar。所以若是使用了translatemessage,消息的處理順序會變成wm_keydown->wm_char->wm_keyup。

10.     getmessage/peekmessage內部算法:先判斷線程消息隊列狀態是否有qs_sendmessage標誌,若是有則從send隊列取消息並處理但不返回(即getmessage內部檢測到send的消息後,會replaymessage(dispatchmessage(msg));而若是將send消息交給用戶代碼來dispatch,後者可能忘記須要答覆發送線程);再判斷是否有qs_postmessage,若是有則從post隊列取消息填充msg結構而後返回(所以,用戶經過msg結構從getmessage處取得的消息只能是post的消息);判斷是否有qs_quit標記,若有則表示已經postquitmessage因而填充msg結構並返回(所以即便先postquitmessage再post用戶消息,也能保證退出前用戶消息被處理)。再判斷是否有qs_input標誌,若是viq中有輸入則填充msg結構並返回(所以即便有輸入也能夠退出且若是有輸入則不重繪)。判斷是否有qs_paint標誌,有則表示窗口仍然有髒區域(直到beginpaint)因而填充msg產生一個wm_paint消息。最後判斷是否有qs_timer標誌,若有則表示剛到時,因而移除標誌並填充msg結構返回。能夠看見有幾種消息被賦予了至關低的優先級,並不加入消息隊列:wm_quit是爲了保證退出前處理完全部普通消息;wm_paint是由於開銷大,只在空閒時處理;wm_timer是爲了不處理慢觸發快而致使消息隊列溢出。

11.     msgwaitformultipleobjects實現爲,在事件對象數組後追加一項,若是要檢測的消息隊列標誌被置位則觸發新追加的事件對象。關於輸入消息的監聽,因爲ms設計爲只在新增輸入消息時事件對象才觸發,所以須要以mwmo_inputavailable爲參數來調用msgwaitformultipleobjectsex,達到一旦輸入隊列非空就觸發的效果。另外msgwaitformultipleobjectsex還支持waitall及apc等功能。

12.     對於跨進程用sendmessage發送wm_gettext、wm_settext等消息,系統開發會自動使用共享內存來轉換消息參數的地址值以跨越進程邊界。顯然用戶自定義消息須要本身來處理跨進程問題。wm_copydata能夠用來跨進程發送數據,發送進程傳入一個有數據的緩衝,接受進程獲得的緩衝地址轉而指向一塊相同內容的共享內存,系統開發在sendmessage返回時釋放共享內存(故這個消息只能send)。

13.     任意一個窗口都有編碼屬性,這個屬性在綁定消息處理函數時肯定(即調用regis地理信息系統 terclassa或以gwlp_wndproc調用setwindowlongptra表示這是一個ansi窗口而不是unicode窗口),經過系統開發在不一樣窗口間轉發數據時,系統開發會自動進行編碼轉換。判斷窗口的編碼iswindowunicode。

14.     對getkeystate和getasynckeystate的理解:線程的局部輸入狀態中有一份鍵盤狀態表,在處理每一個鍵盤消息的時候更新。getkeyboardstate獲取整個表,getkeystate獲取某個表項,因爲鍵盤消息不必定可以及時處理,所以內部表不必定夠新,要得到實事狀態,用getasynckeystate,該api經過其餘硬件 中斷得到最新按鍵狀態。考慮一種getasynckeystate的實現:線程先設置中斷函數,再等待一個事件,中斷到來時發現該線程中斷函數指針非空因而執行函數,函數內部查詢最新按鍵狀態而後觸發事件,中斷結束後線程從等待的事件中被喚醒,最後返回按鍵狀態。

第27章  其餘硬件 輸入模型和局部輸入狀態(第4版)

 

1.         系統開發啓動後建立rit(raw input thread,原始輸入線程),它維護一個結構叫shiq(system hardware input queue,系統開發其餘硬件 輸入隊列),鼠標鍵盤的其餘硬件 驅動開發 將各自的消息添加到shiq中,若是消息是鼠標消息,rit就檢測當前光標下方的窗口,而後將鼠標消息拋到該窗口建立線程的viq中(virtual input queue,虛擬輸入隊列),除非某個窗口調用了setcapture,則rit把鼠標消息拋給捕獲窗口所在的線程;若是是鍵盤消息,ritvar url = window.location.href;document.write("此文連接:"+url+"

 

");document.write("轉載請註明出處:"+document.title+"");

相關文章
相關標籤/搜索