# 等待链表
等待链表(Wait List) 用于存储正在等待某个资源或事件(如锁、信号量等)的线程队列,当资源可用时,系统会从等待链表中唤醒线程。
- 等待链表是一个双向链表
- 它有两个成员 ——
Flink (Forward Link,指向下一个节点)和 Blink (Backward Link,指向上一个节点)
- 当线程调用了
Sleep 或 WaitForSingleObject 等函数时,线程会被挂到这个链表上
dd KiWaitListHead
线程在等待链表和调度链表中的挂载位置是同一个字段:
1 2 3 4 5 6
| kd> dt _KTHREAD ntdll!_KTHREAD ... +0x060 WaitListEntry : _LIST_ENTRY +0x060 SwapListEntry : _SINGLE_LIST_ENTRY ...
|
# 实验:分析等待链表中的线程所属进程
一、 查看等待链表。
dd KiWaitListHead
二、 从链表中取出第一个节点,减去 0x60 ( WaitListEntry 在 KTHREAD 中的偏移)得到 ETHREAD 起始地址( KTHREAD 是 ETHREAD 的第一个成员,偏移为 0,因此两者地址相同),然后查看 ThreadsProcess 字段。
1 2 3 4 5
| dt _ETHREAD 85ac2b88-60 nt!_ETHREAD ... +0x220 ThreadsProcess : 0x8603f2d8 _EPROCESS // 所属进程 ...
|
三、 查看进程结构体,确认进程名。
1 2 3 4 5
| dt _EPROCESS 0x8603f2d8 nt!_EPROCESS ... +0x174 ImageFileName : [16] "svchost.exe" ...
|
# 调度链表
调度链表(Dispatcher Ready List) 用于存储就绪状态的线程队列,调度器根据优先级从中选择下一个要执行的线程。
- 调度链表共有 32 个双向链表(对应 32 个优先级,0~31)
- 线程在调度链表中的下标即表示其线程优先级
- 在 Windows XP 中有 32 个级别;在 64 位操作系统(如 Win7 x64)中同样为 32 个级别(优先级范围仍为 0~31)
- 在单核 CPU 系统中,无论 XP 还是 Win7,都只有一组调度链表
- 在多处理器(服务器版本) 系统中,等待链表只有一个,但调度链表的数量等于 CPU 的数量
dd KiDispatcherReadyListHead L70
# 总结
- 线程总数 = 等待链表中的线程 + 各 CPU 调度链表中的线程 + 正在运行的线程
- 无论是等待链表还是调度链表,在线程结构体中都使用
_KTHREAD+0x060 ( WaitListEntry )这个位置进行挂载
# 实验:模拟线程切换
下面通过一个用户态程序来模拟 Windows 的线程切换机制,帮助理解线程调度的核心原理。
# 设计思路
在 Windows 中,线程在不同状态下位于不同的位置:
- 正在运行的线程位于 KPCR
- 等待中的线程位于等待链表
- 就绪的线程位于调度链表
本模拟程序使用一个数组来统一管理所有线程:
- 下标 0:
main 函数线程(空闲线程)
- 下标 1 及之后:用户创建的线程
# ThreadCore.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| #if !defined(AFX_THREADCORE_H__INCLUDED_) #define AFX_THREADCORE_H__INCLUDED_
#if _MSC_VER > 1000 #pragma once #endif
#define MAXGMTHREAD 0x100
#define GMTHREAD_CREATE 0x01 #define GMTHREAD_READAY 0x02 #define GMTHREAD_RUNING 0x04 #define GMTHREAD_SLEEP 0x08 #define GMTHREAD_SUSPEND 0x10 #define GMTHREAD_EXIT 0x100
typedef struct { char* name; int Flags; int SleepMillisecondDot;
void* InitialStack; void* StackLimit; void* KernelStack;
void* lpParameter; void (*func)(void* lpParameter);
} GMThread_t;
extern GMThread_t GMThreadList[MAXGMTHREAD];
void IdleGMThread(void* lpParameter); void GMThreadStartup(GMThread_t* GMThreadp); void initGMThread(GMThread_t* GMThreadp, char* name, void (*func)(void* lpParameter), void* lpParameter); int RegisterGMThread(char* name, void (*func)(void* lpParameter), void* lpParameter); void Scheduling(void); void GMSleep(int Milliseconds); void ThreadPolling();
void Thread1(void* lpParameter); void Thread2(void* lpParameter); void Thread3(void* lpParameter); void Thread4(void* lpParameter);
#endif
|
# ThreadCore.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
| #include "stdio.h" #include "windows.h" #include "ThreadCore.h"
#define _SELF "仿Windows线程切换"
int CurrentThreadindex = 0; GMThread_t GMThreadList[MAXGMTHREAD] = { NULL, 0 };
#define GMTHREADSTACKSIZE 0x80000
void* WindowsStackLimit = NULL;
__declspec(naked) void SwitchContext(GMThread_t* SrcGMThreadp, GMThread_t* DstGMThreadp) { __asm { push ebp mov ebp, esp push edi push esi push ebx push ecx push edx push eax
mov esi, SrcGMThreadp mov edi, DstGMThreadp
mov [esi + GMThread_t.KernelStack], esp
mov esp, [edi + GMThread_t.KernelStack]
pop eax pop edx pop ecx pop ebx pop esi pop edi pop ebp ret } }
void GMThreadStartup(GMThread_t* GMThreadp) { GMThreadp->func(GMThreadp->lpParameter);
GMThreadp->Flags = GMTHREAD_EXIT;
Scheduling(); }
void IdleGMThread(void* lpParameter) { Scheduling(); }
void PushStack(unsigned int** Stackpp, unsigned int v) { *Stackpp -= 1; **Stackpp = v; }
void initGMThread(GMThread_t* GMThreadp, char* name, void (*func)(void* lpParameter), void* lpParameter) { unsigned char* StackPages; unsigned int* StackDWORDParam;
GMThreadp->Flags = GMTHREAD_CREATE; GMThreadp->name = name; GMThreadp->func = func; GMThreadp->lpParameter = lpParameter;
StackPages = (unsigned char*)VirtualAlloc(NULL, GMTHREADSTACKSIZE, MEM_COMMIT, PAGE_READWRITE); memset(StackPages, 0, GMTHREADSTACKSIZE);
GMThreadp->InitialStack = (StackPages + GMTHREADSTACKSIZE - 0x10); GMThreadp->StackLimit = StackPages;
StackDWORDParam = (unsigned int*)GMThreadp->InitialStack;
PushStack(&StackDWORDParam, (unsigned int)GMThreadp); PushStack(&StackDWORDParam, (unsigned int)9); PushStack(&StackDWORDParam, (unsigned int)GMThreadStartup); PushStack(&StackDWORDParam, 5); PushStack(&StackDWORDParam, 7); PushStack(&StackDWORDParam, 6); PushStack(&StackDWORDParam, 3); PushStack(&StackDWORDParam, 2); PushStack(&StackDWORDParam, 1); PushStack(&StackDWORDParam, 0);
|
构造完成后的堆栈布局如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
| GMThreadp->KernelStack = StackDWORDParam; GMThreadp->Flags = GMTHREAD_READAY; }
int RegisterGMThread(char* name, void (*func)(void* lpParameter), void* lpParameter) { int i;
if (GMThreadList[0].name == NULL) { initGMThread(&GMThreadList[0], "IDLE GM Thread", IdleGMThread, NULL); }
for (i = 1; GMThreadList[i].name; i++) { if (0 == stricmp(GMThreadList[i].name, name)) break; }
initGMThread(&GMThreadList[i], name, func, lpParameter);
return (i | 0x55AA0000); }
void Scheduling(void) { int i; int TickCount; GMThread_t* SrcGMThreadp; GMThread_t* DstGMThreadp;
TickCount = GetTickCount();
SrcGMThreadp = &GMThreadList[CurrentThreadindex]; DstGMThreadp = &GMThreadList[0];
for (i = 1; GMThreadList[i].name; i++) { if (GMThreadList[i].Flags & GMTHREAD_SLEEP) { if (TickCount > GMThreadList[i].SleepMillisecondDot) { GMThreadList[i].Flags = GMTHREAD_READAY; } }
if (GMThreadList[i].Flags & GMTHREAD_READAY) { DstGMThreadp = &GMThreadList[i]; break; } }
CurrentThreadindex = DstGMThreadp - GMThreadList; SwitchContext(SrcGMThreadp, DstGMThreadp); }
void GMSleep(int Milliseconds) { GMThread_t* GMThreadp; GMThreadp = &GMThreadList[CurrentThreadindex];
if ((GMThreadp->Flags & GMTHREAD_SUSPEND) == 0) { GMThreadp->SleepMillisecondDot = GetTickCount() + Milliseconds; GMThreadp->Flags = GMTHREAD_SLEEP; }
Scheduling(); }
void ThreadPolling() { unsigned char StackPage[GMTHREADSTACKSIZE]; memset(StackPage, 0, GMTHREADSTACKSIZE); IdleGMThread(StackPage); }
void Thread1(void* lpParameter) { int i; for (i = 0; i < 3; i++) { printf("[仿Windows线程切换]: Thread1\n"); GMSleep(1000); } }
void Thread2(void* lpParameter) { for (;;) { printf("[仿Windows线程切换]: Thread2\n"); GMSleep(500); } }
void Thread3(void* lpParameter) { for (;;) { printf("[仿Windows线程切换]: Thread3\n"); GMSleep(800); } }
void Thread4(void* lpParameter) { for (;;) { printf("[仿Windows线程切换]: Thread4\n"); GMSleep(200); } }
|
# ThreadSwitch.cpp(主函数)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #include <stdio.h> #include <windows.h> #include "ThreadCore.h"
int main(int argc, char* argv[]) { RegisterGMThread("Thread1", Thread1, NULL); RegisterGMThread("Thread2", Thread2, NULL); RegisterGMThread("Thread3", Thread3, NULL); RegisterGMThread("Thread4", Thread4, NULL);
for (;;) { Sleep(20); ThreadPolling(); }
return 0; }
|
# 核心原理说明
- SwitchContext 函数是整个模拟的核心,它通过切换
ESP 实现线程切换 —— 保存当前线程的 ESP 到 KernelStack ,然后将 ESP 切换为目标线程的 KernelStack 。此时堆栈已经属于目标线程, pop 和 ret 指令恢复的是目标线程的寄存器和执行地址。
- GMSleep 函数模拟了 Windows 的
Sleep —— 将线程状态设置为 SLEEP 并记录到期时间,然后触发调度。调度器在遍历时会检查休眠到期的线程,将其重新设置为就绪状态。
- Scheduling 函数模拟了 Windows 的调度器 —— 遍历线程数组,找到第一个就绪线程并切换到它。如果没有就绪线程,则切换到空闲线程(下标 0)。