Windows 95 System Programming SECRENTS学习笔记(五)

类别:编程语言 点击:0 评论:0 推荐:

补充:

    进程的handle保存在自己的process handle table中,系统可以通过hProcess找到对应的process database,那么进程所拥有的线程的handle保存在哪儿?前面提到过,在PDB中有两个位置与此有关:一个用来保存该进程拥有的线程个数,另一个保存ThreadList的表头。个人感觉,hTread应该在process handle table中,因为可以对hThread是用CloseHandle,而且线程也是K32对象。

 

Thread Database

Thread Database是一个K32对象(K32OBJ_THREAD),从KERNEL32共享数据区种分配而来。在本章的示例代码的ThreadDB.h文件中,有关于Thread Database的C语言定义,其格式如下:

00h DWORD Type

此位置为6,表示K32OBJ_THREAD对象(在Windows 2000 or later中是否还如此,未知)

 

04h DWORD cReference

此位置内含线程的引用计数

 

08h PROCESS_DATABASE pProcess

这是一个指针,指向线程所属的进程

 

0Ch DWORD someEvent

一个指针,指向K32OBJ_EVENT对象。Event对象通常被交给WaitForSingleObject。这个Event对象正是你调用WaitForSingleObject函数时给予的Event。

 

10h DWORD pvExcept

这是一个指针,指向结构化异常处理的串链头(结构化异常处理将会在稍后讨论)。请注意,这个位置也标识了Task Database中的TIB(Thread Information Block)巢状结构的起始处。TIB将在稍后讨论。

补充:

   似乎本书中关于TIB结构的描述,在Windows 2000(SP4)中仍然有效,在对本章的学习结束后,我会将原书中本章的例程移植到Windows 2000下。目前估计可能无法完全移植,因为我没有Windows 2000下的Kernel32.Lib的完全版。希望在完成《Undocumented Windows 2000 Secrets》一书的学习后,我能补齐这些内容。2004-12-26

 

14h DWORD TopOfStack

此位置存放的是线程堆栈的最高地址。一般而言,保留给线程的堆栈大小是1MB。

补充:Windows 2000中线程的默认堆栈大小也是1MB。

 

18h DWORD StackLow

此位置存放的是线程堆栈的最低位标记(以page为单位)。把TopOfStack减去StackLow就可以知道线程目前使用了多大的堆栈。

 

1Ch WORD W16TDB

这里存放的是Win 16 task database的selector。

 

1Eh WORD StackSelector16

Win32代码下移(thunk down)至16位代码之前,必须先切换到一个16位堆栈上。这个成员存放的就是该16位堆栈的selector。

 

20h DWORD SelmanList

一个指针,指向线程的SelmanList。“Selman”的含义是“Selector Manager”。KERNEL32中的Selman似乎有责任管理selectors,线程可以从中配置、作为给种用途。

 

24h DWORD UserPointer

此位置的精确意义还不明确。然而TIB结构的文件中说,这个位置可以给应用程序使用。别忘了,TIB结构在Thread Database中是巢状的。

 

28h  PTIB  pTIB

这个位置指向TIB(Thread Information Block)。Windows 95的TIB位于Thread Database之中,所以此指针其实是指向Thread Database的另一个位置:pvExcept位置(偏移地址10h)。

 

Thread Information Block(TIB)

在Thread Database中,有一些成员对于执行中的程序极为有用。事实上,它们是如此的有用,以至于Win32架构让它们可以立刻被取用,而不需要经过Thread Database。这些成员被放置在一个名为Thread Information Block(TIB)的结构中。Thread Database的10h-3Ch成员统统被放置在TIB之中。

 

应用程序如何取得TIB呢?如果你看过Win32程序的汇编代码,你会发现FS寄存器的使用频率非常高(Windows 2000 or later是否如此,目前还未知。2004-12-27)。等一下,Win32不是会移动节区吗?显然答案是Yes,但是Win32底层(包括Windows NT)都使用FS寄存器,用以指向当前正在执行的线程的TIB.Win32并不是第一个这么做的操作系统,OS/2 2.0就已经如此了。就像你所感受到的,是的,当Windows 95切换线程时,调度器必须更改FS寄存器的值,让其包含一个selector,指向新的TIB。

 

FS寄存器和TIB的主要用途就是增加结构化异常处理链(structured exception handing chain)的项目个数。处理链的头部存放在TIB的0偏移处,所以当你看到汇编代码使用FS:[0],你就知道它正在做某些与结构化异常有关的动作了。

 

TIB的另两个成员也被十分广泛的使用,它们是pvQueue和pvTLSArray(分别是28h和2Ch成员)。pvQueue成员存放的是当前线程的消息队列的handle,此成员常常被USER.EXE的窗口系统使用。因为在Windows 95之中,焦点窗口(focus windows)并非整个系统只有一个。pvTLSArray则指向Thread Database中的TLS数组。编译器厂商把它和可执行文件的.tIs section连用,提供透明化的所谓“per-thread global variable”。

 

虽然TIB结构的布局可以从Thread Database中推算出来,我要在这里提供一点摘要。本章的示例代码中的TIB.H中有TIB的C语言描述。微软对此的第一份文档是Windows NT 3.5 DDK提供的NTDDK.H。

 

Windows 95的TIB内容如下:

00h  DWORD  pvExcept

04h  DWORD  TopOfStack

08h  DWORD  StackLow

0Ch  WORD   W16TDB

0Eh  WORD   StackSelector16

10h  DWORD  SelmanList

14h  DWORD  UserPointer

18h  PTIB     pTIB

1Ch  WORD   TIBFlags

1Eh  WORD   Win16MutexCount

20h  DWORD  DebugContext

24h  PDWORD pCurrentPriority

28h  DWORD  MessageQueue

2Ch  DWORD  pTLSArray

 

如果想知道每个成员的意义,把其偏移值增加10h,然后看看上一节的结构内容,就可以知道了。请注意,其中只有一些成员对于其他Win32平台是共同的。

 

补充:

    在Windows NT/2000系统中,线程信息块(Thread Information Block)被定义为NT_TIB结构,在Windows NT/2000 DDK的NTDDK.H中,可以找到该结构的C语言定义。其布局如下:

//

//  NT_TIB - Thread Information Block - Portable part.

//

//      This is the subsystem portable part of the Thread Information Block.

//      It appears as the first part of the TEB for all threads which have

//      a user mode component.

//

//      From Windows 2000 DDK 1999-9-11

 

// begin_winnt

 

typedef struct _NT_TIB {

    struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList;  // SEH链入口

    PVOID StackBase;  // 堆栈基址

    PVOID StackLimit;  // 堆栈大小

    PVOID SubSystemTib;

    union {

        PVOID FiberData;

        ULONG Version;

    };

    PVOID ArbitraryUserPointer;

    struct _NT_TIB *Self;  // 本NT_TIB结构自身的线性地址

} NT_TIB;

typedef NT_TIB *PNT_TIB;

可以看出,Windows 2000中TIB结构与Windows 95有很大的不同,主要是去掉了很多与DOS和Win16相关的东西,加入了纤程等一些新的内容。但有一点是相同的,那就是:TIB永远存放在FS段选择器指定的数据段的0偏移处,所以,FS:[0]的地方就是TIB结构起始位置(也是SEH链的入口地址)。这个特性对于Windows 9x和Windows NT/2000系统都是有效的。

 

由于一个进程中的不同线程可以有不同的context。所以,在不同线程中FS段选择器可以使用不同的值,这种特征使每个线程都可以设置自己的TIB,这对于结构化异常非常有帮助,后面我们将深入地介绍。

 

Thread Priority(线程优先权)

Windows 95的VMM核心线程调度系统并不真正在乎进程,它只在乎线程的优先权,而不管线程属于哪一个进程。换句话说,进程也不真正拥有优先权。当然啦,对于线程调度服务的终端使用者(也就是应用程序)而言,进程有优先权是一种比较有用的抽象想法。

 

任何时候,拥有最高优先权的线程,而且它又没有什么东西要等待的话,将会是即将执行的一个。为了确保平顺的让系统运转并避免许多问题,线程优先权可以动态的被系统改变。例如,当一个线程正在进行I/O动作,它的优先权可以暂时性的提高。更详细的讨论这个题目,会耗掉大半章。

 

Windows 95的VMM线程调度管理器支持32中优先权。这32种优先权被分为四个等级,成为优先权等级(priority classes),每个等级关系到一组优先权。在一个等级之中,优先权可以上下增减2。但是也有特殊情况如THREAD_PRIORITY_LEVEL,它可以使优先权完全跳出其等级的规范之外。除非有特别指定,否则操作系统产生一个进程时给予的优先权等级就是NORMAL_PRIORITY_CLASS。

四个预先设定的优先权等级和范围是:

优先权

默认值

变动范围

IDLE_PRIORITY_CLASS

4

2-6

NORMAL_PRIORITY_CLASS

9或7(前台为9,后台为7)

6-10

HIGH_PRIORITY_CLASS

13

11-15

REALTIME_PRIORITY_CLASS

24

16-31

 

线程优先权为1是一种特殊情况。凡是IDLE_PRIORITY_CLASS、NORMAL_PRIORITY_CLASS或HIGH_PRIORITY_CLASS三个等级,都可以通过SetPriorityClass函数设定优先权为1。

 

注意,Windows 95的32个优先权,其数值并非对于WINBASE.H的定义。例如,NORMAL_PRIORITY_CLASS在WINBASE.H中定义为0x20。KERNEL32.DLL将这些值映射为适当的Windows 95线程调度优先权。

 

GetThreadPriority

GetThreadPriority是一个简单的函数。给予一个Thread Handle(可以是进程中的任何线程),这个函数会把handle转换为指针,指向Thread Database。如果转换没有问题,函数就返回Thread Database中的DeltaPriority成员(198h成员)。所有的代码都被一个EnterSysLevel和一个LeaveSysLevel包裹起来,以避免不适当的线程切换。

 

SetThreadPriority

SetThreadPriority的主体被切割为四个部分。首先,把Thread Database转换为一个Thread Database指针。然后,检查传入的优先权(参数之一),看看是否在合理范围内。然后,再以内部函数CalculateNewPriority将传入的优先权转换为Windows 95线程调度器认识的优先权值。

 

最后,SetThreadPriority调用VWIN32.VXD,将新的优先权通知ring0层。KERNEL32通过VxdCall函数调用ring0层的代码。Ring3层的代码就是使用VxDCall函数才能够调用Win32 VxD的服务。在这个例子中,VWIN32.VXD提供了一个ring3可调用的服务函数(Service),用以设定优先权。Win32 VxD Service是Windows 95的新特性。

补充:

可否认为:在Windows 2000中也存在类似的机制,在Windows 2000中,WDM代替了VXD用来实现ring0层的功能。既然Ring3层的代码可以通过VXD来执行Ring0层的代码,那么在Windows 2000中也应该可以通过WDM来执行ring0级的代码。2004年发布的KV2005号称采用了驱动级杀毒引擎,可能就是利用了这种机制。将杀毒引擎做到了ring0级。

 

SetPriorityClass

调用此函数可以改变线程的优先权等级。它首先把hProcess转换为一个PROCESS_DATABASE指针,然后根据这个指针决定进程的当前优先权等级。如果和原等级相同,就不再做任何事。

 

如果等级和原等级不同,SetPriorityClass会把新等级的默认值写入process database的BasePriority成员中。但是,先等一下,还有呐,稍早我曾说过所谓进程的优先权只是一种概念,因为VMM线程调度器只关心线程,而不是进程。为了在两个视野之间搭起桥梁,SetPriorityClass一一找到进程所拥有的每个线程,然后调用VMM32.VXD为每个线程设定新的优先权。

 

然而有一点需要注意。线程的优先权可以与其优先权等级的标准值有轻微的差别。这个差别放在Thread Database的DeltaPriority成员中。SetPriorityClass必须取得每个线程的优先权差额—当它计算新优先权的时候。CalculateNewPriority负责这样的计算。

 

GetPriorityClass

此函数针对某个进程,返回其优先权等级。在把hProcess转换为一个PROCESS_DATABASE指针后,它从process database结构中取出其优先权等级。优先权的值应该是在1-31之间,那和WINBASE.H中所定义的xxx_PRIORITY_CLASS并不相同。因此,GetPriorityClass必须把VMM线程调度器的优先权等级转换为对应的xxx_PRIORITY_CLASS定义。

补充:

在WINBASE.H中定义的xxxx_PRIORITY_CLASS所对应的值,并不是实际的优先权值,系统会根据这些xxx_PRIORITY_CLASS来计算线程实际的优先权值,其范围是:1-31,在Windows 2000中也是如此。

本文地址:http://com.8s8s.com/it/it23681.htm