小續
這是我11年看《windows核心程式設計》時所作的一些筆記,現整理出來共享給大家
windows核心程式設計整理(上)
windows核心程式設計整理(下)
線程的基礎知識
程序是不活潑的,程序從來不執行任何東西,程序隻是線程的容器
線程用于描繪程序中的運作路徑
每個程序必須擁有一個進入點函數,線程從這個進入點開始運作
線程的退出代碼線上程的核心對象中維護
程序由程序核心對象和位址空間組成
線程由線程核心對象和線程堆棧組成
大多數線程擁有的所有使用者對象是由包含建立這些對象的線程的程序擁有的,但是,一個線程擁有兩個使用者對象,即視窗和挂鈎
每個線程都有他自己的一組CPU寄存器,稱為線程的上下文,該上下文反映了線程上次運作該線程的CPU寄存器的狀态
指令指針和堆棧指針寄存器是線程上下文中兩個最重要的寄存器,線程總是在程序得到上下文中運作的,這些位址都用于标志擁有線程的程序位址空間中的記憶體
當實作任何類型的程式設計項目時,必須知道将哪個庫與你的項目相連結
每個線程都需要他自己的errno變量,此外,必須有一種機制,使得線程能夠應用他自己的errno變量,但是又不觸及另一個線程的errno變量
若要使多線程C和C++程式能夠正确地運作,必須建立一個資料結構,并将他與使用C/C++運作期庫函數的每個線程關聯起來,若要建立一個新線程,絕對不要調用作業系統的createThread函數,必須調用c/C++運作期庫函數_beginthreadex
單線程庫在多線程應用程式中不能正确地運作
如果建立一個多線程應用程式,必須在編譯器的指令行上設定/MT(指多線程應用程式)或者./MD(指多線程DLL)開關
WINDOWS不是一種實時作業系統
線程對象的暫停計數大一1,這意味着該線程已經暫停運作,不應該給他安排任何CPU時間
系統不給無事可做的線程配置設定CPU時間
線程的暫停計數減為〇的時候,除非線程正在等待其他某種事情的發生,否則該線程就處于可排程狀态
一旦改變了線程的環境,必須使線程成為可排程線程
如果一個線程暫停了3次,他必須恢複3次,然後他才可以被配置設定一個CPU
線程可以自行暫停運作,但是不能自行恢複運作
程序從來不會被安排獲得CPU時間
睡眠模式:線程也可以告訴系統,他不想在某個時間段内被排程
環境結構(context結構)使得系統能夠記住線程的狀态,這樣,當下次線程擁有可以運作的CPU時,他就能夠找到他上次中斷運作的地方了
一個線程實際上有兩個環境,一個是使用者方式,一個是核心方式
修改其環境的線程應該首先暫停,否則其結果将無法預測
每個線程都會被賦予一個從0(最低)到31(最高)的優先級号碼
WINDOWS API 展示了系統的排程程式上的一個抽象層,這樣就永遠不會直接與排程程式進行通信
希望無論系統中正在運作什麼,外殼程式都具有極強的相應能力
系統不允許任何其他線程擁有0優先級,隻有一個零頁線程
程序是根本不能排程的,隻有線程才能被排程
一旦線程終止運作,核心對象就能被撤銷
導緻線程成為可排程線程的裝置驅動程式可以決定優先級等級提高的數量
可以控制哪個CPU能夠運作某些線程,這稱為硬親緣性
使用者方式中線程的同步
系統中的所有的線程都必須擁有對各種系統資源的通路權,這些資源包括記憶體堆棧,序列槽,視窗,檔案和許多其他資源
所謂原子通路,是指線程在通路資源時能夠確定所有其他線程都不在同一時間内通路相同的資源 對X86家族的CPU來說,互鎖函數會對總線發出一個硬體信号,防止另一個CPU通路用一個記憶體位址
沒有任何互鎖函數僅僅負責對值進行讀取操作(而不改變這個值)
當一個CPU從記憶體讀取一個位元組時,他不隻是取出一個位元組,他要取出足夠的位元組來填入高速緩存行。高速緩存行有32或64個位元組組成(視CPU而定),而且始終在第32個位元組或第64個位元組的邊界上對齊。高速緩存行的作用是為了提高CPU運作的性能
當線程想要通路共享資源,或者得到關于某個特殊事件的通知時,該線程必須調用一個作業系統函數
volatile類型的限定詞,他告訴編譯器,變量可以被應用程式本身以外的某個東西進行修改這些東西包括作業系統,硬體或者同時執行的線程等,這個限定詞會告訴編譯器,不要對該變量進行任何優化,并且總是重新加載來自該變量的記憶體單元的值
有一個關鍵的問題必須記住,當擁有一項可供多個線程通路的資源時,應該建立一個critical_section結構
編寫需要使用共享資源的任何代碼都必須封裝在entercriticalsection和leavecriticalsection函數中
當線程試圖進入另一個線程擁有的關鍵代碼段時,調用線程就立即被置于等待狀态,這意味着該線程必須從使用者方式轉入核心方式,這種轉換是要付出很大代價的
線程與核心對象的同步
使用者方式同步的優點是他的同步速度非常快,如果強調線程的運作速度,那麼首先應該确定使用者方式的線程同步機制是否合适需要
互鎖函數家族隻能在單值上運作,根本無法使線程進入等待狀态
核心對象機制的适應性遠遠優于使用者方式機制
核心對象機制的唯一不足之處是他的速度比較慢
當一個對象得到狀态改變時,我稱之為成功等待的副作用
算法是公平的,這意味着如果多個線程正在等待,那麼每當對象變為已通知狀态時,每個線程都應該得到他自己的被喚醒的機會
當調試一個程序時,隻要達到一個斷點,該程序中的所有線程均暫停運作
在所有的核心對象中,時間核心對象是個最基本的對象。包含一個使用計數,一個用于指明該事件是個自動重置的事件還是個人工重置的事件的布爾值,另一個用于指明該事件處于已通知狀态還是未通知狀态的布爾值
事件能夠通知一個操作已經完成,有兩種不同類型的事件對象,一種是人工重置的事件,另一種是自動重置的事件,當人工重置的事件得到通訊時,等該該時間的所有線程均變為可排程線程,當一個自動重置的事件得到通知時,等待該事件的線程中隻有一個線程變成可排程線程
當不再需要事件核心對象時,應該調用closehandle函數
一旦事件已經建立,就可以直接控制他的狀态
等待計時器是在某個時間或者按規定的間隔時間發出自己的信号通知的核心對象。他們通常用來在某個時間執行某個操作
異步過程調用(APC)
定時器常常用于通信協定中
凡是稱職的WINDOWS程式設計員都會立即将等待定時器與使用者定時器(用settimer函數進行設定)進行比較
等待定時器屬于核心對象,這意味着他們可以供多個線程共享,并且是安全的
當使用者定時器報時的時候,隻有一個線程得到通知,另一方面,多個線程可以在等待定時器上進心等待,如果定時器是個人工重置的定時器,則可以排程若幹個線程
程序、線程和作業都是核心對象
當程序正在運作的時候,程序核心對象處于未通知狀态,當程序終止運作的時候,他就變成為已通知狀态
線程可以使自己進入等待狀态,直到一個對象變為已通知狀态
當程序等待的對象處于未通知狀态中時,這些線程不可排程,但是一旦對象變為已通知狀态,線程看到該标志變為可排程狀态,并且很快恢複運作
等待函數可使線程自願進入等待狀态,知道一個特定的核心對象變為已通知狀态為止
信标核心對象用于對資源進行計數。他們與所有核心對象一樣,包含一個使用數量,另外包含兩個帶符号的32位值,一個是最大資源數量,一個是目前資源數量
使用信标,就能很好的處理對資源的監控和對線程的排程
使用信标時,不要将信标對象的使用數量與他的目前資源數量混為一談
信标的出色之處在于他們能夠以原子操作方式來執行測試和設定操作
互斥對象核心對象能夠確定線程擁有對單個資源的互斥通路權,互斥對象包含一個使用數量,一個線程ID和一個遞歸計數器。
互斥對象屬于核心對象,而關鍵代碼段則屬于使用者方式對象,這意味着互斥對象的運作速度不關鍵代碼要慢,但是這也意味着不同的程序中的多個線程能夠通路單個互斥對象,并且這意味着線程在等待通路資源時可以設定一個逾時值
ID用于标志系統中的哪個線程目前擁有互斥對象,遞歸計數器用于指明線程擁有互斥對象的次數
互斥對象用于保護由多個線程通路的記憶體塊
通過調用一個等待函數,并傳遞負責保護資源的互斥對象的句柄,線程就能夠獲得對共享資源的通路權
與所有情況一樣,對互斥核心對象進行的檢查和修改都是以原子操作方式進行的
每當線程成功地等待互斥對象時,該對象的遞歸計數器就遞增
互斥對象有一個“線程所有權”的概念,沒有一種對象能夠記住哪個線程成功地等待到該對象,隻有互斥對象能夠對此保持跟蹤
系統保持對所有互斥對象和線程核心對象的跟蹤,是以他能準确的知道互斥對象合适被放棄
異步裝置I/O使得線程能夠啟動一個讀操作或寫操作,但是不必等待讀操作或寫操作完成,當初始化操作完成時,該線程可以終止自己的運作,等待系統通知他檔案已經讀取。裝置對象可以是同步的核心對象
線程同步工具包
反複強調,關鍵代碼段屬于使用者方式對象(一般情況下)
關鍵代碼段必須包含某個核心對象,以便是線程進入有效的等待狀态。關鍵代碼段的運作速度很快,因為隻有當争用該關鍵代碼段的時候,才使用該核心對象
c++對象不能被通知,隻有核心對象才能被通知,并且可以用于線程的同步
許多應用程式存在一個基本的同步問題,這個問題稱為單個寫入程式/多個閱讀程式環境
線程池的使用
使用者方式的同步機制的出色之處在于他的同步速度很快
建立多線程應用程式是非常困難的,需要會面臨兩個大問題,一個是要對線程的建立和撤銷進行管理,另一個是要對線程對資源的通路實施同步
如何對線程個建立個撤銷進行管理的問題上用線程池實作
你自己從來不調用createthread,系統會自動為你的程序建立一個線程池
當該線程處理完客戶機的請求之後,該線程并不立即被撤銷,他要傳回線程池,這樣他就可以處理已經排隊的任何其他工作項目
線程池希望經常處理異步I/O請求,即每當線程将一個I/O請求排隊放入裝置驅動程式時,便要處理異步I/O請求
設計良好的線程池也必須設法保證線程始終都能處理各個請求
線程池不能對線程池中的線程數量規定一個上限,否則就會發生渴求或死鎖現象
當使用線程池函數時,應該查找潛在的死鎖條件
隻有當線程不是吃梨定時器的工作項目的線程時,該線程才能進行對定時器的中斷删除
如果你正在使用定時器元件的線程,不應該試圖對任何定時器進行中斷删除,否則就會産生死鎖
線程池的定時器元件建立等待定時器,這樣,他就可以給apc(異步過程調用)項目排隊,而不是給對象發送通知
如果正在等待一個自動重置的事件核心對象,一旦該對象變為已通知狀态,該對象就重置為他的為通知狀态,并且他的工作項目将被放入隊列
I/O元件的線程全部在一個I/O元件端口上等待
關閉裝置會導緻他的所有待處理的I/O請求立即完成,并産生一個錯誤代碼,要做好準備,在你的回調函數中處理這種情況
纖程
WINDOWS添加了一種纖程,以便能夠非常容易地将現有的nuix伺服器應用程式移植到WINDOWS
nuix伺服器應用程式屬于單線程應用程式(由WINDOWS定義),但是他能夠為多個客戶程式提供服務
纖程是以使用者方式反碼來實作的,核心并不知到纖程,并且他們是根據使用者定義的算法來排程的,纖程采用非搶占式排程方式
單線程可以包含一個或多個纖程,就核心而言,線程是搶占排程的,是正在執行的代碼
除非打算建立更多的纖程以便在同一個線程運作,否則沒有理由将線程轉換層纖程
Windows的記憶體結構
作業系統使用的記憶體結構是了解作業系統如何運作的最重要的關鍵
每個程序都被賦予他自己的虛拟位址空間
對于32位程序來說,這個位址空間是4GB,因為32位指針可以擁有從0x00000000至0xffffffff
由于每個程序可以接收他自己的私有位址空間,是以當程序中的一個線程正在運作時,該線程可以通路隻屬于他的程序的記憶體,屬于所有其他程序的記憶體則隐藏着,并且不能被正在運作的線程通路
屬于作業系統本身的記憶體也是隐藏的,正在運作的線程無法通路
這是個虛拟位址空間,不是實體位址空間,該位址空間隻是記憶體位址的一個範圍,在你能夠成功地通路資料而不會出現違規通路之前,必須賦予實體存儲器,或者将實體存儲器映射到各個部分的位址空間
注意,當作業系統建立程序的位址空間時,需要檢查一個可執行的largeadderssaware标志。對于dll,系統則忽略該标志
有時候系統能夠代表你的程序來保留位址空間的區域
程序環境塊(FEB)
系統也需要建立一個線程環境塊(TEB),以便管理程序中目前存在的所有線程
系統規定,要求保留的位址空間區域均從配置設定粒度邊界(目前所有平台上均為64KB)開始,但是系統本身并不受這個規定的限制
保留區域必須是CPU的頁面大小的倍數
當你的程式算法不再需要通路已經保留的位址空間區域時,該區域應該被釋放。這個過程稱為位址空間的區域
若要使用已保留的位址空間區域,必須配置設定實體存儲器,然後将該實體存儲器映射到已保留的位址空間區域。這個過程稱為送出實體存儲器
實體存儲器總是以頁面的形式來送出的
磁盤上的檔案通常稱為頁檔案,他包含了可供所有程序使用的虛拟記憶體
當一個線程試圖通路一個位元組的記憶體時,CPU必須知道這個位元組是在ram中還是在磁盤上
作業系統與CPU相協調,共同将ram的各個部分儲存到頁檔案中,當運作的應用程式需要時,在将頁檔案的各個部分重新加載到ram
頁檔案增加了應用程式可以使用的ram的容量,是以頁檔案的使用是視情況而定的
最好将實體存儲器視為存儲在磁盤驅動器(通常是硬碟驅動器)上的頁檔案中的資料
系統的頁檔案的大小是确定有多少實體存儲器可供應用程式使用時應該考慮的最重要的因素,ram的容量則影響非常小
要提高系統的運作性能,增加ram比提高CPU的速度所産生的效果更好
當硬碟上的一個程式的檔案映像(這是個.exe檔案或dll檔案)用作位址空間的區域的實體存儲器時,他稱為記憶體映射檔案
Microsoft不得不通過軟碟來運作的映射檔案,這樣,安裝應用程式才能正确運作
已經配置設定的實體存儲器的各個頁面可以被賦予不同的保護屬性
讓所有的執行個體共享同樣的記憶體頁面能夠大大提高系統的性能,但是這要求所有執行個體都将該記憶體視為隻讀或隻執行的記憶體
資料對齊并不是作業系統的記憶體結構的一部分,而是CPU結構的一部分
當資料大小的資料模數的記憶體位址是0時,資料是對齊的,例如,word值應該總是從被2除盡的位址開始,而dword值應該總是從被4除盡的位址開始
在應用程式中使用虛拟記憶體
WINDOWS提供3種進行記憶體管理的方法:
虛拟記憶體,最适合用來管理大型對象或結構數組
記憶體映射檔案,最适合用來管理大型資料流(通常來自檔案)以及在單個計算機 上運作的多個程序之間共享資料
記憶體堆棧,最适合用來管理大量的小對象
用于管理虛拟記憶體的函數可以用來直接保留一個位址空間區域,将實體存儲器(來自頁檔案)送出給該區域,并且可以設定你自己的保護屬性
如果保留的區域預計在很長時間内不會被釋放,那麼可以在盡可能搞的記憶體位址上保留該區域,防止其導緻該區域分成碎片
當保留一個區域時,應該為該區域賦予一個已送出記憶體最常用的保護屬性
當保留一個區域後,必須将實體存儲器送出給該區域,然後才能通路該區域中包含的記憶體位址,系統從他的頁檔案中将已送出的實體存儲器配置設定給一個區域,實體存儲器總是按頁面邊界和頁面大小的塊來送出的
虛拟記憶體為我們提供了一種兼顧預先聲明二維矩陣和實作連結表的兩全其美的方法
虛拟記憶體技術存在的一個問題是,必須确定實體存儲器在何時送出
系統總是按頁面的配置設定粒度來送出實體存儲器的
與送出實體存儲器的情況一樣,回收時也必須按照頁面的配置設定粒度來進行
保護屬性是與記憶體的整個頁面相關聯的,而不是賦予記憶體的各個位元組的
位址視窗擴建(AWE),Microsoft建立awe是出于下面兩個目地:允許應用程式對從來不在作業系統與磁盤之間交換的ram進行配置設定;允許應用程式通路的ram大于程序的位址空間
你建立的最大視窗取決于你的位址空間中可用的最大相鄰空閑位址塊
AWE的局限性是,映射到位址視窗的所有記憶體必須是可讀的和可寫入的
每個ram頁面由作業系統賦予一個頁框号
頁框号本身對應用程式沒有任何用處,不應該檢視該數組的内容,并且肯定不應該修改該數組中的任何一個值
隻有擁有頁面的程序才能使用已經配置設定的ram頁面,AWE不允許ram頁面被映射到另一個程序的位址空間,是以不能在程序之間共享ram塊