0%

Windows核心编程 - 线程调度

最近在学习 Win32编程,所以顺便将每日所学记录下来,一方面为了巩固学习的知识,另一方面也为同样在学习Win32开发的童鞋们提供一份参考。

本系列博文均根据学习《Windows核心编程》一书总结而来;

运行环境:

  • 操作系统: Windows 10家庭版
  • 编译器:Visual Studio 2019

线程调度

进程有提过,Windows是一个抢占式操作系统,Windows必须使用某种算法来确定那些线程应该在合适运行多久。

实际情况是,Windows中大多数线程是不可调度的。

线程暂停与恢复

在线程内核对象的内部有一个值,用于指明线程的暂停计数。当调用创建进程或线程函数时,就创建了线程的内核对象,并且它的暂停计数被初始化为1。这可以方式线程被调度到CPU中。

当线程初始化完成后,CreateProcess或CreateThread要查看是否传递了CREATE_SUSPENDED标志,如果尚未传递,线程的暂停计数为0;当线程暂停计数为0,那么线程是可以被调度的;

暂停状态中创建一个线程,就能够在线程有机会执行任何代码之前改变线程的运行环境。一旦改变了线程的环境,必须使线程成为可调度线程。可以ResumeThread,将句柄传递给它;

1
DWORD ResumeThread( HANDLE hThread); 

如果ResumeThread函数运行成功,它将返回线程的前一个暂停计数,否则返回0xFFFFFFFF

单个线程可以暂停若干次,但暂停多少次,就必须恢复多少次,然后它才会被分配CPU时间片。可以使用SuspendedThread来暂停线程运行;

1
DWORD SuspendThread(HANDLE hThread); 

暂停和恢复进程运行

Windows下,不存在暂停或恢复进程的概念,因为进程永远不会被安排CPU时间片;但是Windows允许一个进程暂停另外一个进程中所有线程的运行;但是从事暂停的进程必须是调试程序。特别是调用是WaitForDebugEvent和ContinueDebugEvent等函数;

由于竞争关系,Windows没有提供其他方法来暂停进程中所有线程的运行;

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
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <tlhelp32.h>
int main()
{
PROCESSENTRY32 pe32;
//在使用这个结构前,先设置它的大小
pe32.dwSize = sizeof(pe32);
//给系统内所有的进程拍个快照
HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
{
printf("CreateToolhelp32Snapshot 调用失败.\n");
return -1;
}
BOOL bMore = ::Process32First(hProcessSnap, &pe32);
while (bMore)
{
printf("进程名称:%ls\n", pe32.szExeFile); //这里得到的应该是宽字符,用%ls,不然无法正常打印
printf("进程ID:%u\n\n", pe32.th32ProcessID);
bMore = ::Process32Next(hProcessSnap, &pe32);
}
//不要忘记清除掉snapshot对象
::CloseHandle(hProcessSnap);

return 0;
}

有bug版本:

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
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <tlhelp32.h>
int SuspendProcess(DWORD dwProcessID, BOOL fSuspend)
{
PROCESSENTRY32 pe32;
//在使用这个结构前,先设置它的大小
pe32.dwSize = sizeof(pe32);
HANDLE hSnapshot;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, dwProcessID);
if (hSnapshot != INVALID_HANDLE_VALUE)
{
THREADENTRY32 te = { sizeof(te) };
BOOL fOk = Thread32First(hSnapshot, &te);
for (; fOk; fOk = Thread32Next(hSnapshot,&te))
{
if (te.th32OwnerProcessID == dwProcessID)
{
HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32OwnerProcessID);
if (hThread != NULL)
{
if (fSuspend)
{
SuspendThread(hThread);
}
else
{
ResumeThread(hThread);
}
}
CloseHandle(hThread);
}
}
CloseHandle(hSnapshot);
}
return 0;
}

DWORD WINAPI ThreadProc(LPVOID pR)
{
printf("------");
return 0;
}

int main()
{
DWORD dThreadID;
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
si.cb = sizeof(si);
TCHAR szCmdLineName[] = TEXT("CMD");
if (!CreateProcess(NULL,
szCmdLineName,
NULL,
NULL,
FALSE,
0, NULL,
NULL,
&si,
&pi))
{
printf("CreateProcess Error = %d\n", GetLastError());
}
SuspendProcess(pi.dwProcessId, FALSE);
return 0;
}



睡眠方式

线程也可以告诉操作系统,它不想在某个时间段内被调度,通过Sleep函数来实现

1
void Sleep(DWORD dwMilliseconds); 

调用Sleep可以放弃CPU时间片,但是唤醒的时候并不一定能直接唤醒,却决于子进程中是否还有操作在运行

切换线程

系统提供了一个SwitchToThread函数,使得另一个可调度线程运行;

1
BOOL SwitchToThread(VOID);
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
#include <windows.h>
#include <stdio.h>
#include <tchar.h>


DWORD WINAPI ThreadProc1(LPVOID pR)
{
for (size_t i = 0; i < 10; i++)
{
printf("-------\n");
}

return 0;
}

DWORD WINAPI ThreadProc2(LPVOID pR)
{
for (size_t i = 0; i < 10; i++)
{
printf("+++++++\n");
}

return 0;
}


int main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
SwitchToThread();
hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);

CloseHandle(hThread1);
CloseHandle(hThread2);
getchar();
return 0;
}

环境结构

环境结构使得系统能够记忆线程的状态,这样,下次当线程拥有CPU时间片的时候,它就可以找到上次中断运行的地方;

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
typedef struct DECLSPEC_NOINITALL _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;

Demo:

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
#include <windows.h>
#include <stdio.h>
#include <tchar.h>


DWORD WINAPI ThreadProc1(LPVOID pR)
{
for (size_t i = 0; i < 10; i++)
{
printf("-------\n");
}

return 0;
}

DWORD WINAPI ThreadProc2(LPVOID pR)
{
for (size_t i = 0; i < 10; i++)
{
printf("+++++++\n");
}

return 0;
}


int main()
{
HANDLE hThread1;
HANDLE hThread2;
hThread1 = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);

hThread2 = CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
SuspendThread(hThread1);
CONTEXT co;
co.ContextFlags = CONTEXT_FULL; //标识整数寄存器
GetThreadContext(hThread1, &co);
printf("%x %x %x %x\n", co.Eax, co.Ebx, co.Ecx, co.Edx);
SetThreadContext(hThread1,&co);
co.Eax = 0x100;
printf("%x %x %x %x\n", co.Eax, co.Ebx, co.Ecx, co.Edx);
ResumeThread(hThread1);
CloseHandle(hThread1);
CloseHandle(hThread2);
getchar();
return 0;
}


我们可以通过SetThreadContext函数来设置上下文;

线程优先级