第三章 进程与线程的描述和调度
这个章节将详细介绍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; /* 00: ID of this process [ie: it's slot number] */ BYTE bChainDebug /* 02: Did the creator want to debug child processes? */ BYTE bTrustLevel; /* 03: level of trust of this exe */ #define OFFSET_TRUSTLVL 3 // offset of the bTrustLevel member in Process structure LPPROXY pProxList; /* 04: list of proxies to threads blocked on this process */ HANDLE hProc; /* 08: handle for this process, needed only for SC_GetProcFromPtr */ DWORD dwVMBase; /* 0C: base of process's memory section, or 0 if not in use */ PTHREAD pTh; /* 10: first thread in this process */ ACCESSKEY aky; /* 14: default address space key for process's threads */ LPVOID BasePtr; /* 18: Base pointer of exe load */ HANDLE hDbgrThrd; /* 1C: handle of thread debugging this process, if any */ LPWSTR lpszProcName; /* 20: name of process */ DWORD tlsLowUsed; /* 24: TLS in use bitmask (first 32 slots) */ DWORD tlsHighUsed; /* 28: TLS in use bitmask (second 32 slots) */ PEXCEPTION_ROUTINE pfnEH; /*2C: process exception handler */ LPDBGPARAM ZonePtr; /* 30: Debug zone pointer */ PTHREAD pMainTh; /* 34 primary thread in this process*/ PMODULE pmodResource; /* 38: module that contains the resources */ LPName pStdNames[3]; /* 3C: Pointer to names for stdio */ LPCWSTR pcmdline; /* 48: Pointer to command line */ DWORD dwDyingThreads; /* 4C: number of pending dying threads */ openexe_t oe; /* 50: Pointer to executable file handle */ e32_lite e32; /* ??: structure containing exe header */ o32_lite *o32_ptr; /* ??: o32 array pointer for exe */ LPVOID pExtPdata; /* ??: extend pdata */ BYTE bPrio; /* ??: highest priority of all threads of the process */ BYTE fNoDebug; /* ??: this process cannot be debugged */ WORD wPad; /* padding */ |
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; /* nonce for blocking lists */ 停滞队列的当前计数
WORD wCount2; /* nonce for SleepList */ 睡眠队列的当前计数
WORD wCrabCount;
6. 有关Windows CE调度
Windows CE是基于preeemptive的time-sliced round-robin来进行调度,系统可能要在同的线程之间切换,所以切换时必须保存线程的上下文 (context) 信息。
CPUCONTEXT ctx; /* thread's cpu context information */
为方便读者理解,下面列出Windows CE中所定义的Thread结构:
程序代码3.2
struct Thread { WORD wInfo; /* 00: various info about thread, see above */ BYTE bSuspendCnt; /* 02: thread suspend count */ BYTE bWaitState; /* 03: state of waiting loop */ LPPROXY pProxList; /* 04: list of proxies to threads blocked on this thread */ PTHREAD pNextInProc; /* 08: next thread in this process */ PPROCESS pProc; /* 0C: pointer to current process */ PPROCESS pOwnerProc; /* 10: pointer to owner process */ ACCESSKEY aky; /* 14: keys used by thread to access memory & handles */ PCALLSTACK pcstkTop; /* 18: current api call info */ DWORD dwOrigBase; /* 1C: Original stack base */ DWORD dwOrigStkSize; /* 20: Size of the original thread stack */ LPDWORD tlsPtr; /* 24: tls pointer */ DWORD dwWakeupTime; /* 28: sleep count,also pending sleepcnt on waitmult */ LPDWORD tlsSecure; /* 2c: TLS for secure stack */ LPDWORD tlsNonSecure; /* 30: TLS for non-secure stack */ LPPROXY lpProxy; /* 34: first proxy this thread is blocked on */ DWORD dwLastError; /* 38: last error */ HANDLE hTh; /* 3C: Handle to this thread, needed by NextThread */ BYTE bBPrio; /* 40: base priority */ BYTE bCPrio; /* 41: curr priority */ WORD wCount; /* 42: nonce for blocking lists */ PTHREAD pPrevInProc; /* 44: previous thread in this process */ LPTHRDDBG pThrdDbg; /* 48: pointer to thread debug structure, if any */ LPBYTE pSwapStack; /* 4c */ FILETIME ftCreate; /* 50: time thread is created */ CLEANEVENT *lpce; /* 58: cleanevent for unqueueing blocking lists */ DWORD dwStartAddr; /* 5c: thread PC at creation, used to get thread name */ CPUCONTEXT ctx; /* 60: thread's cpu context information */ PTHREAD pNextSleepRun; /* ??: next sleeping thread,if sleeping; next runnable thread on runq,if runnable */ PTHREAD pPrevSleepRun; /* ??: back pointer if sleeping or runnable */ PTHREAD pUpRun; /* ??: up run pointer (circular) */ PTHREAD pDownRun; /* ??: down run pointer (circular) */ PTHREAD pUpSleep; /* ??: up sleep pointer (null terminated) */ PTHREAD pDownSleep; /* ??: down sleep pointer (null terminated) */ LPCRIT pOwnedList; /* ??: list of crits (critical section) and mutexes for priority inversion */ LPCRIT pOwnedHash[PRIORITY_LEVELS_HASHSIZE]; DWORD dwQuantum; /* ??: thread quantum */ DWORD dwQuantLeft; /* ??: quantum left */ LPPROXY lpCritProxy; /* ??: proxy from last critical section block, in case stolen back */ LPPROXY lpPendProxy; /* ??: pending proxies for queueing */ DWORD dwPendReturn; /* ??: return value from pended wait */ DWORD dwPendTime; /* ??: timeout value of wait operation */ PTHREAD pCrabPth; WORD wCrabCount; WORD wCrabDir; DWORD dwPendWakeup; /* ??: pending timeout */ WORD wCount2; /* ??: nonce for SleepList */ BYTE bPendSusp; /* ??: pending suspend count */ BYTE bDbgCnt; /* ??: recursive level in debug message */ HANDLE hLastCrit; /* ??: Last crit taken, cleared by nextthread */ DWORD dwCrabTime; CALLSTACK IntrStk; DWORD dwKernTime; /* ??: elapsed kernel time */ DWORD dwUserTime; /* ??: elapsed user time */ }; /* Thread */ |
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;
|
/*建立时间*/ /*结束时间*/ /*kernel-mode时间*/ /*user-mode时间*/ |
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; |
/* Pointer to a critical_section structure */
/* previous event in list */ /* Is this on someone's owner list */
/* Index of the owner process */
/* Prev crit/mutex (for prio inversion) */ /* Next crit/mutex section owned (for prio inversion) */
/* Next CRIT in list */ |
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 为 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)
功能:把中断从事件中分离出来
回传值:空