這個章節将詳細介紹Windows CE 系統中的程序 (process) 和線程 (thread),并對Windows CE作業系統所使用的排程政策進行分析。程序是資源配置設定的基本機關,而線程是排程的基本機關。這一章的程式代碼主要節選于 [CEROOT]PRIVATE/WINCEOS/COREOS/NK/KERNEL/ 目錄的 schedule.c、intrapi.c 以及 [CEROOT]PRIVATE/WINCEOS/COREOS/NK/INC 目錄的 schedule.c、kernel.h的幾個檔案,其目的在于了解程式在開發執行時,對系統資源的共享以及程式的排程。
3.1 程序的定義和描述
3.1.1 程序概述
程序是一個具有一定獨立功能之程式的動态執行過程。程序由正文段 (text)、使用者資料段 (user segment) 以及系統資料段 (system segment) 共同組成一個執行環境,負責處理器、記憶體和外圍等資源的配置設定和回收。程序是計算機系統資源的使用主體,是作業系統配置設定資源的基本機關。
程序具有動态性、獨立性、并行性和結構化等特征。動态性是指程序具有動态的位址空間,位址空間的大小和内容都是動态變化的。位址空間的内容包括程式代碼 (指令執行和處理器狀态的改變)、資料 (變量的生成和初始化) 和系統控制資訊 (PCB (Process Control Block) 的生成和删除)。獨立性是指各個程序的位址空間互相獨立,除非采用程序間通信服務,否則不能互相影響。并行性也稱為異步性,是指從宏觀上來看,各個程序是同時獨立運作的。結構化是指程序對于位址空間的結構劃分,如程式代碼段、資料段和核心段劃分。
我們必須了解程序和程式的差別,程式是一個普通檔案,是一個程式代碼指令和資料的集合,這些指令和程式代碼儲存在磁盤上成為可執行映像 (Executable Image),是一個靜态的實體。我們可以用下面簡單的方式了解程序和程式的關系:
1. 程序和程式的關系
程式是程序的兩個重要組成之一。程序的主要目的是執行它所對應的程式。
2. 程序和程式的差別
主要有以下三種:
l 程式是靜态的,程序是動态的;
l 程式可以在儲存裝置 (如:磁盤) 上長期儲存,而程序則是在 建立程序後産生,結束程序後消失。
l 一個程式可以對應多個程序,但是一個程序隻能對應一個程式。例如:打開Word的兩個視窗,編輯兩個不同的文字檔案,就對應到兩個不同的程序。
3.1.2 Windows CE程序的描述
Windows CE的程序不同于Windows 98或Windows NT,最大差别在于 Windows CE最多隻可以支援32個程序在系統中同時運作,系統啟動的時候,将至少自動啟動四個程序,一個是NK.exe,用來提供作業系統中kernel的服務,第二個是FILESYS.EXE,它用來提供相關檔案系統的服務,第三個是GWES.EXE,它用來提供對GUI系統的支援,第四個是DEVICE.EXE,它用來加載和管理外圍的驅動程式。他們占據虛拟位址的前四個slots,一個slot有32MB空間,詳見資料儲存部分的介紹,目前執行的程序将會對應到第一個slot (slot 0)。大部分的Windows CE系統,也會同時建立EXEPLORER.EXE程序﹔如果Windows CE系統正在與個人計算機相連,則會啟動REPLLOG.EXE和PAPISRV.EXE,他們用來管理個人計算機和Windows CE系統之間的連接配接服務。是以使用者可以啟動的程序最多大概有24個,或稍微多一點,但是對一般的使用來說,這是足夠的。
不同于Windows 98或Windows NT系統,Windows CE系統不支援一些功能,例如Windows CE系統不支援許多程序和與線程相關的函數。Windows CE系統不支援環境 (environment),所有與處理環境有關的Win32函數在Windows CE系統中并不存在。
3.1.3 Windows CE程序結構分析
在Windows CE中,每一個程序由一個程式結構來描述。也就是我們平時說的PCB。它定義于NK/INC/kernel.h。程序的所有資訊都儲存在這個結構中,當系統建立一個程序時,将配置設定一個新的程式結構,程序結束時,這個結構将被回收。
與Windows 98或Windows NT的程序相比較,Windows CE程序包含比較少的狀态資訊。由于Windows CE不支援驅動程式及工作目錄 (Working Directory) 的概念,是以每個程序不需要儲存這些資訊。Windows CE也不需要儲存一系列的環境變量,是以PCB中不需要有關于環境變量的部分。Windows CE不支援句柄繼承,是以也不需要告訴程序這些相關的資訊。由于以上種種原因,Windows CE程序的結構相對地簡單很多。
程序是系統資源配置設定的基本機關,為友善管理,在Windows CE中把程序當作對象 (HANDLE hProc)。下面将簡單介紹一個程式結構的主要部分:
l procnum:BYTE類别,目前程序的識别号碼 (ID),用來辨識不同的程序。
l pProxList:存放proxy的隊列,LPPROXY結構的連結。
l hProc:這是此程序的句柄,在呼叫SC_GetProcFromPtr時使用。
l dwVMBase:DWORD類别,記錄程序在記憶體所占區域中的基位址。
l pTh:一個程序可能擁有多個線程 (詳見線程介紹部分), pTh表示目前程序中的第一個線程。
l BasePtr:LPVOID類别,指向載入.EXE可執行檔的基底名額。
l lpszProcName:LPWSTR類别,記錄程序的名稱。
l PfnEH:程序例外處理器,PEXCEPTION_ROUTINE類别。
l pMainTh:此程序所擁有的主線程,當主線程結束後,程序也随之結束。
l pmodResource:PMODULE類别,MODULE結構在NK/INC/kernel.h中所定義。包含資源的子產品指針,其中的資源可以被目前的程序用到。
l oe:openexe_t類别。指向可執行檔句柄的名額。
為了讓讀者容易了解,下面列出Windows CE中所定義的程式結構。
程式的結構如下:
程式代碼3.1
struct Process { BYTE procnum; BYTE DbgActive; BYTE bChainDebug BYTE bTrustLevel; #define OFFSET_TRUSTLVL 3 // offset of the bTrustLevel member in Process structure LPPROXY pProxList; HANDLE hProc; DWORD dwVMBase; PTHREAD pTh; ACCESSKEY aky; LPVOID BasePtr; HANDLE hDbgrThrd; LPWSTR lpszProcName; DWORD tlsLowUsed; DWORD tlsHighUsed; PEXCEPTION_ROUTINE pfnEH; LPDBGPARAM ZonePtr; PTHREAD pMainTh; PMODULE pmodResource; LPName pStdNames[3]; LPCWSTR pcmdline; DWORD dwDyingThreads; openexe_t oe; e32_lite e32; o32_lite *o32_ptr; LPVOID pExtPdata; BYTE bPrio; BYTE fNoDebug; WORD wPad; }; |
3.1.4 程序的同步與互斥
Windows CE在 NK/INC/schedule.h中定義了event (相當于觸發器,可以用于通知個别或多個線程某個event的出現)、mutex與 semaphore三種同步對象。而在NK/kernel.c中定義了這些同步對象的系統呼叫,可以用于程序或者線程的同步和互斥。
具體關于semaphore、mutex、event的機制不再介紹,這跟在Windows 2000中的semaphore、mutex、event有一定的相同之處,讀者可以參考Windows 2000中的對應結構以獲得更一步的了解,這裡隻介紹相關的API函數。
1. event
event是同步對象之一,用于通知線程事件的發生,有signaled和nonsignaled兩種狀态。建立時可以選擇自動從signaled狀态恢複到nonsignaled狀态,或者手動恢複。從Windows CE 2.0開始,event可以被命名,是以可以被程序用于同步通信的共享。與事件相關的API有:SC_CreateEvent,用于建立一個event并傳回相應的句柄﹔SC_OpenEvent,用于打開一個已經存在的event,并傳回相應的句柄﹔SC_EventCloseHandle,用于關閉一個event等等。下面以其中的部分函數為例,進行簡單的說明。
函數原型:
HANDLE CreateEvent (
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPTSTR lpName
);
要達到程序間的event共享,程序必需各自建立一個相同名字的event,而不能隻在單一程序中建立,再把句柄交給其餘程序使用。要用它來發号志,可以使用以下函數:
BOOL SetEvent (HANDLE hEvent ); //隻釋放一個等待的線程
BOOL PulseEvent (HANDLE hEvent ) ; //釋放所有等待的線程 //釋放所有等待的線程
BOOL ResetEvent (HANDLE hEvent ) ; //手動恢複nonsignal狀态 //手動恢複nonsignal狀态
與mutex的API有:SC_CreateMutex、SC_ReleaseMutex等等,分别用來建立和釋放一個mutex,這裡不再詳細說明,請讀者自己參閱Windows 2000對應函數的說明。
與semaphore有關的API有:SC_CreateSemaphore、SC_ReleaseSemaphore、 SC_SemCloseHandle等等,分别用來建立semaphore,釋放semaphore,以及關閉一個semaphore的句柄時使用,這裡也不詳細說明,請讀者參閱Windows 2000對應函數的說明。
2. 相關的等待函數
DWORD WaitForSingleObject ( HANDLE hHandle,DWORD dwMilliseconds ) ;
作用:線程等待一個event,用于停滞 (Block) 線程直到等待的event發出信号或者預定的時間到達。其回傳值表明了等待結束的原因,回傳值可能如下:
WAIT_OBJECT_0 //指定的event發出信号
WAIT_TIMEOUT //時間到,但等待的event沒有發出信号
WAIT_ABANDONED //被等待的線程結束了,但沒有釋放event
WAIT_FAILED //同步對象的句柄不合法
DWORD WaitForMultipleObjects (DWORD nCount, CONST HANDLE *lpHandles, BOOL bWaitAll,
DWORD dwMilliseconds);
作用:線程等待多個event,Windows CE不允許等待所有event發信号,是以bWaitAll隻能為FALSE,當任何一個event發信号時,函數就傳回。其回傳值與前一函數基本相同,隻是如果是event發出信号而傳回時,回傳值為WAIT_OBJECT_0加上同時發出信号事件的句柄 (handle) 中最小的注标 (即lpHandles發出信号事件中最小的注标)。
3.2 線程介紹
3.2.1 線程概述
從60年代程序的概念提出以來,程序就一直是作業系統獨立運作的基本機關,在多個程序同時執行時,程序切換的系統負荷卻非常的大,在此同時,程序間資料共享的效率亦受到高度重視。80年代中,人們提出了比程序更小、且能獨立運作的基本機關——線程 (thread),目的是用來減少程式同時執行時所需要的事件和空間使用,提高程序的并行性。
1. 線程的概念:
線程是程序的一個實體,是CPU排程和配置設定的基本機關,除了一些在運作中必要的資源 (例如:程式計數器,一些緩存器和堆棧),線程基本上不擁有系統資源,但是線程可以和同屬于同一個程序的其它線程共享程序所擁有的全部資源。一個線程可以建立和撤銷另一個線程,同一個程序内的線程也可以并行執行。
線程具有程序所具有的許多特征,是以又被稱為輕量級程序。通常一個程序都有至少有一個以上的線程 (Windows CE中是主線程)。下面将簡單地比較線程和程序的差别:
l 排程方面:傳統作業系統中,程序是配置設定資源,獨立排程和分派的基本機關。引入線程後,線程是排程和分派的基本機關,程序仍然是擁有資源的基本機關。将原本程序的兩個屬性分開 (為資源配置設定與排程的基本機關),線程成為排程的基本機關後,線程就可以輕裝執行,這樣可以顯著提高系統的并行程度,同一程序内部線程的切換并不需要程序的切換。
l 并行性:引入線程,不僅在程序之間可以并行執行,而且在一個程序中的多個線程之間也可以并行執行,是以作業系統具有更好的并行性,并且能更有效的使用系統資源及提高系統的效率。
l 資源之擁有:程序是一個擁有資源的獨立機關,一般來說,線程不擁有自己的系統資源,它使用其所屬的程序的資源。
系統負荷:由于程序的建立、結束或切換時,系統都要為程序配置設定、回收或暫存資源,付出的代價比較大,而線程的建立、結束及切換所需要和儲存的資源 (如:緩存器) 較少,是以線程之各項系統負荷遠遠小于程序所需。
2. 引入線程的優點:
l 建立一個新線程的負擔比建立程序少得多。建立線程不需要額外配置設定大量資源,是以相對于建立程序,建立線程的速度要快得非常多,而且系統的負擔也非常小。
l 兩個線程之間的切換時間非常的快。
l 在同一個程序内的線程共享程序所擁有的資源 (包括記憶體和檔案等),是以線程之間的資料共享不需要額外的機制﹔簡化的通訊方式,也使資料交換速度得以加快。
l 線程能獨立執行,是以能充分利用和發揮處理器與外圍裝置的并行工作能力。
3.2.2 Windows CE線程的結構分析
在Windows CE中,每一個線程由一個在NK/INC/kernel.h中定義的thread結構所描述。下面對線程的資料結構進行大緻的分類:
1. wInfo資訊:
它是一個16位的無符号整數 (unsigned integer),包括下列資訊 (見表3.1):
表3.1 wInfo資訊
名稱 | 說明 |
TIMEMODE_SHIFT | 線程所處的時間模式 |
RUNSTATE_SHIFT | 線程是否可執行 |
BURIED_SHIFT | 隐藏偏移 |
SLEEPING_SHIFT | 線程是否在睡眠 |
DEBUGBLK_SHIFT | 是否啟動偵錯,暫停了線程 |
DYING_SHIFT | 是否被設定為結束 |
STACKFAULT_SHIFT | 堆棧錯誤資訊 |
DEAD_SHIF | 線程有關結束的資訊 |
NEEDDBG_SHIFT | 線程是否要偵錯 |
PROFILE_SHIFT | Montecarlo profiling |
NOPRIOCALC_SHIFT | 非優先級計算位 |
DEBUGWAIT_SHIFT | 線程偵錯等待資訊 |
USERBLOCK_SHIFT | 線程是否可以自動進入停滞狀态 |
NEEDSLEEP_SHIFT | 線程是否應該放回睡眠隊列中 |
我們可以透過在kernel.h中定義的偏移量來獲得以上的資訊:
#define RUNSTATE_SHIFT 0 // 2位 ( bit )
#define DYING_SHIFT 2 // 1位
#define DEAD_SHIFT 3 // 1位
#define BURIED_SHIFT 4 // 1位
#define SLEEPING_SHIFT 5 // 1位
#define TIMEMODE_SHIFT 6 // 1位
#define NEEDDBG_SHIFT 7 // 1位
#define STACKFAULT_SHIFT 8 // 1位
#define DEBUGBLK_SHIFT 9 // 1位
#define NOPRIOCALC_SHIFT 10 // 1位
#define DEBUGWAIT_SHIFT 11 // 1位
#define USERBLOCK_SHIFT 12 // 1位
#ifdef DEBUG
#define DEBUG_LOOPCNT_SHIFT 13 // 1位 (僅在偵錯時使用)
#endif
#define NEEDSLEEP_SHIFT 14 // 1位
#define PROFILE_SHIFT 15 // 1位, 必須是15,透過彙編語言來使用
2. 各種連結資訊
一個線程屬于某個程序,一個程序中可能同時有多個線程,排程以線程為基本機關,某個線程可能正在執行,可能處于可執行隊列,也可能處于睡眠隊列,是以需要各種連結資訊來聯系不同的線程,線程的一些主要連接配接資訊如表3.2所示:
表3.2 線程的一些主要連接配接資訊
名稱 | 描述 |
PnextInProc | 指向目前程序的下一個線程 |
Pproc | 指向目前程序的指針 |
PprevInProc | 目前程序的上一個線程 |
POwnerProc | 指向父程序的指針 |
pNextSleepRun | 下一個睡眠的線程,或者是下一個可執行的線程 |
pPrevSleepRun | 上一個睡眠的或可執行的線程 |
pUpRun; | 指向上一個線程,它執行完後自己才可以執行 |
pDownRun | 指向等待此線程執行完後才可執行的線程 |
pUpSleep | 指向上一個線程,它執行完後自己才被喚醒 |
pDownSleep | 指向等待此線程執行完後才喚醒的線程 |
3. 線程排程資訊
系統透過排程資訊來決定讓那一個線程運作,這主要由線程的目前優先級 (bCPrio) 決定,而透過線程的時間片段 (dwQuantum) 資訊,系統可以決定讓線程執行多長的時間。由于使用preemptive round-robin排程算法,是以一個正在執行的線程被preempted後,它所剩下的時間資訊也要保留。
表3.3 線程資料結構中的一些排程相關資訊
名稱 | 含義 |
bBPrio | 基本的優先級 |
bCPrio | 目前優先級 |
dwQuantum | 線程所擁有的時間片段 |
dwQuantLeft | 線程所剩下的時間片段 |
4. 線程時間資訊
線程從建立到結束間有一個生命周期,kernel需要記錄并統計線程所耗費的CPU時間。這個時間分為user-mode時間和kernel-mode時間,每個時間中斷,kernel都要更新先前正在執行的線程所耗費的時間資訊。除了這些,還有一些其它的相關資訊,表3.4中列出了一些比較重要的時間資訊:
表3.4 線程時間資訊
名稱 | 描述 |
ftCreate | 線程建立的時間 |
dwKernTime | 線程在kernel-mode所消耗的時間 |
dwUserTime | 線程在user-mode所消耗的時間 |
dwPendTime | 線程等待操作的最長等待時間 |
dwPendWakeup | 線程等待的最長時間 |
5. 一些有關計數的資訊
wCount用于停滞線程隊列的計數,wCount2用于睡眠隊列的計數。
WORD wCount; 停滞隊列的目前計數
WORD wCount2; 睡眠隊列的目前計數
WORD wCrabCount;
6. 有關Windows CE排程
Windows CE是基于preeemptive的time-sliced round-robin來進行排程,系統可能要在同的線程之間切換,是以切換時必須儲存線程的上下文 (context) 資訊。
CPUCONTEXT ctx;
為友善讀者了解,下面列出Windows CE中所定義的Thread結構:
程式代碼3.2
struct Thread { WORD wInfo; BYTE bSuspendCnt; BYTE bWaitState; LPPROXY pProxList; PTHREAD pNextInProc; PPROCESS pProc; PPROCESS pOwnerProc; ACCESSKEY aky; PCALLSTACK pcstkTop; DWORD dwOrigBase; DWORD dwOrigStkSize; LPDWORD tlsPtr; DWORD dwWakeupTime; LPDWORD tlsSecure; LPDWORD tlsNonSecure; LPPROXY lpProxy; DWORD dwLastError; HANDLE hTh; BYTE bBPrio; BYTE bCPrio; WORD wCount; PTHREAD pPrevInProc; LPTHRDDBG pThrdDbg; LPBYTE pSwapStack; FILETIME ftCreate; CLEANEVENT *lpce; DWORD dwStartAddr; CPUCONTEXT ctx; PTHREAD pNextSleepRun; PTHREAD pPrevSleepRun; PTHREAD pUpRun; PTHREAD pDownRun; PTHREAD pUpSleep; PTHREAD pDownSleep; LPCRIT pOwnedList; LPCRIT pOwnedHash[PRIORITY_LEVELS_HASHSIZE]; DWORD dwQuantum; DWORD dwQuantLeft; LPPROXY lpCritProxy; LPPROXY lpPendProxy; DWORD dwPendReturn; DWORD dwPendTime; PTHREAD pCrabPth; WORD wCrabCount; WORD wCrabDir; DWORD dwPendWakeup; WORD wCount2; BYTE bPendSusp; BYTE bDbgCnt; HANDLE hLastCrit; DWORD dwCrabTime; CALLSTACK IntrStk; DWORD dwKernTime; DWORD dwUserTime; }; |
3.3 其它一些重要的資料結構
在schedule.h和schedule.c中還有一些重要的資料結構,這些資料結構在排程過程中有着非常大的影響,下面列出幾個主要的結構。
l schedule.h中有關于線程時間的資料結構:
typedef struct THREADTIME { struct THREADTIME *pnext; HANDLE hTh; FILETIME CreationTime; FILETIME ExitTime; FILETIME KernelTime; FILETIME UserTime; } THREADTIME, *LPTHREADTIME; |
|
l 有關臨界區的資料結構:
typedef struct CRIT { LPCRITICAL_SECTION lpcs; LPPROXY pProxList; LPPROXY pProxHash[PRIORITY_LEVELS_HASHSIZE]; LPCRIT pPrev; BYTE bListed; BYTE bListedPrio; BYTE iOwnerProc; BYTE bPad; struct CRIT * pPrevOwned; struct CRIT * pNextOwned; struct CRIT * pUpOwned; struct CRIT * pDownOwned; LPCRIT pNext; } CRIT; |
l 有關可執行線程隊列的資料結構:
typedef struct { PTHREAD pRunnable; PTHREAD pth; PTHREAD pHashThread[PRIORITY_LEVELS_HASHSIZE]; } RunList_t; |
pRunnable 是指向可執行隊列的指針,這個隊列是由雙向連結串行 (doubly-linked list) 組成的。PRIORITY_LEVELS_HASHSIZE 的值是 256,是以,pHashTable 共有 256 個entry,每一個entry各指向一個優先權等級大小不同的線程隊列。
l 有關線程等待隊列的資料結構:
typedef struct sleeper_t { PTHREAD pth; WORD wCount2; WORD wDirection; DWORD dwWakeupTime; } sleeper_t; |
l 結束線程(void RemoveThread(RTs *pRTs)函數)相關結構:
typedef struct RTs { PTHREAD pHelper; DWORD dwBase, dwLen; DWORD dwOrigBase; PPROCESS pProc; LPTHRDDBG pThrdDbg; HANDLE hThread; PTHREAD pThread; CLEANEVENT *lpce1; CLEANEVENT *lpce2; CLEANEVENT *lpce3; LPDWORD pdwDying; } RTs; | //如果要釋放堆棧,将用到此資訊 //如果在fiber堆棧中,則需要釋放的初始基址資訊 //如果要釋放程序,将用到此資訊 //如果要釋放一個偵錯結構,将用到此資訊 //如果要釋放一個句柄或線程時間,将用到此資訊 //如果要釋放一個線程結構,将用到此資訊 |
3.4 Windows CE中的排程
3.4.1 Windows CE排程的簡介
Windows CE是一個preemptive的多任務作業系統,它采用以優先權順序為主的時間片段循環方法 (time-sliced round-robin)。一般來說線程是排程的基本機關,線程可執行一個固定的時間片段,在Windows CE系統中有許多隊列,分别對應不同目錄的程序。一個程序在同一個時間隻能在一個隊列中,而目前正在執行的程序 (如果有的話) 則例外。可執行的線程處于一個可執行隊列之中 (對每一種優先級,都對應一個可執行隊列,最多可有256個可執行隊列)。其它隊列包括等待隊列等等,目前執行的線程之時間片段用完後,如果這個線程不是時間關鍵 (time-critical) 的話,線程排程政策會把它排到相同優先級的執行隊列的末尾,然後再讓等待隊列中優先級最高的線程執行,此線程的優先級應該和剛剛用完時間片段的線程具有相同的優先級。否則,如果等待隊列中之線程優先級較高,它會搶占前一個執行的線程,如果其優先級較低,前一個用完時間片段的線程會繼續執行。在Windows CE系統中,一般設定的時間片段大小為100ms (當然這個時間片段可以藉由OEM廠商所開發的不同硬體來設定)。也就是說,Windows CE以優先權順序選擇線程來執行,擁有最高優先級的線程将在低優先級的線程前面執行。基本排程情況如圖3.1所示。
圖3.1 Windows CE的排程機制
3.4.2 線程排程的時機
線程排程的時機如下:
n 線程狀态改變
n 可執行隊列的前面插入了一個線程
n 時間片段用完或被preempted
n 中斷處理完後。
線程狀态切換方式如下:線程執行完後,線程從就緒态轉入睡眠态,或者線程從睡眠态轉換到就緒态,分别藉由執行RemoveThread()、RunqDequeue()、NextThread()來呼叫MakeRun(),在這個函數中會呼叫相應的程式代碼。當可執行隊列前面插入了一個線程時,MakeRun()會進行判斷,一個線程要侵占目前執行的線程,也會呼叫類似上述的MakeRun()函數,時間片段的配置設定與中斷相關,是以這也跟中斷處理完後的情況相同,在KCNextThread(void)函數中,将判斷時間片段使用的情況。
借着Reschedule來判斷是否關閉電源,還是執行目前線程相關的下一個線程,或者是直接執行一個新的線程。如果沒有線程可以執行,那麼将呼叫一個OEMIdle函數使CPU閑置。如下面程式代碼所示 (在NK/KERNEL/x86的fault.c中):
程式代碼3.3
Naked Reschedule() { __asm { test [KData].bPowerOff, 0FFh jz short rsd10 mov [KData].bPowerOff, 0 call DoPowerOff rsd10: sti cmp word ptr ([KData].bResched), 1 jne short rsd11 mov word ptr ([KData].bResched), 0 call NextThread rsd11: cmp dword ptr ([KData].dwKCRes), 1 jne short rsd12 mov dword ptr ([KData].dwKCRes), 0 call KCNextThread cmp dword ptr ([KData].dwKCRes), 1 je short rsd10 rsd12: mov eax, [RunList.pth] test eax, eax jz short rsd50 cmp eax, edi jne short rsd20 jmp RunThread //轉換到一個新的程序上下文資訊 //.轉換到一個新的線程,更新目前程序和位址空間資訊,在TSS中編輯ring0堆棧指針,使其//指向新的線程的寄存器儲存空間 // (eax) = ptr to thread structure rsd20: mov edi, eax mov esi, (THREAD)[eax].hTh push edi call SetCPUASID pop ecx mov hCurThd, esi mov PtrCurThd, edi mov ecx, [edi].tlsPtr mov [KData].lpvTls, ecx cmp edi, g_CurFPUOwner jne SetTSBit clts jmp MuckWithFSBase SetTSBit: mov eax, CR0 test eax, TS_MASK jnz MuckWithFSBase or eax, TS_MASK mov CR0, eax MuckWithFSBase: mov edx, offset g_aGlobalDescriptorTable+KGDT_PCR sub ecx, FS_LIMIT+1 mov word ptr [edx+2], cx shr ecx, 16 mov byte ptr [edx+4], cl mov byte ptr [edx+7], ch lea ecx, [edi].ctx.TcxSs+4 mov [MainTSS].Esp0, ecx jmp RunThread // 如果沒有線程可以執行,呼叫OEMldle關閉掉CPU rsd50: cli cmp word ptr ([KData].bResched), 1 je short DoReschedule call OEMIdle mov byte ptr ([KData].bResched), 1 jmp Reschedule DoReschedule: sti jmp Reschedule } } | // 是否需要關閉電源 // 是 – 關閉電源 // 沒有可運作的線程 // 再次分發同一個線程 // 儲存線程名額 (Program Counter) // (esi) = 線程句柄 (thread handle) // 設定hCurProc // 清除堆棧 // 設定目前線程句柄 // 設定目前線程名額 // (ecx) = thread local storage ptr // 設定TLS(Thread Local Storage)名額 // (ecx) = ptr to NK_PCR base // 設定FS基址的低端值 // 設定FS基址的三個比特值 // 設定FS基址的高端值 // (ecx) = ptr to end of context save area // 執行edi所指向的線程 |
3.4.3 關于線程的優先級
與Windows 98和Windows NT相比,Windows CE為線程配置設定時間的方法有很大的不同。在Windows NT中,一個程序建立的時候将同時建立一個與優先級有關的類别 (class),線程将從建立它的父程序中的優先級類别 (priority class) 中獲得自己的優先權,具有優先級類别高的程序所建立的線程将具有較高優先權。Windows CE系統沒有類似Windows NT系統的優先級類别,是以所有的程序都是同類的,單個線程可以有不同的優先級,而且線程所在的程序并不影響線程的優先級。
Windows CE中的線程可以粗略的分為執行态 (RUNSTATE_RUNNING—正在執行)、可執行态,睡眠态 (等待态)。其中後面兩種狀态可以細分如下:
l 可執行态:
RUNSTATE_RUNNABLE ‐可以執行
RUNSTATE_BLOCKED ‐可執行态的停滞态,可能是自行進入停滞态
RUNSTATE_NEEDSRUN ‐即将進入可執行狀态
l 停滞态:
WAITSTATE_SIGNALLED ‐等待某個信号的喚醒
WAITSTATE_PROCESSING ‐重新處理等待态
WAITSTATE_BLOCKED ‐等待狀态的停滞态
在Windows CE原始程式代碼NK/INC/schedule.h中,關于優先級的定義如下:
#define MAX_PRIORITY_LEVELS 256
#define MAX_WIN32_PRIORITY_LEVELS 8
這表明有8種不同的優先級别,最多256個優先級,藉由在NK/inc/schedule.h中定義的雜湊值 (hash value) 對應到相應的優先級,一個優先級級别的大小是32。
#define PRIORITY_LEVELS_HASHSIZE 32
#define PRIORITY_LEVELS_HASHSCALE
(MAX_PRIORITY_LEVELS/PRIORITY_LEVELS_HASHSIZE)
數值越低,優先級越高。一個線程可以有任一優先級。線程優先級如表3.5所示:
表3.5 線程的優先等級
優先級 | 描述 |
THREAD_PRIORITY_TIME_CRITICAL | 比普通優先級高三個級别,這種優先級的線程不能被preempted |
THREAD_PRIORITY_HIGHEST | 比普通優先級高兩個級别 |
THREAD_PRIORITY_ABOVE_NORMAL | 比普通優先級高一個級别 |
THREAD_PRIORITY_NORMAL | 普通優先級,所有的線程建立時皆為這個級别 |
THREAD_PRIORITY_BELOW_NORMAL | 比普通優先級低一個級别 |
THREAD_PRIORITY_LOWEST | 比普通優先級低兩個級别 |
THREAD_PRIORITY_ABOVE_IDLE | 比普通優先級低三個級别 |
THREAD_PRIORITY_IDLE | 比普通優先級低四個級别 |
所有的高優先級線程在低優先級線程之前被執行,也就是說:如果某個優先級的線程能夠被排程,那麼所有具有較高優先級的線程必須是在被停滞的狀态。被停滞的線程是指那些等待某種系統資源或同步對象的線程還沒有用完自己所擁有的時間片段,但是因為沒有這些系統資源或同步對象,是以被停滞的線程不能執行。相同優先級的線程采用時間片段循環的方法來排程。一旦一個線程自動放棄它的時間片段,而進入被停滞狀态,或是用完了它的時間片段而換給别的線程執行,所有相同優先級的線程就可以在這個線程繼續執行之前執行。如果一個有較高優先級被停滞線程清除了停滞狀态,而且目前有一個低優先級的線程正在執行,那麼這個低優先級的線程将被preempted,轉而排程這個高優先級的線程,也就是說高優先級的線程搶占了低優先級線程的執行權。上面說的情況有兩個例外,如果一個線程具THREAD_PRIORITY_TIME_CRITICAL優先級,這個有THREAD_PRIORITY_TIME_CRITICAL優先級的線程将使所有的線程等待,直到他執行結束。是以這個優先級一般是被保留,用來在裝置驅動中斷時使用。
還有一種例外情況就是:如果一個低優先級的線程擁有執行所需的資源,而高優先級的線程正在等待某種資源,則低優先級的線程将臨時被賦予較高的優先級 (priority inversion),這樣可以使低優先級的線程得以執行完并且釋放它所占有的資源。一個線程剛建立的時候,它的優先級總是THREAD_PRIORITY_NORMAL,我們可以藉由SetThreadPriority (HANDLE hThread, int nPriority) 來設定線程的優先級,對于已經存在的線程,可以透過int GetThreadPriority (HANDLE hThread) 來得知它的優先級。
3.4.4 跟排程有關的函數簡介
在shedule.c中有很多與排程有關的函數,下面介紹其中的一些主要函數:
1. MakeRunIfNeeded(HANDLE hth)
程式代碼3.4
Void MakeRunIfNeeded (HANDLE hth) { PTHREAD pth; KCALLPROFON(39); if (( pth = HandleToThread(hth)) && (GET_RUNSTATE(pth) == RUNSTATE_NEEDSRUN)) { if (GET_SLEEPING(pth)) { SleepqDequeue(pth); pth->wCount ++; } MakeRun(pth); } KCALLPROFOFF(39); } | //開啟profiling 機制。 //檢查線程的狀态是否為即将進入可運作狀态。 //線程現正處在睡眠态,将它從睡眠隊列中移除。 //呼叫 MakeRun 重新排程。 //關閉profiling 機制。 |
2. MakeRun(PTHREAD pth)
如果目前沒有可以運作的線程,或者指定的線程pth是優先級最高的線程,那麼把pth插入到可運作隊列的最前面,并判斷是否需要重新修改排程政策。否則,就是有比pth優先級更高的線程,把pth插入到合适的地方插入。
程式代碼3.5
VOID MakeRun(PTHREAD pth ) { DWORD prio, prio2; PTHREAD pth2, pth3; if (!pth->bSuspendCnt) { SET_RUNSTATE(pth,RUNSTATE_RUNNABLE); prio = GET_CPRIO(pth); prio2 = prio/PRIORITY_LEVELS_HASHSCALE; if (!(pth2 = RunList.pRunnable) || (prio < GET_CPRIO(pth2))) { pth->pPrevSleepRun = 0; if (pth->pNextSleepRun = pth2) { pth2->pPrevSleepRun = pth; } pth->pUpRun = pth->pDownRun = RunList.pHashThread[prio2] = pth; RunList.pRunnable = pth; if (!RunList.pth || (prio < GET_CPRIO(RunList.pth))) SetReschedule(); } else { if (!(pth2 = RunList.pHashThread[prio2])) { RunList.pHashThread[prio2] = pth; while (!(pth2 = RunList.pHashThread[-- prio2])) ; } if (prio < GET_CPRIO(pth2)) { DEBUGCHK (prio/PRIORITY_LEVELS_HASHSCALE == prio2); pth->pPrevSleepRun = pth2->pPrevSleepRun; pth->pNextSleepRun = pth2; pth->pUpRun = pth->pDownRun = RunList.pHashThread[prio2]; pth->pPrevSleepRun->pNextSleepRun = pth2->pPrevSleepRun =pth; } else { while ((pth3 = pth2->pNextSleepRun) && (prio >=GET_CPRIO(pth3))) pth2 = pth3; DEBUGCHK (!pth3 || (prio < GET_CPRIO(pth3))); DEBUGCHK (GET_CPRIO (pth2) <= prio); if (prio == GET_CPRIO(pth2)) { pth->pUpRun = pth2->pUpRun; pth->pUpRun->pDownRun = pth2->pUpRun = pth; pth->pDownRun = pth2; pth->pPrevSleepRun = pth->pNextSleepRun = 0; } else { if (pth->pNextSleepRun = pth3) { pth3->pPrevSleepRun = pth; } pth->pPrevSleepRun = pth2; pth2->pNextSleepRun = pth->pUpRun = pth->pDownRun = pth; } } } } else { DEBUGCHK(!((pth->wInfo >> DEBUG_LOOPCNT_SHIFT) & 1)); SET_RUNSTATE(pth, RUNSTATE_BLOCKED); } } | //沒有可執行的線程或者pth自己就是最高優先權的線程。 //将 pth 插入runnable 隊列最前面,并更新hash table。 //檢查是否需要重新排程 //沒有其它正在執行的線程或者目前的線程 pth 優先權是最高的。 //設定重新排程。 //若有其它優先權高于 pth 的線程,找出隊列中正确的位置将 pth 插入。 //與 pth 相同優先權的執行隊列為空。 //找到前面非0的hash table entry。 //pth 的優先權是否高于 pth2。 //将 pth 線程插在執行隊列中 pth2 之前。 //pth 優先權低于 pth2,找到适當位置将 pth 插入執行隊列中。 //找到執行隊列中優先權不高于 pth 的線程。 //pth 的優先權等于 pth2,則将pth插在pth2之後的位置。 //pth 的優先權介于pth2 與pth3 之間,則将其插在執行隊列中它們兩個的中間。 //pth 的等待計數不為0,必需等待某些線程執行完之後才能執行。 //将pth的狀态設定為停滞(block)狀态。 |
3. RunqDequeue(PTHREAD pth, DWORD cprio)
從執行隊列中删除一個線程,如果目前線程由于某種原因不能執行,則從可執行隊列中删除,并讓下一個可執行的線程運作 (呼叫MakeRun)。
程式代碼3.6
void RunqDequeue(PTHREAD pth, DWORD cprio) { PTHREAD pDown, pNext; DWORD prio = cprio/PRIORITY_LEVELS_HASHSCALE; DEBUGCHK (!GET_SLEEPING (pth)); DEBUGCHK (!pth->pUpSleep); // check if there is a hanging tail of the thread... if (pDown = pth->pDownSleep) { DEBUGCHK (!GET_NEEDSLEEP(pDown) && GET_SLEEPING (pDown)); DEBUGCHK (cprio <= GET_CPRIO (pDown)); DEBUGCHK (!pDown->bSuspendCnt); pDown->wCount ++; pDown->wCount2 ++; CLEAR_SLEEPING (pDown); pDown->pUpSleep = pth->pDownSleep = 0; // don't worry about it if NEEDRUN flag is set if (GET_RUNSTATE(pDown) != RUNSTATE_NEEDSRUN) { // setup proxy for cleanup if not pure sleeping DEBUGCHK(GET_RUNSTATE(pDown) == RUNSTATE_BLOCKED); if (pDown->lpce) { // not set if purely sleeping pDown->lpce->base = pDown->lpProxy; pDown->lpce->size = (DWORD)pDown->lpPendProxy; pDown->lpProxy = 0; } else if (pDown->lpProxy) { // must be an interrupt event - special case it! LPPROXY pprox = pDown->lpProxy; LPEVENT lpe = (LPEVENT)pprox->pObject; DEBUGCHK(pprox->bType == HT_MANUALEVENT); DEBUGCHK(pprox->pQDown == pprox); DEBUGCHK(pprox->pQPrev == (LPPROXY)lpe); DEBUGCHK(pprox->dwRetVal == WAIT_OBJECT_0); lpe->pProxList = 0; pprox->pQDown = 0; pDown->lpProxy = 0; } // replace pth's slot if of same priority if (cprio == GET_CPRIO (pDown)) { if (pth == pth->pDownRun) { pDown->pUpRun = pDown->pDownRun = pDown; } else { // fix up the links if (pDown->pUpRun = pth->pUpRun) pDown->pUpRun->pDownRun = pDown; if (pDown->pDownRun = pth->pDownRun) pDown->pDownRun->pUpRun = pDown; } // fix up next node if (pDown->pNextSleepRun = pth->pNextSleepRun) pDown->pNextSleepRun->pPrevSleepRun = pDown; // fix up prev node, update pRunnable if necessary if (pDown->pPrevSleepRun = pth->pPrevSleepRun) { pDown->pPrevSleepRun->pNextSleepRun = pDown; } else if (RunList.pRunnable == pth) { RunList.pRunnable = pDown; } if (RunList.pHashThread[prio] == pth) RunList.pHashThread[prio] = pDown; SET_RUNSTATE (pDown, RUNSTATE_RUNNABLE); return; } // not of the same priority, just call MakeRun // might want to save an instruction or two by // handling the logic here (don't have to check // suspend/pRunnable, etc. MakeRun (pDown); } } pDown = pth->pDownRun; pNext = pth->pNextSleepRun; if (RunList.pHashThread[prio] == pth) { RunList.pHashThread[prio] = ((pDown != pth) ? pDown : (pNext && (GET_CPRIO(pNext)/PRIORITY_LEVELS_HASHSCALE == (WORD)prio)) ? pNext : 0); } if (pDown == pth) { if (!pth->pPrevSleepRun) { DEBUGCHK(RunList.pRunnable == pth); if (RunList.pRunnable = pNext) pNext->pPrevSleepRun = 0; } else { if (pth->pPrevSleepRun->pNextSleepRun = pNext) pNext->pPrevSleepRun = pth->pPrevSleepRun; } } else { pDown->pUpRun = pth->pUpRun; pth->pUpRun->pDownRun = pDown; if (pth->pPrevSleepRun) { pth->pPrevSleepRun->pNextSleepRun = pDown; pDown->pPrevSleepRun = pth->pPrevSleepRun; goto FinishDequeue; } else if (pth == RunList.pRunnable) { RunList.pRunnable = pDown; DEBUGCHK(!pDown->pPrevSleepRun); FinishDequeue: if (pNext) { pNext->pPrevSleepRun = pDown; pDown->pNextSleepRun = pNext; } } } } | //檢查 pth 之後是否有其它線程等着讓 pth 喚醒。 //pth 與 pDown 的優先權是同等級的。 //與 pth 相同優先權的等級且于可執行狀态的線程隻有 pth 自己一個。 //還有其它與 pth 相同優先權等級且處于可執行狀态的線程。 //以 pDown 取代 pth 在 hash table中相同優先權等級的隊列中的位置。 //以 pDown 取代 pth 在執行隊列中的位置。 //若是 pth 為正在執行的線程,以 pDown 取代 pth 成為正在執行的線程。 //若 pth 為 hash table 中優先等級為 prio 的隊列的第一個,則必須更新 hash table 的值,以 pDown 取代之。 //設定 pDown 為可執行狀态。 //若pDown 與 pth 為不同優先權等級,隻要呼叫 MakeRun 重新調整pDown 在執行隊列中位置。 //沒有其它線程等着讓 pth 喚醒,将 pth 從執行隊列及 hash table 中移除,并對隊列及 hash table 做适當更新。 //hash table 中優先權等級與 pth 相同的隊列中隻有 pth 一個線程。 //hash table 中優先權等級與 pth 相同的隊列中還有其它線程。 //将 pth從 hash table 中優先權等級與 pth 相同的隊列中移除。 //将 pth 從執行隊列中移除。 |
4. SleepqDequeue(PTHREAD pth)
把一個線程從睡眠隊列中删除。
程式代碼3.7
void SleepqDequeue(PTHREAD pth) { PTHREAD pth2; DEBUGCHK(pth && GET_SLEEPING(pth)); pth->wCount2++; if (pth2 = pth->pUpSleep) { DEBUGCHK (pth != SleepList); DEBUGCHK (!pth->pNextSleepRun && !pth->pPrevSleepRun); if (pth2->pDownSleep = pth->pDownSleep) { pth2->pDownSleep->pUpSleep = pth2; pth->pDownSleep = 0; } pth->pUpSleep = 0; } else if (pth2 = pth->pDownSleep) { DEBUGCHK (!pth2->pNextSleepRun && !pth2->pPrevSleepRun); if (pth2->pNextSleepRun = pth->pNextSleepRun) { pth2->pNextSleepRun->pPrevSleepRun = pth2; } if (pth2->pPrevSleepRun = pth->pPrevSleepRun) { pth2->pPrevSleepRun->pNextSleepRun = pth2; } else { DEBUGCHK (pth == SleepList); SleepList = pth2; } pth2->pUpSleep = pth->pDownSleep = 0; } else if (pth2 = pth->pPrevSleepRun) { if (pth2->pNextSleepRun = pth->pNextSleepRun) { pth2->pNextSleepRun->pPrevSleepRun = pth2; } } else { DEBUGCHK (pth == SleepList); // update SleepList and dwReschedTime dwReschedTime = dwPrevReschedTime + ((RunList.pth && RunList.pth->dwQuantum)? RunList.pth->dwQuantLeft: 0x7fffffff); if (SleepList = pth->pNextSleepRun) { SleepList->pPrevSleepRun = 0; if ((int) (dwReschedTime - SleepList->dwWakeupTime) > 0) { dwReschedTime = SleepList->dwWakeupTime; } } } CLEAR_SLEEPING(pth); } | //将 pth 的睡眠計數加1。 // pth 等着其它線程喚醒它。 //更新UpSleep 與 DownSleep 指針,将 pth 從隊列中移除。 //有其它線程等着讓 pth 喚醒。 //更新 pNextSleepRun 與 pPreySleepRun 名額,将 pth 從睡眠隊列中移除。 //在睡眠隊列中在 心pth 之前另有一個線程。 //更新 pNextSleepRun 與 pPreySleepRun名額,将 pth 從睡眠隊列中移除。 //pth 位于睡眠隊列中的第一個位置。 //重算下次重新排程的時間。 //将 pth 從睡眠隊列中移除。 //確定若新的睡眠隊列中第一個線程被喚醒時,會重新排程一次。 |
5. ThreadSleep(DWORD time)
線程睡眠一段時間,被SC_Sleep函數呼叫用來判斷一個線程是否需要執行,如果是,判斷這個線程是否在睡眠隊列中,如果是,将其由睡眠隊列中删除,并呼叫MakeRun(pth)來執行這個線程。另外在NK/KERNEL的schedule.c中,還有void NextThread(void),void KCNextThread (void)兩個函數,他們跟重新排程,reschedule(),關系密切,執行reschedule()後,可能會跳到其中的某一個函數,這裡不再詳細說明,請讀者自己參考檔案中的注釋。
3.5 關于中斷
處理過程與Unix具有類似機制,也就是先找到切入點,然後setevent (Unix中為signal),等到排程的時候再真正進行處理。讀過Unix程式代碼分析相關書籍的讀者應該很容易了解,這裡就不再詳細說明,下面主要介紹一下有關中斷API接口。
Windows CE中有一個包含了所有有關中斷API接口的函數子產品intrapi.c (在nk/kernel目錄下),這些函數接口允許在使用者狀态下的線程接收中斷信号,并且與硬體中斷管理例程進行互相作用,下面簡單介紹一下這個子產品。
3.5.1 關于核心中斷 (kernel interrupt)
首先介紹一個關于核心中斷支援的主題。所謂kernel的中斷子產品是指那些從kernel中獲得中斷回應的中斷。為了能夠管理這些中斷,OEM必須安裝ISR (Interrupt Service Routine)和IST (Interrupt Service Thread)。主要的工作是由IST完成的。為了安裝ISR,ISR 應該在一個特别的硬體中斷上被啟動,OEM程式代碼呼叫HookInterrupt函數,将核心狀态跳轉到注冊的函數功能上,讓所有的系統負荷盡可能的少,根本原因就是使用了少量的彙編語言。然而這些ISR,與傳統的使用在DOS或者WINDOWS中的傳統的ISR又有一些不同,它們要非常地小并且快,起碼要跟少數的彙編語言指令差不多。這是因為這些例程常在下面的限制之下:
1) 它們在中斷關閉的情況下執行;
2) 它們不能呼叫任何kernel mode下的功能函數;
3) 它們不能使用任何堆棧;
4) 它們甚至不能使用任何的緩存器;
上面所有的原因不能導緻巢狀 (nested) 的例外。它們所能做的隻是帶一個回傳值回到kernel,這個回傳值告訴kernel這個中斷已經被處理 (NOP),或者藉由傳回與其一緻的中斷程式代碼來要求kernel排程一個制定的IST。在<Interrupt ID's.Interrupt ID's>中詳細介紹關于傳回程式代碼。
如果ISR傳回一個NOP,kernel立刻結束對中斷的處理權。也就是說ISR應該做所需要做的事。相反,如果IST将被kernel呼叫,需要在注冊事件上呼叫<l SetEvent.SetEvent>,然後結束對中斷的處理權。
為了注冊一個IST,OEM裝置驅動程式應該呼叫<f InterruptInitialize>函數以及注冊事件,在中斷需要被處理的時候,需要對這些事件發出信号。被喚醒之後,相應的線程應該做所有需要做的事情,例如:與硬體進行溝通,讀取資料,處理資料,等等。線程需要呼叫kernel中的<f InterruptDone>函數,這個函數呼叫相應的硬體例程<l OEMInterruptDone.OEMInterruptDone>。OEM函數應該做适當的屏蔽 (mask) 或者結束所需要的中斷信号。其它的一些函數用來初始化系統,以及開啟或者關閉一些指定的中斷。
3.5.2 相關函數
1. extern BOOL HookInterrupt(int hwIntNumber, FARPROC pfnHandler) ;
功能:允許硬體與某種相應的硬體中斷聯系。
回傳值:如果聯系失敗,傳回一個FALSE值。
參數:int hwIntNumber:這個參數是硬體中斷所離開的硬體中斷線路。注意:不是Windows CE系統定義的中斷ID。
FARPROC pfnHandler:中斷處理器。(需要注冊)
這是一個核心程式無掩蓋的功能,通常被OEM硬體使用,用來呼叫<f OEMInit>函數注冊它的ISR之用。
2. BOOL IsValidIntrEvent(HANDLE hEvent);
功能:判斷事件是否為有效的中斷事件
回傳值:如果是,傳回TRUE﹔不是的話,則傳回FALSE
3. BOOL SC_InterruptInitialize(DWORD idInt, HANDLE hEvent, LPVOID pvData,
DWORD cbData)
功能:初始化硬體中斷
回傳值:如果不能初始化中斷,則傳回FALSE
參數:DWORD idInt :與這個IST相關的中斷ID
HANDLE hEvent :當中斷消失的時候,所需要通知的事件句柄
LPVOID pvData :需要傳遞給硬體呼叫<f OEMInterruptEnable>的資料,或者是對硬體ISR要使用的scratch空間的尋址。kernel會鎖定這些資料并把實體位址傳遞給硬體例程,這樣的話,硬體例程可以對它進行存取,而不用考慮快表 (TLB, Translation-Look-ahead Buffer) 會不會命中。
DWORD cbData :所傳遞的資料的大小。
裝置驅動程式呼叫這個函數來注冊一個事件,這個事件在中斷發生時,以及允許中斷的情況下需要被通知。
4. Void SC_InterruptDone(DWORD idInt)
功能:中斷處理的信号完成
參數:DWORD idInt:中斷的ID
回傳值:空
當一個裝置驅動程式完成對一個中斷的處理,并且做好對下一個中斷進行處理的準備時,呼叫這個函數。呼叫這個函數的作用,是中斷在驅動程式等待注冊事件再一次被發信号通知之前,使中斷顯露出來,kernel基本上是藉由 <f OEMInterruptDone>呼叫的。
5. void SC_InterruptDisable(DWORD idInt)
功能:關閉硬體中斷:
參數:DWORD idInt :中斷ID
回傳值:空
裝置驅動程式呼叫這個函數來關閉所描述的硬體中斷,藉由InterruptInitialize移除事件的注冊。驅動程式必須在關閉事件處理之前呼叫這個函數。在這個函數中,kernel呼叫了<f OEMInterruptDisable>這個例程。
6. void FreeIntrFromEvent(LPEVENT lpe)
功能:把中斷從事件中分離出來
回傳值:空