1 / 72

同步問題

同步問題. 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

cricket
Télécharger la présentation

同步問題

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


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

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

  3. C Runtime Library • C Runtime Library︰ • Single-threaded • Multi-threaded • Single-threaded 生于1970年代。當時的硬體情況決定多任務是不可能的,多線程更是痴人說夢。 • 所以Single-threaded 使用數個全局變量和靜態變量,這不會引起什麼問題,因為只有一個線程使用它。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  36. NotificationEvent和SynchronizationEvent • 通知事件(notification event)有這樣的特性,當它進入信號態后,它將一直處于信號態直到明確地把它重置為非信號態。此外,當通知事件進入信號態后,所有在該事件上等待的線程都被釋放。 • 同步事件(synchronization event),只要有一個線程被釋放,該事件就被自動重置為非信號態。

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

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

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

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

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

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

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

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

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

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

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

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

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

More Related