# 等待链表

等待链表(Wait List) 用于存储正在等待某个资源或事件(如锁、信号量等)的线程队列,当资源可用时,系统会从等待链表中唤醒线程。

  1. 等待链表是一个双向链表
  2. 它有两个成员 —— Flink (Forward Link,指向下一个节点)和 Blink (Backward Link,指向上一个节点)
  3. 当线程调用了 SleepWaitForSingleObject 等函数时,线程会被挂到这个链表上

dd KiWaitListHead

线程在等待链表和调度链表中的挂载位置是同一个字段:

1
2
3
4
5
6
kd> dt _KTHREAD
ntdll!_KTHREAD
...
+0x060 WaitListEntry : _LIST_ENTRY // 等待链表 / 调度链表共用此节点
+0x060 SwapListEntry : _SINGLE_LIST_ENTRY
...

# 实验:分析等待链表中的线程所属进程

一、 查看等待链表。

dd KiWaitListHead

二、 从链表中取出第一个节点,减去 0x60WaitListEntryKTHREAD 中的偏移)得到 ETHREAD 起始地址( KTHREADETHREAD 的第一个成员,偏移为 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) 用于存储就绪状态的线程队列,调度器根据优先级从中选择下一个要执行的线程。

  1. 调度链表共有 32 个双向链表(对应 32 个优先级,0~31)
  2. 线程在调度链表中的下标即表示其线程优先级
  3. 在 Windows XP 中有 32 个级别;在 64 位操作系统(如 Win7 x64)中同样为 32 个级别(优先级范围仍为 0~31)
  4. 单核 CPU 系统中,无论 XP 还是 Win7,都只有一组调度链表
  5. 多处理器(服务器版本) 系统中,等待链表只有一个,但调度链表的数量等于 CPU 的数量

dd KiDispatcherReadyListHead L70

# 总结

  1. 线程总数 = 等待链表中的线程 + 各 CPU 调度链表中的线程 + 正在运行的线程
  2. 无论是等待链表还是调度链表,在线程结构体中都使用 _KTHREAD+0x060WaitListEntry )这个位置进行挂载

# 实验:模拟线程切换

下面通过一个用户态程序来模拟 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 // 已退出

// 线程结构体(模拟 KTHREAD 关键字段)
typedef struct
{
char* name; // 线程名(相当于 TID)
int Flags; // 线程状态
int SleepMillisecondDot; // 休眠到期时间

void* InitialStack; // 线程堆栈栈底(EBP 端)
void* StackLimit; // 线程堆栈界限
void* KernelStack; // 线程堆栈当前栈顶(ESP)

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 // 每个线程的堆栈大小:512KB

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 // 目标线程结构体指针

// 保存当前 ESP 到源线程的 KernelStack
mov [esi + GMThread_t.KernelStack], esp

//========== 经典堆栈切换:另一个线程"复活" ==========
// 将 ESP 切换到目标线程的 KernelStack
mov esp, [edi + GMThread_t.KernelStack]

// 从目标线程堆栈中恢复寄存器
pop eax
pop edx
pop ecx
pop ebx
pop esi
pop edi
pop ebp
ret // EBP 之后是 GMThreadStartup 函数地址
}
}

//-----------------------------------------------------------------------
// 线程启动函数:执行线程函数,结束后触发调度
//-----------------------------------------------------------------------

void GMThreadStartup(GMThread_t* GMThreadp)
{
// 执行线程函数
GMThreadp->func(GMThreadp->lpParameter);

// 线程函数执行结束,标记为 EXIT 状态
GMThreadp->Flags = GMTHREAD_EXIT;

// 触发线程切换
Scheduling();
}

//-----------------------------------------------------------------------
// 空闲线程:当没有就绪线程时执行
//-----------------------------------------------------------------------

void IdleGMThread(void* lpParameter)
{
Scheduling();
}

//-----------------------------------------------------------------------
// 辅助函数:向堆栈压入一个值
//-----------------------------------------------------------------------

void PushStack(unsigned int** Stackpp, unsigned int v)
{
*Stackpp -= 1; // ESP 减 4 字节
**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;

// 构造初始堆栈(模拟 SwitchContext 的 ret 和 pop 过程)
PushStack(&StackDWORDParam, (unsigned int)GMThreadp); // 参数:线程结构体指针
PushStack(&StackDWORDParam, (unsigned int)9); // 占位(平衡堆栈用)
PushStack(&StackDWORDParam, (unsigned int)GMThreadStartup); // ret 返回地址
PushStack(&StackDWORDParam, 5); // push ebp
PushStack(&StackDWORDParam, 7); // push edi
PushStack(&StackDWORDParam, 6); // push esi
PushStack(&StackDWORDParam, 3); // push ebx
PushStack(&StackDWORDParam, 2); // push ecx
PushStack(&StackDWORDParam, 1); // push edx
PushStack(&StackDWORDParam, 0); // push eax

构造完成后的堆栈布局如下:

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;     // 保存栈顶(ESP)
GMThreadp->Flags = GMTHREAD_READAY; // 设置为就绪状态
}

//-----------------------------------------------------------------------
// 注册新线程
//-----------------------------------------------------------------------

int RegisterGMThread(char* name, void (*func)(void* lpParameter), void* lpParameter)
{
int i;

// 若空闲线程(下标 0)未初始化,先初始化
if (GMThreadList[0].name == NULL)
{
initGMThread(&GMThreadList[0], "IDLE GM Thread", IdleGMThread, NULL);
}

// 从下标 1 开始查找空闲位置
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); // 执行上下文切换
}

//-----------------------------------------------------------------------
// 模拟 Sleep:设置休眠时间并触发调度
//-----------------------------------------------------------------------

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)
{
// 执行 3 次后退出
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);

// 主循环:每 20ms 触发一次线程调度
for (;;)
{
Sleep(20);
ThreadPolling();
}

return 0;
}

# 核心原理说明

  1. SwitchContext 函数是整个模拟的核心,它通过切换 ESP 实现线程切换 —— 保存当前线程的 ESPKernelStack ,然后将 ESP 切换为目标线程的 KernelStack 。此时堆栈已经属于目标线程, popret 指令恢复的是目标线程的寄存器和执行地址。
  2. GMSleep 函数模拟了 Windows 的 Sleep —— 将线程状态设置为 SLEEP 并记录到期时间,然后触发调度。调度器在遍历时会检查休眠到期的线程,将其重新设置为就绪状态。
  3. Scheduling 函数模拟了 Windows 的调度器 —— 遍历线程数组,找到第一个就绪线程并切换到它。如果没有就绪线程,则切换到空闲线程(下标 0)。