1 调度
Windows不是实时操作系统,它是抢占式多线程操作系统。在假设所有优先级相同的情况下,CPU对线程的调度原则是每隔20m就会切换到下一个线程,根据Context中的IP和SP来接着执行上次的东西。Windows永远不会让1个线程去独占一段时间。
2 可调度性
系统只调用可以调度的线程,其实系统的大部分线程都是处于不可调度的状态,要么处于暂停的状态,要么处于休眠的状态。
3 线程的暂停和恢复
<1>在CreateThread的时候通过制定CREATE_SUSPENDED来让线程暂停执行
<2>在DWORD SuspendThread(HANDLE hThread)函数来暂停一个线程,最多次数为MAXIMUM_SUSPEND_COUNT(127)
<3>通过函数DWORD ResumeThread(HANDLE hThread)来唤醒线程
4 进程的暂停
Windows中从来不存在进程的暂停和恢复,因为进程是不会被调度的。但是在特殊情况下Windows会冻结进程中所有的线程:调试程序中处理函数WaitForDebugEvent返回的debug事件;直到调用ContinueDebugEvent.函数才会恢复进程中的所有线程。
但是我们可以通过遍历系统中所有的线程,通过检查线程所属的进程ID是否满足指定值,就可以做到暂停所有的线程。
弊端:
<1>遍历线程ID时候,如果有新线程在创建,那么新线程将不会被暂停
<2>当重新恢复线程的时候,可能会对新创建的没有被暂停的线程去恢复
<3>遍历线程ID的时候,撤销的线程跟新建的线程可能具有具有相同的ID,这就可能导致暂停多个具有相同ID的线程。
进程中所有线程暂停函数如下所示:
VOID SuspendProcess(DWORD dwProcessID, BOOL fSuspend) { // Get the list of threads in the system. HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, dwProcessID); if (hSnapshot != INVALID_HANDLE_VALUE) { // Walk the list of threads. THREADENTRY32 te = { sizeof(te) }; BOOL fOk = Thread32First(hSnapshot, &te); for (; fOk; fOk = Thread32Next(hSnapshot, &te)) { // Is this thread in the desired process? if (te.th32OwnerProcessID == dwProcessID) { // Attempt to convert the thread ID into a handle. HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID); if (hThread != NULL) { // Suspend or resume the thread. if (fSuspend) SuspendThread(hThread); else ResumeThread(hThread); } CloseHandle(hThread); } } CloseHandle(hSnapshot); } }
5 进程的休眠
VOID Sleep(DWORD dwMilliseconds)
<1>线程的休眠导致线程在一定时间段内放弃被调度的机会
<2>线程休眠的时间大约是指定的时间,但是可能远大于这个时间,这取决于操作系统
<3>参数值为INFINITE,表示系统永远不去调度线程,但是这个方法不好
<4>参数为0,表示放弃此次的时间片,切换到下一个线程,但是线程可能切换到自身如果没有同等优先级或者更高的优先级的存在。
6 线程的切换
BOOL SwitchToThread();
当调用这个函数的时候,系统检测是否有一个线程迫切需求CPU时间,如果没函数就立即返回,如果有就切换到这个线程,即便线程的优先级可能低于当前的线程优先级。
函数的功能和Sleep函数在参数值为0的时候很相似,但是不同点是SwitchToThread函数允许优先级低的线程被调用,Sleep函数却不行。
7 线程的执行时间
<1>通常的程序运行时间计算方法:
ULONGLONG qwStartTime = GetTickCount64();
// Perform complex algorithm here.
ULONGLONG qwElapsedTime = GetTickCount64()- qwStartTime;
但是这样其实是错误的,因为它假设线程执行不被为中断。
<2>Windows提供了一个获取线程和进程时间信息的函数GetThreadTime, GetProcessTime
BOOL GetThreadTimes( HANDLE hThread, PFILETIME pftCreationTim PFILETIME pftExitTime, PFILETIME pftKernelTime, PFILETIME pftUserTime); BOOL GetProcessTimes( HANDLE hProcess, PFILETIME pftCreationTime, PFILETIME pftExitTime, PFILETIME pftKernelTime, PFILETIME pftUserTime);
<3>TSC 计时方法
目前线程的计时时间方式发生了变换,和之前精度为10-15ms的内部时钟计时器不同,系统现在采用一种Time Stamp Counter (TSC)计算时间,它表示的是自从计算机开机后运行的CPU周期个数。
通过QueryThreadCycleTime和QueryProcessCycleTime来获取线程和进程执行的周期个数。
BOOL WINAPI QueryThreadCycleTime( _In_ HANDLE ThreadHandle, _Out_ PULONG64 CycleTime //包含用户时间和内核时间总和的周期计数值 ); BOOL WINAPI QueryProcessCycleTime( _In_ HANDLE ProcessHandle, _Out_ PULONG64 CycleTime //包含用户时间和内核时间总和的周期计数值 );
<4>高精度计时方法
BOOLQueryPerformanceFrequency(LARGE_INTEGER* pliFrequency); BOOL QueryPerformanceCounter(LARGE_INTEGER*pliCount);但是若用这两个函数来计算线程的执行时间的话,前提是假设线程不被抢占。
8 Context的使用
我们说Context中存放着线程的状态信息,允许线程在调用时候继续上次的执行。CONTEXT结构体是唯一的依赖于CPU的结构体。例如在X86体系结构中,它包含下面寄存器。
CONTEXT_CONTROL,CONTEXT_DEBUG_REGISTERS,CONTEXT_FLOATING_POINT,CONTEXT_SEGMENTS,CONTEXT_INTEGER,CONTEXT_EXTENDED_REGISTERS
例如在x86中,它如下所示:
typedef struct _CONTEXT { // // The flag values within this flag control the contents of // a CONTEXT record. // // If the context record is used as an input parameter, then // for each portion of the context record controlled by a flag // whose value is set, it is assumed that that portion of the // context record contains valid context. If the context record // is being used to modify a thread's context, only that // portion of the thread's context will be modified. // // If the context record is used as an IN OUT parameter to capture // the context of a thread, only those portions of the thread's // context corresponding to set flags will be returned. // // The context record is never used as an OUT only parameter. // DWORD ContextFlags; // // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is // set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT // included in CONTEXT_FULL. // DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; // This section is specified/returned if theContextFlags word contains the flag //CONTEXT_FLOATING_POINT. FLOATING_SAVE_AREA FloatSave; // // This section is specified/returned if the // ContextFlags word contains the flag CONTEXT_SEGMENTS. // DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; // // This section is specified/returned if the // ContextFlags word contains the flag CONTEXT_INTEGER. // DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; // // This section is specified/returned if the // ContextFlags word contains the flag CONTEXT_CONTROL. // DWORD Ebp; DWORD Eip; DWORD SegCs; // MUST BE SANITIZED DWORD EFlags; // MUST BE SANITIZED DWORD Esp; DWORD SegSs; // // This section is specified/returned if the ContextFlags word // contains the flag CONTEXT_EXTENDED_REGISTERS. // The format and contexts are processor specific // BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; } CONTEXT;
我们可以对CONTEXT中寄存器的内容进行读取和改写,这是相当酷比的行为。
<1> 获取CONTEXT内容
BOOL GetThreadContext(
HANDLE hThread,
PCONTEXT pContext);
示例:
CONTEXT Context; Context.ContextFlags = CONTEXT_CONTROL; GetThreadContext(hThread, &Context);
<2>设置CONTEXT内容
BOOL SetThreadContext(
HANDLE hThread,
CONST CONTEXT *pContext);
示例:
CONTEXT Context; SuspendThread(hThread); Context.ContextFlags = CONTEXT_CONTROL; GetThreadContext(hThread, &Context); Context.Eip = 0x00010000; Context.ContextFlags = CONTEXT_CONTROL; SetThreadContext(hThread, &Context); ResumeThread(hThread);
9 线程的优先级
<1>每个线程都会被赋予编号为0-31的一个优先级别,31表示最高,0表示最低
<2>只要有优先级为31的可调用,就绝不会调用0-30的
<3>即便低优先级正在运行,只要系统发现一个高的优先级要运行,低的会被暂停。
<4>当系统引导时会创建一个特殊线程叫做0页线程,是系统中唯一优先级为0的线程,当系统没有别的线程运行时候,0页线程会负责将系统所有空闲RAM页面置0.
10 优先级的抽象说明
应用程序的作用是不断变化的,需求也是不断变化的,今天设置的优先级可能在在明天已经不合时宜,为了能使今天写的程序能在以后的系统上正常的运行,所以调度算法不能一成不变,因此为微软给应用程序设置了一个抽象的优先级别,Windows支持6种优先级别
通过这种方式,可以简单改变程序的优先级别就达到改变程序中所有线程优先级别的作用。在应用程序的基础上再根据线程的优先级别,给线程设计优先类,一共7个如下所示:
那么线程结合进程后,线程的优先级如下所示:
注意:进程没有被调度,实质上没有优先级可言,这里说的进程的优先级只是个抽象概念,通过这个抽象的优先级,可以改变线程的优先级。
11 设置程序的优先级
<1>CreateProcess的时候,dwCreationFlags参数可以设置
<2> 子进程在运行的时候改变优先级
BOOL SetPriorityClass(HANDLE hProcess, DWORD fdwPriority);
<3>命令行启动程序
当正常启动时候,默认进程是正常的优先级,当用STRAT启动进程的时候,可以附带优先级开关。如下所示:
C:\>START /LOW CALC.EXE
/BELOWNORMAL, /NORMAL, /ABOVENORMAL,/HIGH, /REALTIME,这些都是可选的模式
<4> 通过任务管理器来设置进程的优先级别
<5>设置线程优先级
BOOL SetThreadPriority( HANDLE hThread, int nPriority);
线程刚刚创建的时候,优先级是默认的正常优先级,设置优先级的代码如下所示:
DWORD dwThreadID; HANDLE hThread = CreateThread(NULL, 0,ThreadFunc, NULL, CREATE_SUSPENDED, &dwThreadID); SetThreadPriority(hThread,THREAD_PRIORITY_IDLE); ResumeThread(hThread); CloseHandle(hThread)
<6> 动态提高线程优先级
线程的基本优先级:线程的相对优先级和线程所属的进程的优先级综合考虑得到的优先级
系统常常要提高线程的优先级等级,以便对窗口消息或读取磁盘等I / O事件作出响应。
系统只能为基本优先级等级在1至1 5之间的线程提高其优先级等级
线程的当前优先级不会低于线程的基本优先级
系统决不会将线程的优先级等级提高到实时范围(高于 1 5)
如果要拒绝操作系统动态的提高线程的优先级,那么就可以使用下面的两个函数:
BOOL SetProcessPriorityBoost(HANDLEhProcess, BOOLbDisablePriorityBoost); BOOL SetThreadPriorityBoost(HANDLE hThread,BOOLbDisablePriorityBoost);
检查是否启动自动调整优先级,使用下面的两个函数:
BOOL GetProcessPriorityBoost(HANDLEhProcess,PBOOL pbDisablePriorityBoost); BOOL GetThreadPriorityBoost(HANDLE hThread,PBOOLpbDisablePriorityBoost);
<7>为前台进程调整调度程序
当用户对进程的窗口进行操作时,该进程就称为前台进程,所有其他进程则称为后台进程。当然,用户希望他正在使用的进程比后台进程具有更强的响应性。为了提高前台进程的响应性,Wi n d o w s能够为前台进程中的线程调整其调度算法。对于 Windows 2000来说,系统可以为前台进程的线程提供比通常多的 C P U时间量。这种调整只能在前台进程属于正常优先级类的进程时才能进行。如果它属于其他任何优先级类,就无法进行任何调整。
当一个优先级为正常的进程移到前台时,系统便将最低、低于正常、正常、高于正常和最高等优先级的线程的优先级提高 1,优先级为空闲和关键时间的线程的优先级则不予提高。
设置是否开始提高前台调度性能的方法:
10 亲缘性
这个不多说了,就是在有多个CPU的时候,指定进程或者线程在哪几个指定的CPU上运行的策略,在一定情况下可以提高CPU的使用率。