7线程的调度优先级和亲缘性(3)

2019-05-18 23:22

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;

C O N T E X T结构可以分成若干个部分。C O N T E X T _ C O N T R O L包含C P U的控制寄存器,比如指令指针、堆栈指针、标志和函数返回地址(与x 8 6处理器不同,Alpya CPU在调用函数时,将该函数的返回地址放入一个寄存器中)。C O N T E X T _ I N T E G E R用于标识C P U的整数寄存器。C O N T E X T _ F L O AT I N G _ P O I N T用于标识C P U的浮点寄存器。C O N T E X T _ S E G M E N T S用于标识C P U的段寄存器(仅为x 8 6处理器)。CONTEXT_DEBUG_ REGISTER用于标识C P U的调试寄存器(仅为x 8 6处理器)。CONTEXT_EXTENDED_ REGISTERS用于标识C P U的扩展寄存器(仅为x 8 6处理器)。 Wi n d o w s实际上允许查看线程内核对象的内部情况,以便抓取它当前的一组C P U寄存器。若要进行这项操作,只需要调用G e t T h r e a d C o n t e x t函数:

BOOL GetThreadContext(HANDLE hThread, PCONTEXT pContext);

若要调用该函数,只需指定一个C O N T E X T结构,对某些标志(该结构的C o n t e x t F l a g s成员)进行初始化,指明想要收回哪些寄存器,并将该结构的地址传递给G e t T h r e a d C o n t e x t。然后该函数将数据填入你要求的成员。

在调用G e t T h r e a d C o n t e x t函数之前,应该调用S u s p e n d T h r e a d,否则,线程可能被调度,而且线程的环境可能与你收回的不同。一个线程实际上有两个环境。一个是用户方式,一个是内核方式。G e t T h r e a d C o n t e x t只能返回线程的用户方式环境。如果调用S u s p e n d T h r e a d来停止线程的运行,但是该线程目前正在用内核方式运行,那么,即使S u s p e n d T h r e a d实际上尚未暂停该线程的运行,它的用户方式仍然处于稳定状态。线程在恢复用户方式之前,它无法执行更多的用户方式代码,因此可以放心地将线程视为处于暂停状态, G e t T h r e a d C o n t e x t函数将能正常运行。

C O N T E X T结构的C o n t e x t F l a g s成员并不与任何C P U寄存器相对应。无论是何种C P U结构,该成员存在于所有C O N T E X T结构定义中。C o n t e x t F l a g s成员用于向G e t T h r e a d C o n t e x t函数指明你想检索哪些寄存器。例如,如果想要获得线程的控制寄存器,可以编写下面的代码:

//Create a CONTEXT structure. CONTEXT Context;

//Tell the system that we are interested in only the //control registers.

Context.ContextFlags = CONTEXT_CONTROL;

//Tell the system to get the registers associated with a thread. GetThreadContext(hThread,&Context);

//The control register members in the CONTEXT structure //reflect the thread's control registers. The other members //are undefined.

注意,在调用G e t T h r e a d C o n t e x t之前,首先必须对C O N T E X T结构中的C o n t e x t F l a g s成员进行初始化。如果想要获得线程的控制寄存器和整数寄存器,应该像下面这样对C o n t e x F l a g s进行初始化:

//Tell the system that we are interested //in the control and integer registers.

Context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER;

下面是一些标识符,使用这些标识符可以获得线程的所有重要的寄存器(即M i c r o s o f t视为最常用的那些寄存器):

//Tell the system we are interested in // the important registers.

Context.ContextFlags = CONTEXT_FULL;

在Wi n N T. h文件中,定义了C O N T E X T _ F U L L,请看表7 - 2。

表7-2 CONTEXT-F U L L的意义

C P U类型 X86 C O N T E X T _ F U L L的定义 CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS A l p h a CONTEXT_CONTROL | CONTEXT_FLOATING_POINT | CONTEXT_INTEGER 当G e t T h r e a d C o n t e x t返回时,能够很容易地查看线程的任何寄存器的值,但是要记住,这意味着必须编写与C P U相关的代码。表7 - 3根据C P U类型列出了C O N T E X T结构的指令指针和堆栈指针。

表7-3 CONTEXT 结构的指令指针和堆栈指针

C P U类型 X 8 6 A l p h a 指令指针 C O N T E X T. E i p C O N T E X T. F i r 堆栈指针 C O N T E X T. E s p C O N T E X T. I n t S p Wi n d o w s为编程人员提供了多么强大的功能啊!如果你认为它确实不错,那么你一定会喜欢

它的,因为Wi n d o w s使你能够修改C O N T E X T结构中的成员,然后通过调用S e t T h r e a d C o n t e x t将新寄存器值放回线程的内核对象中:

BOOL SetThreadContext(HANDLE hThread, CONST CONTEXT *pContext);

同样,修改其环境的线程应该首先暂停,否则其结果将无法预测。

在调用S e t T h r e a d C o n t e x t之前,必须再次对C O N T E X T的C o n t e x t F l a g s成员进行初始化,如下面的代码所示:

CONTEXT Context;

//Stop the thread from running. SuspendThread(hThread);

//Get the thread's context registers. Context.ContextFlags = CONTEXT_CONTROL; GetThreadContext(hThread, &Context);

//Make the instruction pointer point //to the address of your choice.

//Here I've arbitrarily set the address //instruction pointer to 0x00010000.

#if defined(_ALPHA_)

Context.Fir = 0x00010000; #elif defined(_X86_)

Context.Eip = 0x00010000; #else

#error Module contains CPU-specific code;modify and recompile. #endif

//Set the thread's registers to reflect the changed values. //It's not really necessary to reset the ControlFlags member //because it was set earlier.

Context.ControlFlags = CONTEXT_CONTROL; SetThreadContext(hThread, &Context);

//Resuming the thread will cause it to begin execution //at address 0x00010000. ResumeThread(hThread);

这有可能导致远程线程中的访问违规,向用户显示未处理的异常消息框,同时,远程进程终止运行。你将成功地终止另一个进程的运行,而你的进程则可以继续很好地运行。

G e t T h r e a d C o n t e x t和S e t T h r e a d C o n t e x t函数使你能够对线程进行许多方面的控制,但是在使用它们时应该小心。实际上,几乎没有应用程序调用这些函数。增加这些函数是

为了增强调试程序和其他工具的功能。任何应用程序都可以调用它们。 第2 4章将详细地介绍C O N T E X T结构。

7.7 线程的优先级

本章开头讲述了C P U是如何只使线程运行2 0 m s,然后调度程序将另一个可调度的线程分配给C P U的。如果所有线程具有相同的优先级,那么就会发生这种情况,但是,在现实环境中,线程被赋予许多不同的优先级,这会影响到调度程序将哪个线程取出来作为下一个要运行的线程。 每个线程都会被赋予一个从0(最低)到3 1(最高)的优先级号码。当系统确定将哪个线程分配给C P U时,它首先观察优先级为3 1的线程,并以循环方式对它们进行调度。如果优先级为3 1的线程可以调度,那么就将该线程赋予一个C P U。在该线程的时间片结束时,系统要查看是否还有另一个优先级为3 1的线程可以运行,如果有,它将允许该线程被赋予一个C P U。 只要优先级为3 1的线程是可调度的,系统就绝对不会将优先级为0到3 0的线程分配给C P U。这种情况称为渴求调度( s t a r v a t i o n)。当高优先级线程使用大量的C P U时间,从而使得低优先级线程无法运行时,便会出现渴求情况。在多处理器计算机上出现渴求情况的可能性要少得多,因为在这样的计算机上,优先级为3 1和优先级为3 0的线程能够同时运行。系统总是设法使C P U保持繁忙状态,只有当没有线程可以调度的时候, C P U才处于空闲状态。

人们可能认为,在这样的系统中,低优先级线程永远得不到机会运行。不过正像前面指出的那样,在任何一个时段内,系统中的大多数线程是不能调度的。例如,如果进程的主线程调用G e t M e s s a g e函数,而系统发现没有线程可以供它使用,那么系统就暂停进程的线程运行,释放该线程的剩余时间片,并且立即将C P U分配给另一个等待运行的线程。

如果没有为G e t M e s s a g e函数显示可供检索的消息,那么进程的线程将保持暂停状态,并且决不会被分配给C P U。但是,当消息被置于线程的队列中时,系统就知道该线程不应该再处于暂停状态。此时,如果没有更高优先级的线程需要运行,系统就将该线程分配给一个C P U。 现在考虑另一个问题。高优先级线程将抢在低优先级线程之前运行,不管低优先级线程正在运行什么。例如,如果一个优先级为5的线程正在运行,系统发现一个高优先级的线程准备要运行,那么系统就会立即暂停低优先级线程的运行(即使它处于它的时间片中),并且将C P U分配给高优先级线程,使它获得一个完整的时间片。

还有,当系统引导时,它会创建一个特殊的线程,称为0页线程。该线程被赋予优先级0,它是整个系统中唯一的一个在优先级0上运行的线程。当系统中没有任何线程需要执行操作时,0页线程负责将系统中的所有空闲R A M页面置0。

7.8 对优先级的抽象说明

当M i c r o s o f t的开发人员设计线程调度程序时,他们发现该调度程序无法在所有时间适应所有人的需要。他们还发现,计算机的“作用”是不断变化的。当Windows NT问世时,对象链接和嵌入(O L E)应用程序还刚刚开始编写。现在, O L E应用程序已经司空见惯。游戏软件已经相当流行。当然,在Windows NT的早期,并没有更多地考虑I n t e r n e t的问题。

调度算法对用户运行的应用程序类型有着相当大的影响。从一开始, M i c r o s o f t的开发人员就认识到,随着系统的用途的变化,他们必须不断修改调度算法。但是,软件开发人员需要在今天编写软件,而M i c r o s o f t则要保证软件能够在将来的系统版本上运行。那么M i c r o s o f t如何改变系统工作的方式并仍然保证软件能够运行呢?下面是解决这个问题的一些办法:

? Microsoft没有将调度程序的行为特性完全固定下来。 ? Microsoft没有让应用程序充分利用调度程序的特性。

? Microsoft声称调度程序的算法是变化的,在编写代码时应有所准备。

Windows API展示了系统的调度程序上的一个抽象层,这样就永远不会直接与调度程序进行通信。相反,要调用Wi n d o w s函数,以便根据运行的系统版本“转换”参数。本章将介绍这个抽象层。 当设计一个应用程序时,你应该考虑到还有什么别的应用程序会与你的应用程序一道运行。然后,应该根据你的应用程序中的线程应该具备何种响应性,选择一个优先级类。这听起来有些费解,不过情况确实如此。M i c r o s o f t不想作出任何将来可能影响你的代码运行的承诺。 Wi n d o w s支持6个优先级类:即空闲、低于正常、正常、高于正常、高和实时。当然,正常优先级是最常用的优先级类, 9 9 %的应用程序均使用这个优先级类。表7 - 4描述了这些优先级类。

表7-4 Windows 支持的优先级类

优先级类 描述 进程中的线程必须立即对事件作出响应,以便执行关键时间的任务。该进程中的线程还会抢先于操作系统组件之前运行。使用本优先级类时必须极端小心 进程中的线程必须立即对事件作出响应,以便执行关键时间的任务。Task Manager(任务管理器)在这个类上运行,以便用户可以撤消脱离控制的进程 进程中的线程在正常优先级与高优先级之间运行(这是Wi n d o w s 2 0 0 0中的新优先级类) 进程中的线程没有特殊的调度需求 进程中的线程在正常优先级与空闲优先级之间运行(这是Wi n d o w s2 0 0 0中的新优先级类) 进程中的线程在系统空闲时运行。该进程通常由屏幕保护程序或后台实用程序和搜集统计数据的软件使用 实时 高 高于正常 正常 低于正常 空闲 当系统什么也不做的时候,将空闲优先级类用于应用程序的运行是最恰当不过的。没有用交互方式使用的计算机有可能仍然很繁忙(比如作为文件服务器),不应该与屏幕保护程序争用C P U时间。定期更新系统的某些状态的统计信息跟踪应用程序不应该干扰关键任务的运行。 只有当绝对必要的时候,才可以使用高优先级类。你会惊奇地发现, Windows Explorer是在高优先级上运行的。大多数时间E x p l o r e r的线程是暂停的,等待用户按下操作键或者点击鼠标按钮时被唤醒。当E x p l o r e r的线程处于暂停状态时,系统不将它的线程分配给C P U。因为这将使低优先级线程得以运行。但是一旦用户按下一个操作键或组合键,如C t r l + E s c,系统就会唤醒E x p l o r e r的线程(当用户按下C t r l + E s c组合键时,也会出现S t a r t菜单)。如果低优先级线程正在运行,系统会立即抢在这些线程的前面,让E x p l o r e r的线程优先运行。 M i c r o s o f t就是按这种方法设计E x p l o r e r的,因为用户希望无论系统中正在运行什么,外


7线程的调度优先级和亲缘性(3).doc 将本文的Word文档下载到电脑 下载失败或者文档不完整,请联系客服人员解决!

下一篇:新课改背景下小学语文教师课堂教学评价语言探究-教育文档

相关阅读
本类排行
× 注册会员免费下载(下载后可以自由复制和排版)

马上注册会员

注:下载文档有可能“只有目录或者内容不全”等情况,请下载之前注意辨别,如果您已付费且无法下载或内容有问题,请联系我们协助你处理。
微信: QQ: