Windows核心编程笔记(5)----线程调度,优先级

1、操作系统线程调度过程

每个线程都有一个上下文CONTEXT结构体,保存在线程的内核对象中,这个上下文中保存了线程上一次执行时CPU寄存器

的状态。每隔固定时间,Windows会查看所有当前存在的线程内核对象,其中只有一些是可调度的。Windows在可调度的

线程中选择一个,并将上次保存到线程上下文中的数据载入CPU寄存器中。(上下文切换)

CPU时间片到后,Windows移出这个线程,把CPU寄存器信息保存到线程上下文中,切换到另一个线程,如此循环。

2、线程的挂起和恢复

调用CreateProcess或者CreateThread时,系统将创建线程内核对象,并将挂起计数初始化为1,这样系统就不会给这个线

程调度CPU了。在线程初始化完成后,函数将查看是否有CREATE_SUSPENDED标志传入。如果有,函数返回并让新的线程处于

挂起状态;没有,函数会将线程的挂起计数递减为0,挂起计数为0时,线程就成为可调度的了。

使用ResumeThread函数恢复线程至可调度状态,该函数返回线程的前一个挂起计数,失败返回0xffffffff。

SuspendThread挂起线程,一个线程最多可以挂起127次。SuspendThread是异步的,只有在确切知道线程正在做什么,而且

采用完备措施避免出现因挂起线程而引起的问题或者死锁时,调用SuspendThread才是安全的。

3、睡眠

Sleep函数告诉系统线程在多少时间内不需要调度,线程自动放弃CPU时间片剩余时间。由于Windows并不是实时操作系统

睡眠时间不一定准。

Sleep(0)则是告诉系统,当前线程主动放弃CPU时间片剩余时间,强制系统调用其他线程。如果没有相同或者优先级更高的

可调度线程,系统会重新调度这个线程。

4、切换到另一个线程

SwitchToThread切换线程,系统查看是否有正急需CPU的线程。如果没有,函数立即返回;如果存在,SwitchToThread将

调度该线程,继续运行的线程可以执行一个CPU时间片,然后系统调度恢复原线程。

SwitchToThread与Sleep(0)

SwitchToThread允许执行低优先级的线程,Sleep(0)则是即使有优先级低的饥饿线程也不会被调度。

5、线程上下文结构CONTEXT

系统使用CONTEXT记录线程的状态,这样在线程下次执行时,就可以从上下文停止处继续执行。

在Windows定义的所有结构体中,CONTEXT是唯一一个特定于CPU的。

可以使用GetThreadContext获取线程内核的上下文,使用前需要先挂起线程,否则获取的CONTEXT信息就与当前运行线程的

上下文不一致了。一个线程有两个上下文模式:用户模式和内核模式,GetThreadContext获取的是用户模式的上下文信息。

6、

较高优先级的线程总会抢占较低优先级的线程,无论低优先级线程是否正在运行。系统启动时,将创建一个页面清零线程(

zero page thread),这个线程的优先级为0。页面清零线程负责在没有其他线程需要执行时,将系统内存中的所有闲置页

面清零。

7、调度I\O请求优先级

如果一个低优先级的线程获得CPU时间,可以很轻易在短时间内将成百上千个I\O请求入列,I\O请求一般需要时间处理,因

此低优先级线程可能会挂起高优先级线程,使它们无法完成任务,从而显著影响系统的响应性。

可以通过SetThreadPriority传入THREAD_MODE_BACKGROUND_BEGIN属性,告诉Windows线程应该发送低优先级的I\O请求,这

也将降低线程的CPU调度优先级。通过设置THREAD_MODE_BACKGROUND_END属性,可以恢复线程的优先级。

系统不允许线程改变另一个线程的I\O请求优先级。

同样的,可以通过SetPriorityClass传入PROCESS_MODE_BACKGROUND_BEGIN属性,设置进程内所有线程进行低优先级的I\O

请求和CPU调度。PROCESS_MODE_BACKGROUND_END属性来恢复。

系统不允许进程改变另一个进程的I\O请求优先级。

测试代码

挂起一个进程里的所有线程,使用需注意(可能在遍历后有新的线程创建、销毁)
void SuspendProcess(DWORD dwPid, BOOL bSuspend)
{
	HANDLE hSnapshort = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, dwPid);
	if ( INVALID_HANDLE_VALUE != hSnapshort )
	{
		THREADENTRY32 te = { sizeof(THREADENTRY32) };
		BOOL bRet = Thread32First(hSnapshort, &te);
		while( bRet )
		{
			if ( te.th32OwnerProcessID == dwPid )
			{
				HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME, FALSE, te.th32ThreadID);
				if ( NULL == hThread )
					continue;
				if ( bSuspend )
					SuspendThread(hThread);
				else
					ResumeThread(hThread);
				CloseHandle(hThread);
			}
			bRet = Thread32Next(hSnapshort, &te);
		}
		CloseHandle(hSnapshort);
	}
}

设置线程优先级,获取线程上下文信息

	HANDLE hThread = CreateThread(NULL, 0, TestThread, 0, CREATE_SUSPENDED, NULL);
	SetThreadPriority(hThread, THREAD_PRIORITY_IDLE|THREAD_MODE_BACKGROUND_BEGIN);
	ResumeThread(hThread);
	CloseHandle(hThread);
	SuspendThread(hThread);
	SuspendThread(hThread);
	DWORD dwCount = ResumeThread(hThread);
	WaitForSingleObject(hThread, INFINITE);
	CloseHandle(hThread);
	SuspendProcess(316, TRUE);
	SuspendProcess(316, FALSE);
	CONTEXT context;
	memset(&context, 0, sizeof(CONTEXT));
	context.ContextFlags = CONTEXT_INTEGER;//CONTEXT_CONTROL;
	SuspendThread(GetCurrentThread());
	GetThreadContext(GetCurrentThread(), &context);
时间: 2024-08-01 20:38:12

Windows核心编程笔记(5)----线程调度,优先级的相关文章

Windows核心编程笔记(1)

最近工作比较闲了,一直没来得及看的核心编程最近开始看了,分享下笔记. 1.内核句柄用完不释放一定会造成内存泄漏吗? 不一定,内核句柄在进程退出时会被系统释放掉(遍历内核句柄表,只要每个句柄指向的内核对象的引用计数为0,内核就会销毁该对象,适用于所有的内核对象.资源(GDI对象在内).内存块): 2.内核对象如何关闭? 调用CloseHandle(),内核会查找该进程的句柄表,如果没找到该句柄,返回FALSE(Debug下抛出异常);如果找到,则使该句柄指向的内核对象引用计数减一,若引用计数为0,

Windows核心编程笔记(6)----用户模式下的线程同步

1.原子锁 使用InterlockedExchangeAdd函数来实现原子增长,InterlockedExchange\InterlockedExchangePointer用来交换两个变 量的值,InterlockedCompareExchange对比数值,相等则交换(对应的InterlockedCompareExchangePointer).对应的 还有64位函数. InterlockedIncrement\InterlockedDecrement是比较老的函数,只能增加或递减1,Interl

Windows核心编程笔记(7)----内核模式下的线程同步

1.内核对象同步与用户模式下同步对比 使用内核对象的唯一缺点就是性能,调用内核对象函数时,调用线程必须从用户模式切换到内核模式,这种切换是相当 耗时的. 内核对象(进程.线程.作业)要么处于触发态,要么处于未触发状态.进程内核对象在创建时总是处于未触发状态, 当进程终止时,操作系统会自动使进程内核对象变成触发状态.当进程内核对象处于触发状态后,将永远保持这种状态, 再也不能变回未触发状态. 2.等待内核对象 WaitForSingleObject等待单个内核对象,WaitForMultipleO

Windows核心编程笔记(2)

6 进程实例句柄 6.1 每一个EXE或者DLL被加载到内存中后,都会被赋予一个独一无二的句柄(HINSTANCE),该句柄在WinMain函数调用时传入.获取应用程序相关信息(资源.路径)时,有的需要传入HINSTANC有的需要传入HMODULE,实际上HINSTANC与HMODULE完全是一回事,这是16位Windows系统上不同数据类型造成的. WinMain函数的第一个参数:实例句柄是如何传递进来的呢?查看crtexe.c源码,我们会看到如下代码 #ifdef WPRFLAG mainr

Windows核心编程笔记(3)--作业

/*1.如果进程已经与一个作业相关联,就无法将当前进程及其任何子进程从作业中移除,这个安全特性可以保证 /* 进程无法摆脱对它施加的限制. /*2.在调试程序时,调试器是从资源管理器启动的,程序会从调试器继承带"PCA"前缀的作业.因此,调试程序 /* 时总是显示进程已经加入了作业.使用命令行来运行程序时就不会有这个问题了. /*3.关闭一个作业对象,并不会终止作业内所有的进程.作业对象实际上只是加了一个删除标记,只有在作业中 /* 所有进程都终止后,才会自动销毁. /*4.可以向作业

Windows核心编程笔记(4)----线程

1.进程与线程 进程是惰性的,从来不执行任何东西,它只是一个线程的容器.线程必定是在某个进程的上下文中创建的, 而且其生命周期都在该进程中.因为句柄表是针对每一个进程的,因此同一个进程中的多个线程可以共享 内核对象句柄.进程运行需要占用许多的内存资源(加载DLL等),进程只需要一个内核对象和一个进程栈, 无需占用多少内存. 2.终止线程的几种方式: 2.1线程函数返回(强烈推荐) 2.2通过ExitThread函数杀死自己(自杀,不推荐) 终止线程运行,操作系统清理线程使用的系统资源,但是C/C

【转】《windows核心编程》读书笔记

这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入,但应该是合理的.开头几章由于我追求简洁,往往是很多单独的字句,后面的内容更为连贯. 海量细节. 第1章    错误处理 1.         GetLastError返回的是最后的错误码,即更早的错误码可能被覆盖. 2.         GetLastError可能用于描述成功的原因(CreatEvent)

《Windows核心编程》读书笔记 上

[C++]<Windows核心编程>读书笔记 这篇笔记是我在读<Windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和Windows实际机制可能有出入,但应该是合理的.开头几章由于我追求简洁,往往是很多单独的字句,后面的内容更为连贯. 海量细节. 第1章    错误处理 1.         GetLastError返回的是最后的错误码,即更早的错误码可能被覆盖. 2.         GetLas

C++Windows核心编程读书笔记

转自:http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E6%96%87/71405.shtml "C++Windows核心编程读书笔记": 关键词:c++windows 核心 编程 读书笔记 这篇笔记是我在读<windows核心编程>第5版时做的记录和总结(部分章节是第4版的书),没有摘抄原句,包含了很多我个人的思考和对实现的推断,因此不少条款和windows实际机制可能有出入,但应该是合理的.开头几章由于我追求简洁