同步問題 sigang@mti.xidian.edu.cn.

Slides:



Advertisements
Similar presentations
工職數學 第四冊 第一章 導 數 1 - 1 函數的極限與連續 1 - 2 導數及其基本性質 1 - 3 微分公式 1 - 4 高階導函數.
Advertisements

阻塞操作. 在 linux 里,一个等待队列由一个 wait_queue_head_t 类型的结构来描述 等待队列的初始化: static wait_queue_head_t testqueue; init_waitqueue_head(&testqueue);
08 CSS 基本語法 8-1 CSS 的演進 8-2 CSS 樣式規則與選擇器 8-3 連結HTML 文件與CSS 樣式表
Chapter 2 Windows 系統總述 Team 1 (page 17 ~ page 56)
2015/Spring 跨平台行動程式進階應用 王派洲老師
認識倍數(一) 設計者:建功國小 盧建宏.
Hadoop 單機設定與啟動 step 1. 設定登入免密碼 step 2. 安裝java step 3. 下載安裝Hadoop
Chapter 5 遞迴 資料結構導論 - C語言實作.
Chapter 5 迴圈.
第一篇 Unix/Linux 操作介面 第 1 章 Unix/Linux 系統概論 第 2 章 開始使用 Unix/Linux
4B冊 認識公倍數和最小公倍數 公倍數和最小公倍數的關係.
HiNet 光世代非固定制 用戶端IPv6設定方式說明
在NS-2上模擬多個FTP連線,觀察頻寬的變化
桌面環境簡介及IDE開發工具 Outline (一)什麼是Linux? (二)桌面環境系統簡介 (三)IDE開發工具.
SQL Stored Procedure SQL 預存程序.
2017 Operating Systems 作業系統實習 助教:陳主恩、林欣穎 實驗室:720A Lab8 1.
2017 Operating Systems 作業系統實習 助教:陳主恩、林欣穎 實驗室:720A Lab10 1.
Methods 靜宜大學資工系 蔡奇偉副教授 ©2011.
2017 Operating Systems 作業系統實習 助教:陳主恩、林欣穎 實驗室:720A.
8个处理IRP的模型
WDM驱动程序设计 第 5 讲 同步技术.
Wavelet transform 指導教授:鄭仁亮 學生:曹雅婷.
私立南山高中 信息組 電腦研習 電腦資料的備份 中華民國 99年4月20日 星期二.
Chap3 Linked List 鏈結串列.
第一章 直角坐標系 1-1 數系的發展.
China’s Software Industry August 2006 Instructor: Hengming Zou, Ph.D.
網路安全技術 OSI七層 學生:A 郭瀝婷 指導教授:梁明章.
Topic Introduction—RMI
資料結構 優點 缺點 1 陣列 (Array) 沒有額外變量 (例head, next,...) 運作/操作較簡單 更新資料時,若要保持順序,需要移動較大量資料 靜態結構Static (宣告時已決定了陣列元素多少,不能在程式執行期間增減元素) 2 隊列Queue (FIFO) 容易更新 加入enqueue:
第一章 直角坐標系 1-3 函數圖形.
為成功制定目標和行動計畫 國際獅子會分區主席訓練.
學習單元:N6 數的性質 學習單位:N6-3 用短除法求H.C.F. 和 L.C.M. 學習重點 : 1. 複習因數分解法求
數學 近似值 有效數值.
網頁資料知多少? 事 實 ? 謠言?.
讓Emulator可以 使用Android Market
小學四年級數學科 8.最大公因數.
CH05. 選擇敘述.
期末考.
微積分網路教學課程 應用統計學系 周 章.
挑戰C++程式語言 ──第8章 進一步談字元與字串
Class & Object 靜宜大學資工系 蔡奇偉副教授 ©2011.
信号量(Semaphore).
C qsort.
Windows内核安全编程实践之路
流程控制:Switch-Case 94學年度第一學期‧資訊教育 東海大學物理系.
基本指令.
2017 Operating Systems 作業系統實習 助教:陳主恩、林欣穎 實驗室:720A Lab11 1.
Scratch: 動畫或遊戲編程 任務10:尋找小鬼.
Chapter 15 檔案存取 LabVIEW中的檔案存取函數也可將程式中的資料儲存成Excel或Word檔。只要將欲存取的檔案路徑位址透過LabVIEW中的路徑元件告訴檔案存取函數後,LabVIEW便可將資料存成Excel或Word檔;當然也可以將Excel或Word檔的資料讀入LabVIEW的程式中。
12797: Letters ★★★☆☆ 題組:Problem Set Archive with Online Judge
临界区问题的硬件指令解决方案 (Synchronization Hardware)
Commando War ★★☆☆☆ 題組:Problem Set Archive with Online Judge
2018 Operating Systems 作業系統實習 助教:林欣穎 實驗室:720A.
Activity的生命週期: 播放音樂與影片 靜宜大學資管系 楊子青
多站台網路預約系統之 AJAX即時資料更新機制
What is “this”? 在物件導向程式設計中,類別的定義就是在說明如果創建了“這個物件”的話,它會具有那些屬性與功能,以及這些功能是如何實現的。 而所謂的“這個物件”就以 this 來表示。 當我們在JavaScript與jQuery中寫 script 程式(函式)時,“誰”呼叫這個函式,這個“誰”就是該函式中所謂的.
作業系統實習課(二) -Scheduler-Related System Calls-
多國語系 建國科技大學 資管系 饒瑞佶.
第三章 Arduino互動程式設計入門 Arduino程式基礎 認識變數 認識數字系統 認識常數.
ABAP Basic Concept (2) 運算子 控制式與迴圈 Subroutines Event Block
String類別 在C語言中提供兩種支援字串的方式 可以使用傳統以null結尾的字元陣列 使用string類別
NFC (近場通訊, Near Field Communication) 靜宜大學資管系 楊子青
10303: How Many Trees? ★★☆☆☆ 題組:Contest Archive with Online Judge
Chapter 4 Multi-Threads (多執行緒).
營運模式.
7. 三角學的應用 正弦公式 餘弦公式 a2 = b2 + c2 - 2bc cos A b2 = a2 + c2 - 2ac cos B
Develop and Build Drives by Visual C++ IDE
JUDGE GIRL 使用介紹 & 常見問題 TAs :
方法(Method) 函數.
ABAP Basic Concept (2) 運算子 控制式與迴圈 Subroutines Event Block
Presentation transcript:

同步問題 sigang@mti.xidian.edu.cn

引入 Vc中Use run-time library 選項︰ Single-Threaded Multi-threaded Multi-threaded DLL 分別靜態連接 LIBC.LIB 靜態連接 LIBCMT.LIB 動態連接MSVCRT.DLL debug 和release版本連接到不同的庫

C Runtime Library C Runtime Library︰ Single-threaded Multi-threaded

時間的流逝 歷史的車輪不會停,硬體在突飛猛進的發展,軟體也發展。 出現了多任務,多線程 使用Single-threaded庫時出現了很多問題 比如︰

Multi-threaded 利用我們后面要講述的同步機製, Multi-threaded 版本的C Runtime Library出世了 差別︰對于一些變量,改成每個線程一個,對于一些數據架構的訪問加上了同步機製。 Single-threaded Runtime Library 消失么? 不會。因為同步機製的引入而引起的大小和效率問題使Multi-threaded版本僅在需要時使用

一個原始的同步問題 驅動程式中的代碼︰ static LONG lActiveRequests; NTSTATUS DispatchRead(PDEVICE_OBJECT fdo, PIRP Irp) { ++lActiveRequests; ... // process PNP request --lActiveRequests; } 兩個問題?

第一個問題 X86處理器生成如下的彙編代碼 ; ++lActiveRequests; ; ++lActiveRequests; mov eax, lActiveRequests add eax, 1 mov lActiveRequests, eax ; ++lActiveRequests; mov eax, lActiveRequests add eax, 1 mov lActiveRequests, eax

解決的辦法 把load/add/store和load/subtract/store指令序列替換為原子指令︰ ; ++lActiveRequests; inc lActiveRequests ...; --lActiveRequests; dec lActiveRequests INC和DEC指令不能被中斷,但是多處理器環境中仍然是不安全的,因為這兩個指令都是由幾條微代碼(CISC的特點)實現的

最終的解決 ; ++lActiveRequests; lock inc lActiveRequests ...; lock dec lActiveRequests LOCK指令前綴可以使當前執行多微碼指令的CPU鎖定匯流排,從而保證數據訪問的完整性。

第二個問題 一個驅動程式支持多個設備怎么辦? 不能使用靜態變量,而要使用一個保存設備特有數據的設備擴展的一個成員。 這個簡單的同步問題解決了。 但是並不是所有的同步問題都可以這樣容易地解決

使用IRQL優先級方案來避免搶先 複習︰

搶先(preempt) 什麼是搶先? 執行在PASSIVE_LEVEL的IRQL上活動能被任何活動搶先 一旦CPU執行在高于PASSIVE_LEVEL的IRQL上時,該CPU上的活動僅能被擁有更高IRQL的活動搶先。

線程調度呢? 線程調度呢?(dispatch)? 當IRQL級高于或等于DISPATCH_LEVEL級時線程切換停止,無論當前活動的是什麼線程都將保持活動狀態直到IRQL降到DISPATCH_LEVEL級之下。 執行在高于或等于DISPATCH_LEVEL級上的代碼絕對不能造成頁故障。 系統在APC級處理頁故障

IRQL的隱含控制 驅動程式中一些固定的框架(在分發歷程中或者dpc調用startpacket,startnextpacket排隊請求,操作隊列,所有IRP的開始都是在StartIo例程裡面完成) 不理解?因為系統有一個對IRQ的隱含控制 StartIo和Dpc都營運在DISPATCH_LEVEL級,他們不會互相衝突

IRQL的明確控制 可以臨時提升IRQL,然後再降回到原來的IRQL KeRaiseIrql和KeLowerIrql函數 KIRQL oldirql; ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); KeRaiseIrql(DISPATCH_LEVEL, &oldirql); ... KeLowerIrql(oldirql);

自旋鎖 前面IRQL的一個問題? 不能解決多cpu的問題。 自旋鎖(spin lock) 對象可以解決這個問題。 Spin lock的所有者是cpu,不是特定的線程

什麼是自旋鎖 為了獲得一個自旋鎖,在某CPU上營運的代碼需先執行一個原子操作,該操作測試並設置(test-and-set)某個內存變量,由於它是原子操作,所以在該操作完成之前其它CPU不可能訪問這個內存變量。如果測試結果表明鎖已經空閒,則程式獲得這個自旋鎖並繼續執行。如果測試結果表明鎖仍被佔用,程式將在一個小的循環內重複這個“測試並設置(test-and-set)”操作,即開始“自旋”。最後,鎖的所有者透過重置該變量釋放這個自旋鎖,于是,某個等待的test-and-set操作向其調用者報告鎖已釋放。

有關自旋鎖 如果一個已經擁有某個自旋鎖的CPU想第二次獲得這個自旋鎖,則該CPU將死鎖(deadlock) ,自旋鎖沒有與其關聯的“使用計數器”或“所有者標識”;鎖或者被佔用或者空閒 。 CPU在等待自旋鎖時不做任何有用的工作,僅僅是等待。所以,為了避免影響性能,應該在擁有自旋鎖時做盡量少的操作,因為此時某個CPU可能正在等待這個自旋鎖。 僅能在低于或等于DISPATCH_LEVEL級上請求自旋鎖,在擁有自旋鎖期間,內核將把代碼提升到DISPATCH_LEVEL級上營運。

使用自旋鎖 要在非分頁內存中為一個KSPIN_LOCK對象分發存儲 ,通常在設備擴展中 調用KeInitializeSpinLock初始化這個對對象 當代碼營運在低于或等于DISPATCH_LEVEL級上時獲取這個鎖,並執行需要保護的代碼,最後釋放自旋鎖 看一個例子︰

在DISPATCH_LEVEL獲取自選鎖 如果知道代碼已經處在DISPATCH_LEVEL級上 ,如DPC、StartIo,和其它執行在DISPATCH_LEVEL級上的驅動程式例程 可以調用兩個專用函數來操作自旋鎖 ︰ KeAcquireSpinLockAtDpcLevel(&pdx->QLock); ... KeReleaseSpinLockFromDpcLevel(&pdx->QLock);

為什麼不一樣 Why? KeAcquireSpinLock可能是這樣實現的︰ //Disable thread preemption oldirql = KeRaiseIrql(DISPATCH_LEVEL) ; KeAcquireSpinLockAtDpcLevel() 所以在Dispatch level上沒有必要使用KeAcquireSpinLock

內核模式同步對象 Windows NT提供了五種內核同步對象(Kernel Dispatcher Object)。事件(event),Semaphore(信號燈) ,Mutex(互斥) ,Timer(定時器) ,Thread(線程) 可以用它們控制非任意線程(普通線程)的流程 ,因為這些對象和spinlock不一樣,它們不是cpu特定的,而是線程特定的。 下面是這五個對象的簡要比較︰

對象 數據類型 描述 Event(事件) KEVENT 阻塞一個線程直到其它線程檢測到某事件發生 Semaphore(信號燈) KSEMAPHORE 與事件對象相似,但可以滿足任意數量的等待 Mutex(互斥) KMUTEX 執行到關鍵代碼段時,禁止其它線程執行該代碼段 Timer(定時器) KTIMER 延遲線程執行一段時期 Thread(線程) KTHREAD 阻塞一個線程直到另一個線程結束

內核模式同步對象 在任何時刻,任何上面的對象都處于兩種狀態中的一種︰信號態或非信號態。 當代碼營運在某個線程的上下文中時,它可以阻塞這個線程的執行,調用KeWaitForSingleObject或KeWaitForMultipleObjects函數可以使代碼(以及背景線程)在一個或多個同步對象上等待,等待它們進入信號態

“當前”線程 如果在線程執行時發生了軟體或硬體中斷,那么在內核處理中斷期間,該線程仍然是“當前”線程 內核處理中斷開始執行時所在的上下文環境就是指這個“當前”線程的上下文 為了附應各種中斷,Windows NT線程調度可能會切換線程,這樣,另一個線程將成為新的“當前”線程 所以中斷程式中的這個上下文就是任意進程上下文(因為不確定是哪個線程的上下文)

線程阻塞的簡單規則︰ 當我們處理某個請求時,僅能阻塞產生該請求的線程。 執行在高于或等于DISPATCH_LEVEL級上的代碼不能阻塞線程。

在單同步對象上等待 KeWaitForSingleObject ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); LARGE_INTEGER timeout; NTSTATUS status = KeWaitForSingleObject(object, WaitReason, WaitMode, Alertable, &timeout);

參數意義 object指向要等待的對象,它應該指向一個上面表中列出的同步對象。該對象必須在非分頁內存中,例如,在設備擴展中或其它從非分頁內存池中分發的數據區 WaitReason是一個純粹建議性的值,驅動程式應把該參數指定為Executive WaitMode是MODE枚舉類型,該枚舉類型僅有兩個值︰KernelMode和UserMode。 驅動程式中Alertable參數指定為FALSE

timeout 最後一個參數&timeout是一個64位超時值的位址,單位為100納秒。正數的超時表示一個從1601年1月1日起的絕對時間。負數代表相對于當前時間的時間間隔。 指定0超時將使KeWaitForSingleObject函數立即返回,返回的狀態代碼指出對象是否處于信號態。如果代碼執行在DISPATCH_LEVEL級上,則必須指定0超時,因為在這個IRQL上不允許阻塞。 超時參數也可以指定為NULL指針,這代表無限期等待。

返回值 STATUS_SUCCESS,結果是所希望的,表示等待被滿足。即你調用KeWaitForSingleObject時,對象或者已經進入信號態,或者后來進入信號態。 STATUS_TIMEOUT指出在指定的超時期限內對象未進入信號態 。如果指定0超時,則函數將立即返回。返回代碼為STATUS_TIMEOUT,代表對象處于非信號態,返回代碼為STATUS_SUCCESS,代表對象處于信號態。 其它兩個返回值STATUS_ALERTED和STATUS_USER_APC表示等待提前終止,對象未進入信號態 ,上面的Alertable和WaitMode參數決定在驅動程式中不會有這兩個值。

在多同步對象上等待 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); LARGE_INTEGER timeout; NTSTATUS status = KeWaitForMultipleObjects(count, objects, WaitType, WaitReason, WaitMode, Alertable, &timeout, waitblocks);

參數 objects指向一個指針數組,每個數組元素指向一個同步對象,count是數組中指針的個數 WaitType是枚舉類型,其值可以為WaitAll或WaitAny,它指出你是等到所有對象都進入信號態,還是只要有一個對象進入信號態就可以。 waitblocks參數指向一個KWAIT_BLOCK架構數組,內核用這個架構數組管理等待操作。不需要初始化這些架構,內核僅需要知道這個架構數組在那裡,內核用它來記錄每個對象在等待中的狀態。 其餘參數與KeWaitForSingleObject中的對應參數作用相同

返回值 如果指定了WaitAll,則返回值STATUS_SUCCESS表示等待的所有對象都進入了信號態。 如果指定了WaitAny,則返回值在數值上等于進入信號態的對象在objects數組中的索引。 如果碰巧有多個對象進入了信號態,則返回值僅代表其中的一個,可能是第一個也可能是其它。可以認為返回值等于STATUS_WAIT_0加上數組索引。 可以先用NT_SUCCESS測試返回碼,然後再從其中提取數組索引︰

NTSTATUS status = KeWaitForMultipleObjects(...); if (NT_SUCCESS(status)) { ULONG iSignalled = (ULONG) status - (ULONG) STATUS_WAIT_0; ... }

內核事件(event) 使用︰內核中經常使用,把一個特定的事件通知給一個等待中的線程。 下面列出了用于處理內核事件的服務函數。 KeClearEvent 把事件設置為非信號態,不報告以前的狀態 KeInitializeEvent 初始化事件對象KeReadStateEvent 取事件的當前狀態 KeResetEvent 把事件設置為非信號態,返回以前的狀態 KeSetEvent把事件設置為信號態,返回以前的狀態

使用 初始化一個事件對象︰KeInitializeEvent ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); KeInitializeEvent(event, EventType, initialstate); event是要初始化事件對象的位址。EventType是一個枚舉值,可以為NotificationEvent或SynchronizationEvent。 initialstate是布爾量,為TRUE表示事件的初始狀態為信號態,為FALSE表示事件的初始狀態為非信號態。

NotificationEvent和SynchronizationEvent

KeSetEvent 調用KeSetEvent函數可以把事件置為信號態︰ ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); LONG wassignalled = KeSetEvent(event, boost, wait); event參數指向一個事件對象,boost值用于提升等待線程的優先級。wait參數指定為FALSE。如果該事件已經處于信號態,則該函數返回非0值。如果該事件處于非信號態,則該函數返回0。

其他函數 調用KeReadStateEvent函數(在任何IRQL上)可以測試事件的當前狀態︰ LONG signalled = KeReadStateEvent(event); 返回值不為0代表事件處于信號態,為0代表事件處于非信號態。 調用KeResetEvent函數(在低于或等于DISPATCH_LEVEL級)可以把事件對象重置為非信號狀態並即獲得事件對象的當前狀態 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); LONG signalled = KeResetEvent(event);

其他函數 如果對事件的上一個狀態不感興趣,可以調用KeClearEvent函數 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); KeClearEvent(event); KeClearEvent函數執行得更快,因為它不讀取事件的當前狀態而直接設置事件為非信號態。

內核信號燈 生產者消費者問題 內核信號燈是一個有同步語義的整數計數器 信號燈計數器為正值時代表信號態,為0時代表非信號態。計數器不能為負值。 釋放信號燈將使信號燈計數器增1,在一個信號燈上等待將使該信號燈計數器減1。如果計數器值被減為0,則信號燈進入非信號態,之后其它調用KeWaitXxx函數的線程將被阻塞。 注意如果等待線程的個數超過了計數器的值,那么並不是所有等待的線程都可以恢復營運。

服務函數 KeInitializeSemaphore 初始化信號燈對象 KeReadStateSemaphore 取信號燈當前狀態 KeReleaseSemaphore 設置信號燈對象為信號態

KeInitializeSemaphore 信號燈對象應該在PASSIVE_LEVEL級上初始化︰ ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); KeInitializeSemaphore(semaphore, count, limit); semaphore參數指向一個在非分頁內存中的KSEMAPHORE對象。count是信號燈計數器的初始值,limit是計數器能達到的最大值,它必須為正數,且比count大

KeReleaseSemaphore 可以調用KeReleaseSemaphore函數釋放信號燈︰ ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); LONG wassignalled = KeReleaseSemaphore(semaphore, boost, delta, wait); 該函數把delta值加到semaphore指向的信號燈計數器上,這將把信號燈帶入信號態,並使等待線程釋放 。返回值為0代表信號燈的前一個狀態是非信號態,非0代表信號燈的前一個狀態為信號態。

boost和wait參數與在KeSetEvent函數中的作用相同 KeReleaseSemaphore不允許把計數器的值增加到超過limit指定的值。如果這樣做,該函數根本就不調整計數器的值,它將產生一個代碼為STATUS_SEMAPHORE_LIMIT_EXCEEDED的異常。除非系統中存在捕獲該異常的處理程式,否則將導致一個bug check。

KeReadStateSemaphore 讀取信號燈的狀態 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); LONG signalled = KeReadStateSemaphore(semaphore); 非0返回值表示信號燈處于信號態,0返回值代表信號燈為非信號態。不要把該返回值假定為計數器的當前值。

互斥對象Mutex 互斥(mutex)就是互相排斥(mutual exclusion)的簡寫。 內核互斥對象為多個競爭線程串行化訪問共享資源提供了一種方法(不一定是最好的方法)。 如果互斥對象不被某線程所擁有,則它是信號態,反之則是非信號態。 內核互斥可以被遞歸獲取,即內核互斥的所有者可以調用KeWaitXxx並指定所擁有的互斥對象從而使等待立即被滿足。如果一個線程真的這樣做,它必須也要以同樣的次數釋放該互斥對象,否則該互斥對象不被認為是空閒的。

Mutex Mutex的功能可以透過其它方法實現 如果需要長時間串行化訪問一個對象,應該首先考慮使用互斥(而不是倚賴提升的IRQL和自旋鎖)。 利用互斥對象控制資源的訪問,可以使其它線程分佈到多處理器平台上的其它CPU中營運,還允許導致頁故障的代碼仍能鎖定資源而不被其它線程訪問。

互斥對象的服務函數 KeInitializeMutex 初始化互斥對象KeReadStateMutex 取互斥對象的當前狀態KeReleaseMutex 設置互斥對象為信號態

KeInitializeMutex ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); KeInitializeMutex(mutex, level); mutex是KMUTEX對象的位址,level參數最初是用于輔助避免多互斥對象帶來的死鎖。現下,內核忽略level參數。 互斥對象的初始狀態為信號態,即未被任何線程擁有。KeWaitXxx調用將使調用者接管互斥對象的控制並使其進入非信號態。

其它函數 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); LONG signalled = KeReadStateMutex(mutex); 返回值0表示互斥對象已被佔用,非0表示未被佔用。 ASSERT(KeGetCurrentIrql() == PASSIVE_LEVEL); LONG wassignalled = KeReleaseMutex(mutex, wait); 該函數返回值總是0,表示該互斥對象曾被佔用過,如果不是這種情況(所有者釋放的不是它自己的對象),KeReleaseMutex將產生bug check。

Timer(定時器) Timer對象可以在指定的絕對時間或間隔時間后自動從非信號態變為信號態。 Timer對象還可以週期性地進入信號態。可以用它來安排一個定期執行的DPC回調函數。(通常也是這樣使用的) 下頁列出了Timer的服務函數︰

服務函數 描述 KeCancelTimer 取消一個活動的定時器 KeInitializeTimer 初始化一次性的通知定時器 KeInitializeTimerEx 初始化一次性的或重複通知的或同步的定時器 KeReadStateTimer 獲取定時器的當前狀態 KeSetTimer 為通知定時器設定時間 KeSetTimerEx 為定時器設定時間和其它屬性

通知定時器 通知定時器用起來像事件(event) 在非分頁內存中分發一個KTIMER對象。然後 在低于或等于DISPATCH_LEVEL級上調用KeInitializeTimer初始化這個定時器對象︰ 在此,定時器處于非信號狀態,它還沒有開始倒計時,在這樣的定時器上等待的線程永遠得不到喚醒。為了啟動定時器倒計時,調用KeSetTimer函數︰

KeSetTimer LARGE_INTEGER duetime; BOOLEAN wascounting = KeSetTimer(timer, duetime, NULL); 和前面一樣,duetime是一個64位的時間值,單位為100納秒。如果該值為正,則表示一個從1601年1月1日算起的絕對時間。如果該值為負,則它是相對于當前時間的一段時間間隔。 返回值如果為TRUE,則表明定時器已經啟動。(在這種情況下,如果我們再調用KeSetTimer函數,則定時器放棄原來的時間重新開始倒計時)

KeSetTimer調用發生之后,定時器開始倒計時,它仍處于非信號態,直到到達指定的時間。在那個時刻,該定時器對象自動變為信號態,所有等待的線程都被釋放。 線程只能在PASSIVE_LEVEL級上等待,而定時器到時間后,等待的線程並不一定會立即得到執行,因為它在PASSIVE_LEVEL 上。 這種通知定時器和WaitForXxxObject函數一塊使用的情況不是很多。 最多的情況是定時器和Dpc例程一塊使用,DPC例程執行在dispatch level級上,它可以有效地搶先所有線程。

定時器和Dpc例程 思想就是在定時器計時時間到了之后去觸發一個DPC例程。 基本使用過程就是︰ 初始化timer和dpc對象 (兩個對象都要先在某處申請非分頁內存) KeInitializeTimer(timer); KeInitializeDpc(dpc, DpcRoutine, context); Dpc就是某處申請空間的dpc對象。 DpcRoutine是一個DPC例程的位址,這個例程必須存在于非分頁內存中。context參數是一個任意的32位值(類型為PVOID),它將作為參數傳遞給DPC例程。

當開始啟動定時器的倒計時,把DPC對象指定為KeSetTimer函數的一個參數︰ LARGE_INTEGER duetime; BOOLEAN wascounting = KeSetTimer(timer, duetime, dpc); 最後一個參數中指定了一個DPC對象位址。當定時器時間到時,系統將把該DPC排入隊列,並且只要條件允許就立即執行它。在最差的情況下,它也與在PASSIVE_LEVEL級上喚醒線程一樣快。

DPC函數 DPC函數的定義如下︰ VOID DpcRoutine(PKDPC dpc, PVOID context, PVOID junk1, PVOID junk2) { ... } 即使KeSetTimer提供了DPC參數,仍可以調用KeWaitXxx函數在PASSIVE_LEVEL級上等待。在單CPU的系統上,DPC將在等待完成前執行,因為它執行在更高的IRQL上。

同步定時器 與事件對象類似,定時器對象也有兩種形式︰通知模式和同步模式。 通知定時器允許有任意數量的等待線程。同步定時器正相反,它只允許有一個等待線程。一旦有線程在這種定時器上等待,並且開始執行,定時器就自動進入非信號態。 為了創建同步定時器,必須使用擴展形式的初始化函數︰ KeInitializeTimerEx

KeInitializeTimerEx KeInitializeTimerEx(timer, SynchronizationTimer); SynchronizationTimer是枚舉類型TIMER_TYPE的一個枚舉值。另一個枚舉值是NotificationTimer。 上面講的KeInitializeTimer(timer)等價于 KeInitializeTimerEx(timer, NotificationTimer);

週期性定時器 前面討論過的定時器僅能定時一次。透過使用定時器的擴展設置函數KeSetTimerEx ,可以請求一個週期性的超時︰ LARGE_INTEGER duetime; BOOLEAN wascounting = KeSetTimerEx(timer, duetime, period, dpc) period是週期超時值,單位為毫秒(ms),dpc是一個可選的指向KDPC對象的指針。這種定時器在第一次倒計時時使用duetime時間,到期后再使用period值重複倒計時。由於不用重複等待超時通知,所以週期性定時器常常與DPC對象聯用。

取消一個週期性定時器 在驅動程式中的某處一定要調用KeCancelTimer取消任何已創建的週期性定時器。如果這個週期性定時器帶有一個DPC,則還需要在取消該定時器之后調用KeRemoveQueueDpc。 一個例子

定時函數 KeDelayExecutionThread ,可以在PASSIVE_LEVEL級上調用該函數並給出一個時間間隔。該函數省去了使用定時器時的麻煩操作,如創建,初始化,設置,等待操作。 LARGE_INTEGER duetime; NSTATUS status = KeDelayExecutionThread(WaitMode, Alertable, &duetime); 在這裡,WaitMode、Alertable,和函數返回代碼與KeWaitXxx中的對應部分有相同的含義。duetime也是內核定時器中使用的同一種時間表達類型。

KeStallExecutionProcessor 如果需要延遲一段非常短的時間(少于50毫秒),可以調用 KeStallExecutionProcessor(nMicroSeconds); 這個延遲的目的是允許硬體在程式繼續執行前有時間為下一次操作做準備。實際的延遲時間可能大大超過請求的時間,因為KeStallExecutionProcessor可以被其它營運在更高IRQL級上的活動搶先,但不能被同一IRQL級上的活動搶先。

內核線程同步 作業系統的進程架構部件(Process Structure)提供了一些例程,WDM驅動程式可以使用這些例程創建和控制內核線程。 如果在KeWaitXxx調用中指定一個內核線程對象,那么線程將被阻塞直到那個內核線程結束營運。那個內核線程透過調用PsTerminateSystemThread函數終止自身。

為了等待某內核線程結束,首先應獲得一個KTHREAD對象(不透明對象)的指針,在內部,該對象用于代表內核線程。 ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL); PKTHREAD thread = KeGetCurrentThread(); 不幸的是,當調用PsCreateSystemThread創建新內核線程時,僅能獲取該線程的不透明句柄。為了獲得KTHREAD對象指針,必須使用對象管理器服務函數︰

ObReferenceObjectByHandle HANDLE hthread; PKTHREAD thread; PsCreateSystemThread(&hthread, ...); ObReferenceObjectByHandle(hthread, THREAD_ALL_ACCESS, NULL, KernelMode, (PVOID*) &thread, NULL); ZwClose(hthread);

不要忘記 在某處ObDereferenceObject函數釋放對該線程對象的引用。 ObDereferenceObject(thread);

互鎖運算 在WDM驅動程式能調用的函數中,有一些函數可以以線程安全和多處理器安全的模式執行算術運算 這些函數有兩種形式,第一種形式以Interlocked為名字開頭,它們可以執行原子操作,其它線程或CPU不能幹擾它們的執行。另一種形式以ExInterlocked為名字開頭,它們使用自旋鎖。

InterlockedCompareExchange 比較並有條件地交換兩個值 服務函數 描述 InterlockedCompareExchange 比較並有條件地交換兩個值 InterlockedDecrement 整數減1 InterlockedExchange 交換兩個值 InterlockedExchangeAdd 加兩個值並返回和 InterlockedIncrement 整數加1 ExInterlockedAddLargeInteger 向64位整數加 ExInterlockedAddLargeStatistic 向ULONG加 ExInterlockedAddUlong 向ULONG加並返回原始值 ExInterlockedCompareExchange64 交換兩個64位值

比較 InterlockedXxx函數可以在任意IRQL上調用;由於該函數不需要自旋鎖,所以它們還可以在PASSIVE_LEVEL級上處理分頁數據。 ExInterlockedXxx函數也可以在任意IRQL上調用,但它們需要在大于或等于DISPATCH_LEVEL級上操作目標數據,所以它們的參數需要在非分頁內存中 使用ExInterlockedXxx的唯一原因是,如果有一個數據變量,且需要增減該變量的值,並且有時還需要用其它指令序列直接訪問該變量。可以在對該變量的多條訪問代碼周遭明確聲明自旋鎖,然後僅用ExInterlockedXxx函數執行簡單的增減操作。