一、线程
线程由2部分组成:(1)线程内核对象,OS用它来关系线程,存放线程统计信息;(2)线程栈,维护线程执行时所需要的所有函数参数和局部变量。
二、线程相关的数据结构
(1)ETHREAD
typedef struct _ETHREAD {
KTHREAD Tcb; // 本线程的KTHREAD结构
LARGE_INTEGER CreateTime;
... ...
CLIENT_ID Cid; //本线程的cid
ULONG_PTR TopLevelIrp; // either NULL, an Irp or a flag defined in FsRtl.h
PEPROCESS ThreadsProcess; //挂入EPROCESS中的线程队列
LIST_ENTRY ThreadListEntry;
} ETHREAD, *PETHREAD;
</span>
(2)KTHREAD
typedef struct _KTHREAD {
DISPATCHER_HEADER Header; //使线程成为“可等待”对象
... ...
PVOID InitialStack;
PVOID StackLimit;
PVOID KernelStack; //指向本线程的系统空间堆栈
union {
KAPC_STATE ApcState; //本线程当前的APC状态
... ...
};
PVOID Teb; //指向用户空间的线程环境块TEB
PKTRAP_FRAME TrapFrame; //指向系统空间堆栈上的自陷框架
PVOID CallbackStack;
PVOID ServiceTable;
PKPROCESS Process; //指向所属进程的KPROCESS结构
KAFFINITY Affinity;
PKAPC_STATE ApcStatePointer[2];
PVOID Win32Thread; //指向本线程的W32THREAD结构
PVOID StackBase;
LIST_ENTRY ThreadListEntry; //挂入KRPOCESS中的线程队列
} KTHREAD, *PKTHREAD, *PRKTHREAD;</span>
(3)TEB
typedef struct _TEB {
NT_TIB NtTib; // 线程信息块,包含了基本信息,且被结构中有一个指针指向TEB;还有一个ExceptionList
PVOID EnvironmentPointer;
CLIENT_ID ClientId;
PVOID ActiveRpcHandle;
PVOID ThreadLocalStoragePointer;
PPEB ProcessEnvironmentBlock; //指向所属进程PEB
ULONG LastErrorValue;
... ...
PVOID Win32ThreadInfo; // PtiCurrent
PVOID TlsSlots[TLS_MINIMUM_AVAILABLE];
LIST_ENTRY TlsLinks;
} TEB;</span>
三、线程的创建
(1)CreateThread 该方法基本已经不再使用
HANDLECreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);</span>
lpThreadAttributes安全属性;
dwStackSize 线程栈大小;
lpStartAddr 线程函数地址;
lpParam 函数参数;
dwCreateFlags 控制线程创建时的状态,如Create_suspended
lpThreadID 输出线程id
(2) _beginthreadex 提供多线程安全
_MCRTIMP uintptr_t __cdecl _beginthreadex(
void *security,
unsigned stacksize,
unsigned (__CLR_OR_STD_CALL * initialcode) (void *),
void * argument,
unsigned createflag,
unsigned *thrdaddr
)</span>
(a)每个线程都有自己专用的_tiddata内存块,他们是从c/c++运行时库的堆上分配的;
(b)传给_beginthreadex的函数地址和参数都保存在_tiddata中;
(c)_beginthreadex 会在内部调用CreateThread,因为操作系统只知道用这种方式来创建一个线程;
(d)CreateThread函数被调用时,传给他的函数是_threadstartex。另外,参数是_tiddata的地址;
(e)如果一切顺利,会返回线程的句柄,就像CreateThread那样。任何失败,都会返回0。
_threadstartex
(a)首先执行RtlUserThreadStart,然后再跳转到 _threadstartex;
(b) _threadstartex的唯一参数就是_tiddata的地址;
(c) tlsSetvalue是一个系统函数,他将一个值与线程关联起来;
(d) 调用_callthreadstartex中,有一个SEH帧,他将预期要执行的函数包含起来,处理运行时库的很多事情,比如:运行时错误,和运行时库相关的signal函数;
(e) 执行预期的函数;
(f) 线程函数的返回值被认为是线程的退出码;调用了_endthreadex, 在这个函数里,会销毁tiddata
四、线程的退出
和进程的退出类似,一共有4种方式:
(1)线程函数返回
线程创建的C++对象都通过析构函数被正确销毁;
操作系统正确释放线程栈使用的内存;
操作系统把线程的退出代码设为线程函数的返回值;
系统递减对内核对象的使用计数;
(2)线程通过调用exitthread来杀死自己
终止线程运行;
释放操作系统资源;
不会释放C/C++资源,推荐使用_endthreadex;
(3)同一个进程或另一个进程中的线程调用terminatethread
(4)包含线程的进程终止运行,被Terminate
类似于为每个线程调用terminatethread,意味着正确的清理操作不会被执行:C++对象不会被析构,数据不会被写回到磁盘上等。
五、线程切换
(1)用户态看线程切换:
线程有以下几种状态: Initialized, Ready, Running, Standby, Terminated, Waiting, Transition。每个线程都一个时间片段,时间片段消耗完后,会发生时钟中断,然后选择ready状态且优先级高的线程执行。
windows 是任务抢占式操作系统,线程之间不是平均分配时间的,而是优先运行优先级高的线程,高优先级的线程可以中断优先级的线程;
线程切换的几种方式:
(a)当前线程主动让出剩余的运行时间:该调度是当前线程主动让出剩余运行时间,如执行了Sleep,Wait一个对象等等;
(b)有新的线程变得可调度:比如一个被挂起的线程被中断处理程序激活,变为可调度,要进行一次调度,中断处理程序将该线程的优先级和当前线程比较,如果优先级较高,该线程将成为抢夺者,否则该线程按照其优先级挂入相应的就绪队列。
(c)就绪线程的优先级提高:如当前线程将一个就绪线程的级别提高,将引发一次调度,如果该线程的优先级比当前cpu上的线程的优先级高,该线程就成为剥夺者。
(d)调度时钟中断:每一次时钟中断,都会将当前线程的定量减少一定值,如果发现当前线程的时间片已经用完,就会进行一次调度,如果和当前线程优先级对应的就绪队列非空,该排在该就绪队列最面的线程将会得到调度,成为即将运行的线程。
(2)内核态:
每个任务、即进程或线程、都有一个独立的“任务状态段”TSS,里面包含了几乎所有寄存器的映像,而通过TSS的切换来实现任务的切换,而且只要一条指令就能完成这样的切换。这条指令把几乎所有寄存器(除一些“系统寄存器”如GDTR等以外)的当前内容都一下子保存到当前任务的TSS中;然后TR寄存器指向目标任务的TSS,再把TSS中的寄存器值放入相应的寄存器中,任务切换就完成了。
Windows内核中为此定义了一套以“处理器控制区(Processor Control Region)”KPCR为枢纽的数据结构,使每个CPU都有个KPCR结构,用来保存与线程切换有关的全局信息。
typedef struct _KPCR {
KPCR_TIB <strong>Tib</strong>;
struct _KPCR *Self;
struct _KPRCB *<strong>Prcb</strong>;
KIRQL Irql;
ULONG IRR;
ULONG IrrActive;
ULONG IDR;
PVOID KdVersionBlock;
PUSHORT IDT;
PUSHORT <strong>GDT</strong>;
struct _KTSS *<strong>TSS</strong>;
USHORT MajorVersion;
USHORT MinorVersion;
KAFFINITY SetMember;
ULONG StallScaleFactor;
UCHAR DebugActive;
UCHAR ProcessorNumber;
UCHAR Reserved;
UCHAR L2CacheAssociativity;
ULONG VdmAlert;
ULONG KernelReserved[14];
ULONG L2CacheSize;
ULONG HalReserved[16];
ULONG InterruptMode;
UCHAR KernelReserved2[0x48];
KPRCB <strong>PrcbData</strong>;
} KPCR, *PKPCR;</span>
typedef struct _KPCR_TIB {
PVOID <strong>ExceptionList</strong>;
PVOID StackBase;
PVOID StackLimit;
PVOID SubSystemTib;
_ANONYMOUS_UNION union {
PVOID FiberData;
DWORD Version;
} DUMMYUNIONNAME;
PVOID ArbitraryUserPointer;
struct _NT_TIB *Self;
} KPCR_TIB, *PKPCR_TIB;
</span>
/* ProcessoR Control Block */
typedef struct _KPRCB {
USHORT MinorVersion;
USHORT MajorVersion;
struct _KTHREAD *<strong>CurrentThread</strong>;
struct _KTHREAD *<strong>NextThread</strong>;
struct _KTHREAD *<strong>IdleThread</strong>;
. . . . . .
UCHAR CpuType;
UCHAR CpuID;
USHORT CpuStep;
KPROCESSOR_STATE ProcessorState;
. . . . . .
PVOID LockQueue[33]; // Used for Queued Spinlocks
struct _KTHREAD *NpxThread;
ULONG InterruptCount;
ULONG KernelTime;
ULONG UserTime;
. . . . . .
struct _KEVENT *DpcEvent;
UCHAR ThreadDpcEnable;
BOOLEAN QuantumEnd;
. . . . . .
LONG MmPageReadCount;
LONG MmPageReadIoCount;
LONG MmCacheReadCount;
LONG MmCacheIoCount;
LONG MmDirtyPagesWriteCount;
. . . . . .
FX_SAVE_AREA NpxSaveArea;
PROCESSOR_POWER_STATE PowerState;
} KPRCB, *PKPRCB;</span>
线程切换可以参考资料:http://www.longene.org/techdoc/0984381001224576913.html