1.线程内核对象中的CONTEXT反应了线程上一次执行时CPU寄存器的状态。大约每隔20ms,Windows都会查看所有当前存在的线程内核对象。Windows在可调度的线程内核对象中选择一个,并将上次保存在线程上下文中的值载入CPU寄存器。这一操作被称为上下文切换。Windows实际上会记录每个线程的运行次数。
2.调用CreateProcess或者CreateThread时,系统将创建线程内核对象,并把挂起计数初始化为1。这样就不会给这个线程调度CPU了,因为线程初始化需要时间,我们不想再线程准备好之前就开始执行它。在线程初始化之后,CreateProcess或者CreateThread函数将查看是否有CREATE_SUSPENDED标志传入,如果有,函数返回并让新的线程处于挂起状态。如果没有,函数会将线程的挂起计数递减为0,线程就成为可调度的了。
3.通过创建一个处于挂起状态的线程,我们可以在线程执行任何代码之前改变它的环境(比如优先级)。之后可以调用ResumeThread函数使其变为可调度的,如果调用成功会返回线程的前一个挂起计数,否则返回0xFFFFFFFF。
4.还可以调用SuspendThread来挂起线程,任何线程都可以调用这个函数挂起另外一个线程(只要有线程句柄)。显然线程可以将自己挂起,但是它无法自己恢复。SuspendThread返回线程之前的挂起计数。一个线程最多可以挂起MAXIMUM_SUSPEND_COUNT(WinNT.H中定义为127)次。请注意,就内核模式下面执行情况而言,SuspendThread是异步的,但是在线程恢复之前,它是无法在用户模式下执行的。
5.调用SuspendThread时必须小心,因为试图挂起一个线程时,我们不知道线程在做什么,例如线程正在分配堆中的内存,线程将锁定堆,当其他线程要访问堆的时候,它们的执行将被终止,直到第一个线程恢复。所以可能会造成死锁。
6.Windows中不存在挂起和恢复进程的概念,因为系统从来不会给进程调度CPU时间。在一个特殊情况下,即调试器处理WaitForDebugEvent返回的调试事件时,Windows将冻结被调试进程中的所有线程。直至调试器调用ContinueDebugEvent。
7.调用Sleep函数可以告诉系统,在一定时间内自己不需要调度了。
- 调用Sleep函数,将使线程资源放弃属于它的时间片中剩下的部分。
- 系统设置线程不可调度的时间只是“近似于”所设定的毫秒数,实际情况取决于系统中其他线程的运行情况。
- 给Sleep传递INFINITE参数,告诉系统永远不要调度这个进程,这样做没什么用。
- 可以给Sleep传入0,这是在告诉系统,主调线程放弃了时间片的剩余部分,它强制系统调度其他线程。但是系统有可能重新调度刚刚调用Sleep的那个线程,如果没有相同或者优先级较高的可调度线程时,就会发生这样的事情。
8.调用SwitchToThread函数时,系统查看是否存在正急需CPU时间的饥饿线程,如果没有SwitchToThread立即返回,如果存在将调度该线程(其优先级可能比SwitchToThread的主调线程低)。饥饿线程可以运行一个时间量,然后系统调度程序恢复正常运行。
9.超线程处理器芯片有多个“逻辑”CPU,每个都可以运行一个线程。每个线程都有自己的体系结构状态(一组寄存器),但是所有线程共享主要的执行资源,比如CPU高速缓存。当一个线程终止时,CPU自动执行另一个线程,无需操作系统干预,只有在缓存未命中、分支预测错误和需要等待前一个指令的结果等情况下,CPU才会暂停。
10.在超线程CPU上执行旋转循环(spin loop)时,需要我们强制当前线程暂停,使另一个线程可以访问芯片的资源。x86体系结构支持一个名为PAUSE的汇编语言指令。PAUSE指令可以确保避免内存顺序违规,从而改进性能。在x86上,PAUSE指令等价于REP NOP指令。PAUSE会导致一定的延时(有些CPU上为0).在Win32 API中,x86 PAUSE指令是通过调用WinNT.h中定义的YieldProcessor宏发出的。
11.用GetTickCount()或GetTickCount64()来计算某项任务消耗时间的时候,其前提是该任务执行时不会被中断,但是Windows是抢占式的操作系统。在Windows Vista之前的系统中我们就可以使用GetThreadTimes()函数。GetProcessTimes的返回值是进程中所有线程(即使线程已经终止)的时间综总和。
12.在Windows Vista中,系统为线程分配CPU时间的方式发生了改变。操作系统不再依赖约10~15ms的间隔时钟计时器,而是改用处理器的64位时间戳计时器,它计算的是机器启动以来的时钟周期数。QueryThreadCycleTime()和QueryProcessCycleTime()函数分别返回给定线程或进程的所有线程所用的时钟周期数。
13.在Windows定义的所有数据结构中,CONTEXT结构是唯一一个特定于CPU的。所以它的成员的具体情况取决于Windows运行在什么CPU上。以下是x86 CPU的完整CONTEXT结构定义:
typedef struct _CONTEXT { // // The flags 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 threads context, then only that // portion of the threads context will be modified. // // If the context record is used as an IN OUT parameter to capture // the context of a thread, then 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 the // ContextFlags word contians the flag CONTEXT_FLOATING_POINT. // FLOATING_SAVE_AREA FloatSave; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_SEGMENTS. // DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; // // This section is specified/returned if the // ContextFlags word contians 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 contians 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结构分为几部分:
- CONTEXT_CONTROL包含CPU的控制寄存器,比如指令指针、栈指针、标志和函数返回地址。
- CONTEXT_INTEGER标识CPU的整数寄存器。
- CONTEXT_FLOATING_POINT标识CPU的浮点寄存器。
- CONTEXT_SEGMENTS标识CPU的段寄存器。
- CONTEXT_DEBUG_REGISTERS标识CPU的调试寄存器。
- CONTEXT_EXTENDED_REGISTERS标识CPU的扩展寄存器。
14.Windows实际上允许我们调用GetThreadContext查看线程的内核对象的内部,并获取当前CPU寄存器状态的集合。调用之前应先调用SuspendThread,否则,系统可能正好获得调度此线程,这样线程的上下文与所获取的信息就不一致了。
15.一个线程实际上有两个上下文:用户模式和内核模式。GetThreadContext只能返回线程的用户模式的上下文。如果调用SuspendThread暂停一个线程,但是该线程正在内核模式执行,那么它的用户模式上下文保持不变,即使SuspendThread实际上还没有暂停线程。但是,在线程恢复之前,不能再执行任何用户模式的代码,因此,我们完全可以认为线程已经暂停,这时调用GetThreadContext是非常安全的。
16.CONTEXT结构的ContextFlags成员与任何CPU寄存器都不对应。这个成员的作用是告诉GetThreadContext函数应该获取哪些寄存器。请注意在调用GetThreadContext之前,必须首先初始化CONTEXT结构的ContextFlags成员。
17.Windows还允许我们通过调用SetThreadContext来改变结构中的成员,并把新的寄存器放回线程的内核对象中。同样调用之前要先SuspendThread,否则结果无法预料。也必须再初始化CONTEXT的ContextFlags成员。