Windows® CE 系统中的同步机制
看到篇好文章,呵呵,獨樂樂,不如眾樂樂
?
本文轉自http://blog.csdn.net/thl789/archive/2006/01/17/582246.aspx ,轉載請注明出處
?
?
摘要
目錄
一、 WinCE進程 /線程模型概覽
二、臨界區( Critical Section)
三、互斥體( Mutex)
四、信號量( Semaphore)
五、事件( Event)
六、消息隊列( MsgQueue P2P)
七、互鎖函數( Interlocked Function)
八、 Wait函數
總結
參考資料以及進一步閱讀
關于作者
?
一、 WinCE 進程 / 線程模型概覽
WinCE 操作系統實現了進程 / 線程兩級管理模型。連同內核進程和系統進程,以及應用進程一起, WinCE 共支持 32 個進程。進程還可以有自己的線程,進程默認有一個主線程,線程分為 256 個優先級別, WinCE 調度程序按照線程優先級高低來調度。進程是 WinCE 中最小資源分配的單元,線程是 WinCE 的最小調度單元。
本文講述的同步機制有些只適用于線程間同步,有些既能用于線程間同步又能用于進程間同步,下面討論到某一種機制的時候,再具體詳述其適用場景。
二、臨界區( Critical Section )
(本節內容適用于 WinCE 1.0 及以上版本 )
WinCE 實現了操作系統理論里的臨界區管理。臨界區內含有對臨街資源的訪問。通過對臨界區進行有效管理,使得某一時刻最多只能有一個線程進入臨界區,實現對臨界資源的保護。
考慮下面用臨界區實現兩個線程對臨界資源互斥訪問的情形。 The 1st Thread 和 The 2nd Thread 都要調用 Func_CriticalSection() 函數,而 Func_CriticalSection() 內部會對某一臨界資源進行操作,為了保護這一臨界資源,我們用一個 WinCE 的 CriticalSection 來實現。
圖一是該解決方案的一個場景。 The 1st Thread 和 The 2nd Thread 進入臨界區之前已經創建( new )并初始化( InitializeCriticalSection() )了一個臨界區。試圖進入該臨界區的線程首先必須獲得進入該臨界區(通過 EnterCriticalSection() / TryEnterCriticalSection() )的資格 ,如果臨界區內沒有線程,它就能進入,否則必須被掛起等待。進入臨界區的線程可以對臨界資源進行操作( OpOnSharedResources() )。操作完成之后退出臨界區( LeaveCriticalSection () ),以允許其它線程進入。圖一中第一個線程進入臨界區還未退出之前,第二個線程因執行 EnterCriticalSection() 而 一直在被掛起等待,第一個線程退出臨界區之后,第二個線程從等待中被喚醒,按照相應的調度機制重新競爭獲得 CPU ,從而繼續執行,完成臨界區內的操作。
?
圖一、應用臨界區( CriticalSection )實現同步
利用臨界區可以實現對臨界資源的互斥操作, WinCE 的臨界區應用在同一進程內,亦即實現的是同一進程內的線程間同步,不能應用在進程之間。
三、互斥體( Mutex )
(本節內容適用于 WinCE 1.01 及以上版本 )
互斥體( Mutex )顧名思義就是實現對共享資源實現互斥訪問的。 WinCE 中的互斥體的使用規則如下(按線程之間的同步為例):
◇ ? 互斥體可以是匿名互斥體也可以是命名互斥體;
◇ ? 線程創建互斥體的時候可以指定創建完畢它是否就立即擁有該互斥體;
◇ ? 某一時刻最多只有一個線程擁有給定的互斥體;
◇ ? 擁有互斥體的線程可多次獲得該互斥體;
◇ ? 線程可用 CreateMutex 或 wait函數 來獲得互斥體。
看下面應用互斥體的情景。 Thread1 創建并擁有了一個互斥體 g_hMutex[ 序列 1&2] 。互斥體 g_hMutex 是定義的全局量, thread2 可訪問到, Thread2 用 WaitForSingleObject() 試圖獲得該互斥體,因為此時 g_hMutex 是被 Thread1 擁有的,所以 Thread2 被掛起 [ 序列 3] 。 Thread1 執行了一些操作之后,又用 wait 函數試圖再次獲得了該互斥體,因為此時 g_hMutex 的擁有者還是 Thread1 ,所以 Thread1 立即再次獲得了該互斥體 [ 序列 4-6] 。 Thread1 對互斥體 g_hMutex 保護的共享資源操作完畢,釋放該互斥體 [ 序列 7] ,但是因為 Thread1 兩次獲得了 g_hMutex ,所以 g_hMutex 的擁有權并沒有交出。等到 Thread1 再次釋放互斥體 g_hMutex[ 序列 8] 之后, Thread1 才失去了 g_hMutex 的擁有權, Thread2 可競爭 g_hMutex 的擁有權,如能成功擁有,就可從等待狀態被喚醒,完成對共享資源的訪問操作。
?
圖二、應用互斥體( Mutex )實現同步
不 知道從上面的描述,讀者有又沒有看出互斥體與臨界區之間的區別。使用上,它們都實現的對共享資源的互斥訪問,但是臨界區是多個線程對同一段程序的執行,這 段程序會訪問到臨界資源,所以它們是同一個進程內的多個線程;而互斥體的應用情景是在線程之間獨立執行,可以不是程序上的重疊,只是一個線程執行到共享資 源的時候,有可能別的線程也要訪問該共享資源,所以要用互斥體來保護該共享資源。
由于互斥體上述的應用范圍,它不但能應用在同一進程內的線程之間,也能應用在進程之間。進程之間可以通過命名互斥體來實現。一個進程通過為 CreateMutex() 指定一個名字做參數來獲得已經存在的互斥體的句柄,處理過程如下面程序所示。
? HANDLE? hMutex;
? hMutex = CreateMutex (
??????????????? NULL,?????????????????????? //
??????????????? FALSE,????????????????????? // Mutex object NOT initially owned
??????????????? TEXT("NameOfMutexObject")); // Muetx Name
? if (NULL == hMutex)
? {
??? // Something wrong, deal with it here.
? }
? else
? {
??? if ( ERROR_ALREADY_EXISTS == GetLastError () )
??? {
????? // CreateMutex() opened existing mutex."
????? // ...
??? }
??? else
??? {
????? // CreateMutex() created new mutex."
????? // ...
??? }
? }
進程獲得已經存在的互斥體的句柄之后,就可以如線程之間同步規則那樣來實現進程之間的互斥體使用。
四、信號量( Semaphore )
(本節內容適用于 WinCE 3.0 及以上版本 )
信號量實體有一個數值指示當前該信號量使用情況,當前值的大小處于零和最大值之間。用下列操作原語實現信號量的同步操作(用線程間同步來說明):
◇ P ( S, num ) :如果信號量當前值減去 num 大于零,執行該操作的線程獲得信號量,可繼續執行,同時信號量的當前值減小 num ;否則訪問線程被掛起等待
◇ V ( S, num ) :信號量的當前值增加 num (增加之后仍不大于最大值),如果有等待該信號量的線程被掛起,喚醒等待線程并按照相應的調度機制參與調度。
信號量一般用來控制某類共享資源,最大值標識該類資源的數目,執行 P 操作是申請一定數目這類資源, V 操作是釋放一定數目的這類資源。在 WinCE 的信號量實現中,并未實現 OpenSemaphore , P 操作是用 wait函數 來實現的,而 V 操作由 ReleaseSemaphore 來實現。
看下面用信號量來控制數量為 2 的某類共享資源的使用情景。
?
圖三、用信號量( Semaphore )實現同步
Thread1 創建一個控制 2 個共享資源的信號量 [ 序列 1&2] ,并且自己用 WaitForSingleObject() 來申請一個資源,因為當前可用的這類資源有 2 個,所以它就獲得了其中的一個 [ 序列 3&4] 。同樣地, Thread2 獲得了另外一個資源 [ 序列 5&6] 。但是當 Thread3 也申請這類資源的時候,因為此時已經沒有這類資源,信號量的值為零,它就被掛起 [ 序列 7] 。擁有這類資源的線程釋放掉一個資源 [ 序列 8&9] ,并且滿足能滿足 Thread3 申請資源數目的要求, Thread3 競爭獲得了該資源 [ 序列 10] 。
信號量是實現同步的基本方法,在幾乎所有的多任務操作系統里面都做了信號量的實現,其它一些同步機制其實可以通過信號量來實現。如果把信號量的最大值和初始值均設置為 1 ,那么它就可實現互斥體 ,即保證對共享資源互斥訪問的保護。如果把信號量的初始值設置為 0 ,等待別的線程 ReleaseSemaphore 來喚醒它,那么它就可實現事件( Event ) 機制。
信號量機制可以用在同一進程內的線程之間同步,也可以用在進程之間的同步。進程間同步的實現方法如同互斥體的此類實現。
五、事件( Event )
(本節內容適用于 WinCE 1.0 及以上版本 )
WinCE 系統中廣泛用到事件( Event )機制來實現線程之間的協調工作,具體表現在:
◇ 通知一個線程什么時候去執行它的特定的任務
◇ 標識事件的發生
WinCE 中的線程操作原語有 CreateEvent() , SetEvent()/PulseEvent() , ResetEvent() 等。創建 Event 的時候在 CreateEvent() 的參數中指定 Event 的初始狀態(觸發的 / 未觸發的),還要指定事件是否手動復位(手動復位是只有用 ResetEvent() 才能把事件狀態顯式地設置為未觸發的,自動復位是等待該事件的線程等待事件到來之后,系統自動把該事件的狀態復位為未觸發的)。線程等待事件仍然用 wait函數 。
下面是使用 Event 同步的簡單情況:
?
圖四、用事件( Event )實現同步
線程 Thread1 執行過程中,要等到某個條件滿足(事件觸發),所以它創建了一個事件 Event (參數設置為:手動復位,初始條件為未觸發的),用 WaitForSingleObject() 來等待這個事件。線程 Thread2 執行了一些操作之后,滿足了 Thread1 的條件,用 SetEvent 來觸發該事件。
?????? 除了可以用 SetEvent() 來觸發事件之外,也可以用 PulseEvent() 來觸發,區別是 PulseEvent() 觸發該事件之后把它又復位。
?????? 另外,也可以把命名事件用于進程之間的同步。實現方法同互斥體中的描述。
六、消息隊列( MsgQueue P2P )
(本節內容適用于 WinCE.net 4.0 及以上版本 )
消息隊列通信機制如同建立了一個管道,管道的雙方通過分別建立到管道的兩端,與管道的讀端口建立連接的進程可以從該端口讀取消息( Message ),與管道的寫端口建立連接的進程可以寫入消息( Message )到管道。管道內消息組成了一個 FIFO ( F irst I n F irst O ut )的隊列,從讀端口讀取消息是讀取隊列的頭,寫入消息到寫端口是在隊列尾部追加一個消息。
WinCE 中關于 MsgQueue 的操作函數主要有:
◇ CreateMsgQueue() 創建一個消息隊列。在該函數的參數中指定消息隊列的名字,消息隊列的最大數目,每個消息的最大長度,對該消息隊列可進行讀還是寫操作等。因為調用一次 CreateMsgQueue 函數,只能指定讀或者寫這樣的二選一的消息隊列,所以一般需要用相同的消息隊列名字做參數兩次調用該函數,分別創建讀消息隊列和寫消息隊列,它們的返回值分別被讀進程和寫進程用 OpenMsgQueue() 打開用于讀取消息和寫入消息。
◇ OpenMsgQueue() 打開消息隊列并建立與相應端口的連接。進程與讀端口建立連接之后,可用返回的句柄從消息隊列中讀取消息;進程與寫端口建立連接之后,可用返回的句柄寫入消息到消息隊列中。
◇ CloseMsgQueue() 斷開與消息隊列相應的端口之間的連接,并關閉由 CreateMsgQueue() 或 OpenMsgQueue() 創建或打開的消息隊列。
◇ ReadMsgQueue() 如同從普通文件中讀取數據一樣,用于從消息隊列中讀取消息。可以指定讀取消息時,如果消息隊列為空,讀進程是被掛起還是直接返回。
◇ WriteMsgQueue() 如同寫數據到普通文件中一樣,用于寫消息到消息隊列中。可以指定寫入消息時,如果消息隊列已滿,寫進程是被掛起還是直接返回。
下圖是 MsgQueue 應用的典型場景。
?
圖五、用消息隊列( MsgQueue )實現同步
這種場景下的執行過程為:
◇ 主進程 MainProcess 創建了名為“ Reader/Writer MsgQueue ”的讀和寫的消息隊列,并分別返回 hMsgQ_r_m 和 hMsgQ_w_m[ 序列 1-4] 。
◇ 讀進程 ReaderProcess 以主進程的 ProcessId 和 hMsgQ_r_m 為參數,通過 OpenMsgQueue() 與 MainProcess 消息隊列的讀端口建立連接 [ 序列 5&6] 。
◇ ReaderProcess 與消息隊列建立連接之后,用 WaitForSingleOnject(hMsg_r) 看消息隊列中是否有消息,因為此時消息隊列為空,所以 ReaderProcess 被掛起 [ 序列 7] 。
◇ 寫進程 WriterProcess 以主進程的 ProcessId 和 hMsgQ_w_m 為參數,通過 OpenMsgQueue() 與 MainProcess 消息隊列的寫端口建立連接 [ 序列 8&9] 。
◇ WriterProcess 與消息隊列建立連接之后,用 WaitForSingleOnject(hMsg_w) 看消息隊列中消息是否滿,因為此時消息隊列為空,未滿,所以 WriterProcess 不會被掛起 [ 序列 10&11] 。
◇ WriterProcess 寫消息到消息隊列中 [ 序列 12&13] 。
◇ 因為消息隊列中已經有了消息, ReaderProcess 從掛起狀態被喚醒 [ 序列 14] 。
◇ ReaderProcess 繼續執行,從消息隊列中讀取 WriterProcess 剛才寫入的消息。
消息隊列除可用于同步之外,主要用于進程之間的數據傳遞,另外消息隊列也可以用于同一進程中的線程之間同步,但是既然線程之間能直接傳遞數據,又何必那么麻煩呢。
七、互鎖函數( Interlocked Function )
(本節內容適用于 WinCE 1.0 及以上版本)
除了上面各節的同步方法之外, WinCE 還提供了一些用于原子操作的互鎖函數,這些函數在執行過程中,不會因為線程的調度引起的當前線程被搶占而打斷函數內的操作。
這些函數主要有:
InterlockedIncrement
InterlockedDecrement
InterlockedExchange
InterlockedTestExchange
InterlockedCompareExchange
InterlockedCompareExchangePointer
InterlockedExchangePointer
InterlockedExchangeAdd
八、 Wait 函數
(本節內容適用于 WinCE 1.0 及以上版本 )
Wait 函數不是特指的某一個函數,而是指 wait 的系列函數。 wait 函數并不是 WinCE 同步機制中的一種,但是 WinCE 的很多同步機制要用到 wait 函數,這些在前面講述各個同步方法的時候也已有論述。
一般地,執行 wait 函數時,如果等待的同步對象條件不滿足,那么執行 wait 函數的進程 / 線程會被掛起,當然也可以給它們設置等待的超時時間,超過給定時間,不管條件是否滿足,它們會自動從等待狀態蘇醒。等待既可以等待某一個條件,也可以等待多個條件中的一個, WinCE 不支持等待多個條件同時滿足,如果有這種需要,要自己實現。
Wait 函數原型如下:
DWORD WaitForSingleObject (HANDLE hHandle, DWORD dwMilliseconds );
DWORD WaitForMultipleObjects (
? DWORD nCount,????????????? // No. of object handles in the array.
? CONST HANDLE* lpHandles,?? // Pointer to an array of object handles.
? BOOL fWaitAll,???????????? // MUST be FALSE in WinCE
? DWORD dwMilliseconds?????? // Timeout (0, mills, or INFINITE)
);
DWORD MsgWaitForMultipleObjects (
? DWORD nCount,???????????? // No. of object handles in the array.
? LPHANDLE pHandles,??????? // Pointer to an array of object handles.
? BOOL fWaitAll,??????????? // MUST be FALSE in WinCE
? DWORD dwMilliseconds,???? // Timeout (0, mills, or INFINITE)
? DWORD dwWakeMask????????? // Input types for which an input event object handle
);
前面講述各種同步機制的時候都是以 WaitForSingleObject() 來說明的,這里就不再贅述它了。
WaitForMultipleObjects() 和 MsgWaitForMultipleObjects() 可以用來等多個同步對象,它們之間的區別就是 MsgWaitForMultipleObjects() 還等待 dwWakeMask 參數中指定的輸入事件,即這些事件發生時,等待的進程 / 線程也能被喚醒。
用 WaitForMultipleObjects() 等待的多個同步對象的句柄放在參數 lpHandles 數組中,同步對象的句柄的數目放在參數 nCount 中。 dwMilliseconds 指定了等待的超時參數:如果指定為 0 ,該函數等待每個同步對象之后,不管觸發與否都直接返回;如果指定為 INFINITE ,該函數等待每個同步對象,直到有一個同步對象被觸發,否則執行該函數的運行實體將一直被掛起;如果指定為非 0 ,非 INFINITE 的一個數值,那么不管 等待的同步對象是否被觸發,到了指定的時間,執行該函數而被掛起的運行實體也會被喚醒。因哪個同步對象被觸發而返回還是因超時而返回,可以從返回值中來判定,返回值為 WAIT_TIMEOUT ,是因為超時; 返回值為 WAIT_OBJECT_0 到 WAIT_OBJECT_0 + nCount -1 之間的數時,可以按順序找到具體那個同步對象被觸發 。
下面是 WaitForMultipleObjects 的典型應用。
? HANDLE hSynchObjects[EVENT_COUNT];
? DWORD dwEvent;
? /* Put event handles in hEvents */
? // ...
?
? dwEvent = WaitForMultipleObjects (
????????????????????? EVENT_COUNT,??????? // Number of objects in an array
????????????????????? hSynchObjects,????? // Array of objects
????????????????????? FALSE,????????????? // MUST be FALSE
????????????????????? 500);?????????????? // timeout, 0.5s
? switch (dwEvent)
? {
??? case WAIT_TIMEOUT:
????? // Handle for timeout
????? break;
?????
??? case WAIT_OBJECT_0 + 0:
????? // Handle the 1st event
????? break;
?????
??? case WAIT_OBJECT_0 + 1:
????? // Handle the 2nd one
????? break;
??? ...
???
??? case WAIT_OBJECT_0 + EVENT_COUNT -1:
????? // Handle the final one
????? break;
??? default:
????? // Error: Not an anticipant one, handle it.
????? break;
? }
總結
本文探討了 WinCE 中的各種同步機制的用法,并給出了它們的典型應用場景。關于它們進一步的高級話題,將在后續文章中探討。
參考資料以及進一步閱讀
1) MSDN
2) UML Reference Manual, 2nd Edition
3 ) Abraham Silberschatz, Peter Baer Galvin, Greg Gagne. Operating System Concepts, 6th Edition. John Wiley & Sons, Inc/ 高等教育出版社影印 , 2002.5
4 ) David R. Butenhof/ 于磊,曾剛 . Programming with POSIX Threads. Addison Wesley/ 中國電力出版社 , 2003
?
總結
以上是生活随笔為你收集整理的Windows® CE 系统中的同步机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 人民币贬值是好事还是坏事,适度贬值不是坏
- 下一篇: Waveform Audio 驱动(Wa