《Windows内核原理与实现笔记》(五)Windows微内核中进程和线程的数据结构KTHREAD,KPROCESS

Windows中进程和线程的数据结构

Windows内核中的执行体层负责各种与管理和策略相关的功能,而内核层(或微内核)实现了操作系统的核心机制。进程和线程在这两层上都有对应的数据结构。

内核层的进程和线程对象

首先看(微)内核层的数据结构,这是进程和线程最为基本的数据结构,分别名为KPROCESS和KTHREAD。

KPROCESS的定义(见base\ntos\inc\ke.h)

typedef struct _KPROCESS {

    //
    // The dispatch header and profile listhead are fairly infrequently
    // referenced.
    //

    DISPATCHER_HEADER Header;           //表明KPROCESS对象也是一个分发器对象(dispatcher object),进程对象本身是可以被等待的
    LIST_ENTRY ProfileListHead;         //用于当该进程参与性能分析(profiling)时,作为一个节点加入到全局的性能分析进程列表(内核全局变量KiProfileListHead)中。

    //
    // The following fields are referenced during context switches.
    //

    ULONG_PTR DirectoryTableBase[2];    //是一个只有两项的数组,其中第一项指向该进程的页目录表地址,第二项指向该进程的超空间(hyper space)的页目录表地址。

#if defined(_X86_)
    //接下来的LdtDescriptor、Int21Descriptor、IopmOffset和Iopl这四个域是专门针对Intel x86处理器的。
    KGDTENTRY LdtDescriptor;            //进程的LDT(局部描述符表)的描述符
    KIDTENTRY Int21Descriptor;          //是为了兼容DOS程序,允许它们通过int 21h指令来调用DOS系统功能
    USHORT IopmOffset;                  //指定了IOPM(I/O权限表,I/O Privilege Map)的位置,内核通过IOPM可控制进程的用户模式I/O访问权限
    UCHAR Iopl;                         //定义了进程的I/O优先级(I/O Privilege Level)
    BOOLEAN Unused;

#endif

#if defined(_AMD64_)

    USHORT IopmOffset;

#endif

    volatile KAFFINITY ActiveProcessors;//记录了当前进程正在哪些处理器上运行

    //
    // The following fields are referenced during clock interrupts.
    //

    ULONG KernelTime;                   //进程对象在内核模式下所花的时间
    ULONG UserTime;                     //进程对象在用户模式下所花的时间

    //
    // The following fields are referenced infrequently.
    //

    LIST_ENTRY ReadyListHead;           //R一个双向链表的表头,该链表记录了这个进程中处于就绪状态但尚未被加入全局就绪链表的线程
    SINGLE_LIST_ENTRY SwapListEntry;    //个单链表项,当一个进程要被换出内存时,它通过此域加入到以KiProcessOutSwapListHead为链头的单链表中;当一个进程要被换入内存时,它通过此域加入到以KiProcessInSwapListHead为链头的单链表中。

#if defined(_X86_)

    PVOID VdmTrapcHandler;              //指向处理Ctrl+C中断的函数,仅用于在VDM(虚拟DOS机,Virtual DOS Machine)环境下运行16位程序

#else

    PVOID Reserved1;

#endif

    LIST_ENTRY ThreadListHead;          //指向一个链表头,此链表包含了该进程的所有当前线程。当一个线程被初始创建的时候,被加入到此链表中,在终止的时候被从链表中移除。由此我们可以看出进程和线程之间的从属关系
    KSPIN_LOCK ProcessLock;             //ProcessLock域,这是一个自旋锁(spin lock)对象,它的用途是保护此进程中的数据成员。
    KAFFINITY Affinity;                 //Affinity域指定了该进程的线程可以在哪些处理器上运行,其类型是KAFFINITY,这是一个32位或64位整数,其二进制表示的每一位分别对应于当前机器上的一个处理器(或核)。

    //
    // N.B. The following bit number definitions must match the following
    //      bit field.
    //
    // N.B. These bits can only be written with interlocked operations.
    //

#define KPROCESS_AUTO_ALIGNMENT_BIT 0
#define KPROCESS_DISABLE_BOOST_BIT 1
#define KPROCESS_DISABLE_QUANTUM_BIT 2

    union {
        struct {                        //x86未进行检查
            LONG AutoAlignment : 1;     //用于该进程中的内存访问对齐设置,此标志位也会被传递到线程的数据结构中,当一个线程的对齐检查开关打开时,该线程中的未对齐数据访问将会导致对齐错误(alignment fault)
            LONG DisableBoost : 1;      //线程调度过程中的优先级提升
            LONG DisableQuantum : 1;    //线程调度过程中的时限(quantum)分配
            LONG ReservedFlags : 29;
        };
   
        LONG ProcessFlags;
    };
    //KPROCESS中的BasePriority和QuantumReset域是该进程中的线程的调度参数,
    SCHAR BasePriority;                 //指定一个进程中的线程的基本优先级,所有的线程在启动时都会继承进程的BasePriority值
    SCHAR QuantumReset;                 //QuantumReset用于指定一个进程中线程的基本时限重置值,在现代Windows版本中此值被设置为6
    UCHAR State;                        //State域说明了一个进程是否在内存中,共有六种可能的状态:ProcessInMemory、ProcessOutOfMemory、ProcessInTransition、ProcessOutTransition、ProcessInSwap和ProcessOutSwap。所谓一个进程在内存中,或者已被换出,或者正在转移过程中,是指该进程的虚拟地址空间需要占据足够的物理内存,或者虚拟空间中的内容已被换出物理内存,或者正在换入或换出过程之中。
    UCHAR ThreadSeed;                   //ThreadSeed域用于为该进程的线程选择适当的理想处理器(IdealProcessor),在每个线程被初始化的时候,都指定此进程的ThreadSeed值作为它的理想处理器,然后ThreadSeed域又被设置一个新的值,以便该进程的下一个线程使用。这里的理想处理器是指在多处理器环境下,每个线程都有一个优先选择的处理器
    UCHAR PowerState;                   //PowerState域用于记录电源状态,关于电源状态管理
    UCHAR IdealNode;                    //IdealNode域用于为一个进程选择优先的处理器节点,这是在进程初始化时设定的。这里的处理器节点是NUMA(非一致的内存访问)结构中的概念。
    BOOLEAN Visited;
    union {
        KEXECUTE_OPTIONS Flags;
        UCHAR ExecuteOptions;           //用于设置一个进程的内存执行选项,这是为了支持NX(No-Execute,内存不可执行)而引入到Windows XP/Server 2003中的一个域;
    };

#if !defined(_X86_) && !defined(_AMD64_)

    PALIGNMENT_EXCEPTION_TABLE AlignmentExceptionTable;

#endif

    ULONG_PTR StackCount;               //记录了当前进程中有多少个线程的栈位于内存中
    LIST_ENTRY ProcessListEntry;        //用于将当前系统中所有具有活动线程的进程串成一个链表,链表头为KiProcessListHead
} KPROCESS, *PKPROCESS, *PRKPROCESS;
  • Header域表明KPROCESS对象也是一个分发器对象(dispatcher object),进程对象本身是可以被等待的。
  • ProfileListHead 域用于当该进程参与性能分析(profiling)时,作为一个节点加入到全局的性能分析进程列表(内核全局变量KiProfileListHead)中。
  • DirectoryTableBase是一个只有两项的数组,其中第一项指向该进程的页目录表地址,第二项指向该进程的超空间(hyper space)的页目录表地址。
  • 下来的LdtDescriptor、Int21Descriptor、IopmOffset和Iopl这四个域是专门针对Intel x86处理器的。其中LdtDescriptor 是该进程的LDT(局部描述符表)的描述符;Int21Descriptor是为了兼容DOS程序,允许它们通过int 21h指令来调用DOS系统功能;IopmOffset则指定了IOPM(I/O权限表,I/O Privilege Map)的位置,内核通过IOPM可控制进程的用户模式I/O访问权限;Iopl域定义了进程的I/O优先级(I/O Privilege Level)。
  • ActiveProcessors域记录了当前进程正在哪些处理器上运行。
  • KernelTime和UserTime域分别记录了一个进程对象在内核模式和用户模式下所花的时间。
  • ReadyListHead是一个双向链表的表头,该链表记录了这个进程中处于就绪状态但尚未被加入全局就绪链表的线程。这个域的意义在于,当一个进程被换出内存以后,它所属的线程一旦就绪,则被挂到此链表中,并要求换入该进程;以后,当该进程被换入内存时,ReadyListHead中的所有线程被加入到系统全局的就绪线程链表中。注意,ReadyListHead链表中的每一项都是一个指向KTHREAD对象(后文讲述)的WaitListEntry域的地址,所以,从链表中的每一项都可以定位到对应的线程对象。
  • SwapListEntry域是一个单链表项,当一个进程要被换出内存或者换入时,它通过此域加入到以KiProcessOutSwapListHead(出)/KiProcessInSwapListHead(入)为链头的单链表中。
  • VdmTrapcHandler,指向处理Ctrl+C中断的函数,仅用于在VDM(虚拟DOS机,Virtual DOS Machine)环境下运行16位程序。
  • ThreadListHead域指向一个链表头,此链表包含了该进程的所有当前线程,由此我们可以看出进程和线程之间的从属关系。
  • ProcessLock域,这是一个自旋锁(spin lock)对象,它的用途是保护此进程中的数据成员。
  • Affinity域指定了该进程的线程可以在哪些处理器上运行,其类型是KAFFINITY,这是一个32位或64位整数,其二进制表示的每一位分别对应于当前机器上的一个处理器(或核)。
  • ProcessFlags域包括了进程中的几个标志:AutoAlignment、DisableBoost 和DisableQuantum。AutoAlignment位用于该进程中的内存访问对齐设置,此标志位也会被传递到线程的数据结构中,当一个线程的对齐检查开关打开时,该线程中的未对齐数据访问将会导致对齐错误(alignment fault),不过,Intel x86并未做任何对齐检查,所以这一位的意义并不重要;DisableBoost和DisableQuantum位与线程调度过程中的优先级提升和时限(quantum)分配有关。
  • KPROCESS中的BasePriority和QuantumReset域是该进程中的线程的调度参数,BasePriority用于指定一个进程中的线程的基本优先级,所有的线程在启动时都会继承进程的BasePriority值;QuantumReset用于指定一个进程中线程的基本时限重置值,在现代Windows版本中此值被设置为6。
  • State域说明了一个进程是否在内存中,共有六种可能的状态:ProcessInMemory、ProcessOutOfMemory、ProcessInTransition、ProcessOutTransition、ProcessInSwap和ProcessOutSwap。所谓一个进程在内存中,或者已被换出,或者正在转移过程中,是指该进程的虚拟地址空间需要占据足够的物理内存,或者虚拟空间中的内容已被换出物理内存,或者正在换入或换出过程之中。
  • ThreadSeed域用于为该进程的线程选择适当的理想处理器(IdealProcessor),在每个线程被初始化的时候,都指定此进程的ThreadSeed值作为它的理想处理器,然后ThreadSeed域又被设置一个新的值,以便该进程的下一个线程使用。这里的理想处理器是指在多处理器环境下,每个线程都有一个优先选择的处理器。
  • PowerState域用于记录电源状态,关于电源状态管理。
  • IdealNode域用于为一个进程选择优先的处理器节点,这是在进程初始化时设定的。这里的处理器节点是NUMA(非一致的内存访问)结构中的概念。
  • ExecuteOptions用于设置一个进程的内存执行选项,这是为了支持NX(No-Execute,内存不可执行)而引入到Windows XP/Server 2003中的一个域。
  • StackCount域记录了当前进程中有多少个线程的栈位于内存中。
  • ProcessListEntry域用于将当前系统中所有具有活动线程的进程串成一个链表,链表头为KiProcessListHead。

简单归纳一下,KPROCESS对象中记录的信息主要包括两类:一类跟进程的内存环境相关,比如页目录表、交换状态等;另一类是与其线程相关的一些属性,比如线程列表以及线程所需要的优先级、时限设置等。系统中的KPROCESS对象通过KiProcessListHead链表串起来,但这一链表仅用于AMD64系统。系统的总进程链表是在执行体层管理的在EPROCESS对象结构里。

KTHREAD定义(base\ntos\inc\ke.h中KTHREAD)

由于在Windows中,线程是系统处理器调度的基本单元,而且线程调度是在内核层完成的,所以,KTHREAD的许多域都跟Windows的线程调度机制有关。

typedef struct _KTHREAD {

    //
    // The dispatcher header and mutant listhead are fairly infrequently
    // referenced.
    //

    DISPATCHER_HEADER Header;               //说明了内核层的线程对象也是一个分发器对象,线程可以被等待,当线程结束时,在该对象上的等待可被满足
    LIST_ENTRY MutantListHead;              //M指向一个链表头,该链表中包含了所有属于该线程的突变体对象(mutant,对应于API中的互斥体[mutex]对象)。由于突变体对象是有所有权的,一旦被某个线程等到,则其所有权归该线程所有,它也被连接到MutantListHead链表中。

    //
    // The following fields are referenced during context switches and wait
    // operatings. They have been carefully laid out to get the best cache
    // hit ratios.
    //
    //有四个域用于内核栈的维护,它们分别是:InitialStack、StackLimit、KernelStack和StackBase。栈是从高地址向低地址方向变化的
    PVOID InitialStack;                     //记录了原始的栈位置(高地址)
    PVOID StackLimit;                       //记录了栈的低地址
    PVOID KernelStack;                      //记录了真正内核调用栈的开始位置,由于在内核栈的顶部区域还记录了浮点处理器保存区和一个异常陷阱帧,所以,KernelStack的位置比InitialStack要低一些

    KSPIN_LOCK ThreadLock;                  //ThreadLock域是一个自旋锁,用于保护线程数据成员。
    union {
        KAPC_STATE ApcState;                //ApcState是一个结构成员,指定了一个线程的APC(Asynchronous Procedure Call)信息,包括APC链表、是否正在处理APC或者是否有内核APC或用户APC正在等待等信息。
                                            //KAPC_STATE中的Process域指向当前线程所属进程的KPROCESS结构。
        struct {
            UCHAR ApcStateFill[KAPC_STATE_ACTUAL_LENGTH];
            BOOLEAN ApcQueueable;               //是否可以插入APC
            volatile UCHAR NextProcessor;       //处理器调度的选择
            volatile UCHAR DeferredProcessor;
            UCHAR AdjustReason;                 //优先级调整原因
            SCHAR AdjustIncrement;              //调整量
        };
    };

    KSPIN_LOCK ApcQueueLock;                    //一个自旋锁,用于保护APC队列的操作

#if !defined(_AMD64_)

    ULONG ContextSwitches;
    volatile UCHAR State;
    UCHAR NpxState;
    KIRQL WaitIrql;
    KPROCESSOR_MODE WaitMode;

#endif

    LONG_PTR WaitStatus;            //记录了等待的结果状态。
    union {
        PKWAIT_BLOCK WaitBlockList; //指向一个以KWAIT_BLOCK为元素的链表,其中的KWAIT_BLOCK对象指明了哪个线程在等待哪个分发器对象。
                                    //对于一个线程而言,WaitBlockList域以及每个KWAIT_BLOCK对象中的WaitListEntry域构成了一个双链表,指明了该线程正在等待哪些分发器对象;而对于每个分发器对象而言,它又有另一个KWAIT_BLOCK链表指明了哪些线程正在等待它。
        PKGATE GateObject;          //当一个线程正在等待门对象(Gate Object,也是一种分发器对象)时,GateObject域记录了正在等待的门对象。由于等待门对象和等待其他分发器对象是不会同时发生的,所以,GateObject和WaitBlockList域构成了一个union,共用一个指针内存。
    };

    BOOLEAN Alertable;              //说明了一个线程是否可以被唤醒,当一个线程正在等待时,如果它的Alertable值为TRUE,则它可以被唤醒。
    BOOLEAN WaitNext;               //WaitNext域也是一个布尔值,TRUE值表示这个线程马上要调用一个内核等待函数,它的用途是,在发出了一个信号(比如释放了一个信号量对象)以后,接下来该线程会马上调用等待函数,所以,它不必解除线程调度器锁
    UCHAR WaitReason;               //记录了一个线程的等待理由,其值的含义见base\ntos\inc\ke.h中的KWAIT_REASON枚举类型
    SCHAR Priority;                 //Priority域包含了该线程的优先级值,这是指它的动态优先级,即在执行过程中可能由于某些原因而调整过的优先级。
    UCHAR EnableStackSwap;          //EnableStackSwap域是一个布尔值,说明本线程的内核栈是否允许被换出到外存中。
    volatile UCHAR SwapBusy;        //指定了本线程当前是否正在进行上下文环境切换(context swap),其用法是,在将执行环境切换到其他线程以前设置SwapBusy域为TRUE,切换完成以后再设置回FALSE。
    BOOLEAN Alerted[MaximumMode];   //一个数组,指定了该线程在每一种警告模式下是否可以被唤醒,
                                    //在WRK中,所谓警告模式实际上也就是内核模式和用户模式,所以,这个数组的含义是指该线程分别在内核模式和用户模式下是否可以被唤醒。

                                    //WaitListEntry和 SwapListEntry分别是一个双链表节点和一个单链表节点,它们是一个union,分别用于不同的情形。
    union {
        LIST_ENTRY WaitListEntry;       //当一个线程正在等待被执行时,WaitListEntry作为一个线程节点加入到某个链表中。
                                        //例如,KPROCESS的ReadyListHead,在进程被换入内存过程中,就绪状态的线程将被加入到以进程的 ReadyListHead域为链表头的双链表中,链表中的节点即为线程的WaitListEntry域。
        SINGLE_LIST_ENTRY SwapListEntry;//用于当线程的内核栈需要被换入时,插入到以全局变量KiStackInSwapListHead为链表头的单链表中。时,插入到以全局变量KiStackInSwapListHead为链表头的单链表中。另外,当一个线程处于DeferredReady状态时,其SwapListEntry将被插入到某个处理器的DeferredReadyListHead链表中
    };

    PRKQUEUE Queue;                 //一个队列分发器对象,如果不为NULL,则表示当前线程正在处理此队列对象中的项。

#if !defined(_AMD64_)

    ULONG WaitTime;                 //WaitTime域记录了一个线程进入等待时刻的时间点(时钟滴答值的低32位),主要用于平衡集管理器根据一个线程的等待时间的先后来做一些决策。
    //接下来是KernelApcDisable和SpecialApcDisable域,或者合并成一个CombinedApcDisable域。
    //KernelApcDisable和SpecialApcDisable都是16位的整数值,0表示不禁止APC,负数表示禁止APC,
    //一个线程在执行过程中可以有多种因素要禁止APC,这些因素以负值来表示,并累加起来,当因素消除的时候再减去相应的负值。
    //只有当KernelApcDisable或SpecialApcDisable为0的时候,该线程才允许插入或提交APC。这两个值分别控制普通的内核APC和特殊的内核APC
    union {
        struct {
            SHORT KernelApcDisable;
            SHORT SpecialApcDisable;
        };

        ULONG CombinedApcDisable;
    };

#endif

    PVOID Teb;              //指向进程地址空间中的一个TEB(线程环境块)结构。
                            //TEB结构包含了在用户地址空间中需要访问的各种信息,例如与线程相关的GDI信息、系统支持的异常,甚至还有WinSock的信息,等等。
                            //关于TEB结构的定义,可以参考public\sdk\inc\pebteb.h中的_TEB定义。
                            //base\ntos\mm\procsup.c文件中的MmCreateTeb函数在一个进程中创建并初始化一个TEB。
    union {
        KTIMER Timer;       //这是附在一个线程上的定时器,
                            //当一个线程在执行过程中需要定时器时,比如实现可超时的等待函数(KeWaitForSingleObject或KeWaitForMultipleObjects),就会用到此定时器对象。
        struct {
            UCHAR TimerFill[KTIMER_ACTUAL_LENGTH];

            //
            // N.B. The following bit number definitions must match the
            //      following bit field.
            //
            // N.B. These bits can only be written with interlocked
            //      operations.
            //
    
#define KTHREAD_AUTO_ALIGNMENT_BIT 0
#define KTHREAD_DISABLE_BOOST_BIT 1
    
            union {//AutoAlignment和DisableBoost两个位标记,直接继承自所属进程的同名标记。
                struct {
                    LONG AutoAlignment : 1;
                    LONG DisableBoost : 1;
                    LONG ReservedFlags : 30;
                };
        
                LONG ThreadFlags;
            };
        };
    };

    union {
        KWAIT_BLOCK WaitBlock[THREAD_WAIT_OBJECTS + 1];          //WaitBlock域是一个包含4个KWAIT_BLOCK成员的数组,其中第4项专门用于可等待的定时器对象。
                                                                 //KWAIT_BLOCK结构代表了一个线程正在等待一个分发器对象,或者说一个分发器对象正在被一个线程等待,它会被同时加入到两个双链表结构中。
                                                                 //WaitBlock域是一个内置数组,内核在实现等待功能的时候,如果一个线程所等待的对象数量小于4(实际上应该是3个分发器对象加上一个定时器对象),
                                                                 //则内核无须另外分配KWAIT_BLOCK对象内存,只需直接使用WaitBlock中的数组成员即可。如果等待的对象数量大于4,
                                                                 //则内核必须分配额外的KWAIT_BLOCK对象内存。
                                                                 //由于等待操作在内核中非常频繁,所以,利用静态数组来满足大多数情况下的内存需求,这一优化非常有意义。
        struct {
            UCHAR WaitBlockFill0[KWAIT_BLOCK_OFFSET_TO_BYTE0];
            BOOLEAN SystemAffinityActive;
        };

        struct {
            UCHAR WaitBlockFill1[KWAIT_BLOCK_OFFSET_TO_BYTE1];
            CCHAR PreviousMode;
        };

        struct {
            UCHAR WaitBlockFill2[KWAIT_BLOCK_OFFSET_TO_BYTE2];
            UCHAR ResourceIndex;
        };

        struct {
            UCHAR WaitBlockFill3[KWAIT_BLOCK_OFFSET_TO_BYTE3];
            UCHAR LargeStack;
        };

#if defined(_AMD64_)

        struct {
            UCHAR WaitBlockFill4[KWAIT_BLOCK_OFFSET_TO_LONG0];
            ULONG ContextSwitches;
        };

        struct {
            UCHAR WaitBlockFill5[KWAIT_BLOCK_OFFSET_TO_LONG1];
            volatile UCHAR State;
            UCHAR NpxState;
            KIRQL WaitIrql;                 //WaitIrql域与WaitNext一起使用,当WaitNext为TRUE时,WaitIrql记录了原先的IRQL值。
            KPROCESSOR_MODE WaitMode;       //WaitMode域记录了当线程等待时的处理器模式,即内核模式或用户模式的等待
        };

        struct {
            UCHAR WaitBlockFill6[KWAIT_BLOCK_OFFSET_TO_LONG2];
            ULONG WaitTime;
        };

        struct {
            UCHAR WaitBlockFill7[KWAIT_BLOCK_OFFSET_TO_LONG3];
             union {
                 struct {
                     SHORT KernelApcDisable;
                     SHORT SpecialApcDisable;
                 };
         
                 ULONG CombinedApcDisable;
             };
        };

#endif

    };

    LIST_ENTRY QueueListEntry;                      //记录了线程在处理一个队列项时加入到队列对象的线程链表中的节点地址。

    //
    // The following fields are accessed during system service dispatch.
    //

    PKTRAP_FRAME TrapFrame;         //记录控制流状态的数据结构,它是一个指向KTRAP_FRAME类型的指针。
                                    //在Windows中,线程是系统调度的基础,它代表了一个进程中的一个控制流。
                                    //当一个线程离开运行状态时,其当前的执行状态,比如现在的指令指针(IP)在哪里,各个寄存器中的值是什么,等等,
                                    //都必须保存下来,以便下次再轮到这个线程运行时,可以恢复原来的执行状态。
                                    //对于Intel x86体系结构,KTRAP_FRAME的定义位于base\ntos\inc\i386.h文件中,它包含了Intel x86的所有常用寄存器
    PVOID CallbackStack;            //包含了线程的回调(callback)栈地址,此栈在该线程从内核模式调用到用户模式时使用。
    PVOID ServiceTable;             //ServiceTable域指向该线程使用的系统服务表(全局变量KeServiceDescriptorTable),
                                    //如果这是一个图形用户界面(GUI)线程,此域将指向另一个影子系统服务表(全局变量KeServiceDescriptorTableShadow),

#if defined(_AMD64_)

    ULONG KernelLimit;

#endif

    //
    // The following fields are referenced during ready thread and wait
    // completion.
    //

    UCHAR ApcStateIndex;            //ApcStateIndex域是一个索引值,它指明了当前的APC状态在ApcStatePointer域中的索引。
                                    //由于ApcStatePointer是一个只有两个元素的数组,所以,ApcStateIndex的值为0或者1
    UCHAR IdealProcessor;           //指明了在多处理器的机器上该线程的理想处理器。
    BOOLEAN Preempted;              //Preempted域是一个布尔值,说明这个线程是否被高优先级的线程抢占了,
                                    //只有当一个线程正在运行或者正在等待运行而被高优先级线程抢占的时候,此值才会置成TRUE。在其他情况下,该值总是FALSE。
    BOOLEAN ProcessReadyQueue;      //说明一个线程是否在所属进程KPROCESS对象的ReadyListHead链表中,TRUE表示在此链表中,FALSE表示不在。

#if defined(_AMD64_)

    PVOID Win32kTable;
    ULONG Win32kLimit;

#endif

    BOOLEAN KernelStackResident;    //说明该线程的内核栈是否驻留在内存中,当内核栈被换出内存时,该值将被置成FALSE;当换入内存时,再置成TRUE。
    SCHAR BasePriority;                     //线程的静态优先级,其初始值是所属进程的BasePriority值,以后可通过KeSetBasePriorityThread函数重新设定。
    SCHAR PriorityDecrement;                //记录了一个线程在优先级动态调整过程中的递减值,每次当重新计算优先级值的时候,它会被减掉(参见base\ntos\ke\ki.h中的KiComputeNewPriority函数)。
                                            //Windows中的优先级值被分成两个区域:0~15是普通线程的优先级,16~31是实时线程的优先级。而且,线程的优先级在调整时,无论如何其Priority值不会跨越区域。
    CHAR Saturation;                        //Saturation域说明了线程的基本优先级相对于进程的基本优先级的调整量是否超过了整个区间的一半,其值为0、1或-1。
    KAFFINITY UserAffinity;         //线程的用户亲和性,此值初始时也继承自进程对象的Affinity值,以后可通过内核函数KeSetAffinityThread改变。
    PKPROCESS Process;              //指向线程的进程对象,在线程初始化时指定,见base\ntos\ke\thredobj.c中的KeInitThread函数。
    KAFFINITY Affinity;             //Affinity域指定了线程的处理器亲和性,此值初始时继承自进程对象的Affinity值。为线程指定的处理器集合必须是其进程的亲和性处理器集合的子集。
                                    //在线程执行过程中,其Affinity值可能有两种设置:
                                    //一是系统亲和性,当该线程执行系统任务时通过KeSetSystemAffinityThread函数来设置;
                                    //二是线程本身的亲和性,称为用户亲和性,通过KeRevertToUserAffinityThread函数来设置。

    //
    // The below fields are infrequently referenced.
    //

    PKAPC_STATE ApcStatePointer[2];     //ApcStatePointer数组元素的类型是指向KAPC_STATE的指针,其两个元素分别指向线程对象的ApcState和SavedApcState域,而这两个域分别位于两个union中。
    union {
        KAPC_STATE SavedApcState;
        struct {
            UCHAR SavedApcStateFill[KAPC_STATE_ACTUAL_LENGTH];
            CCHAR FreezeCount;
            CCHAR SuspendCount;
            UCHAR UserIdealProcessor;
            UCHAR CalloutActive;

#if defined(_AMD64_)

            BOOLEAN CodePatchInProgress;

#elif defined(_X86_)

            UCHAR Iopl;

#else

            UCHAR OtherPlatformFill;

#endif

        };
    };

    PVOID Win32Thread;              //一个指针,指向由Windows子系统管理的区域。
    PVOID StackBase;                //记录了当前栈的基位置(高地址)。
    //SuspendApc和SuspendSemaphore两个union域相互之间有联系,
    //SuspendApc被初始化成一个专门的APC。当该APC被插入并交付时,KiSuspendThread函数被执行,
    //其执行结果是在线程的SuspendSemaphore信号量上等待,直到该信号量对象有信号,然后线程被唤醒并继续执行。
    //线程的挂起(suspend)操作正是通过这一机制来实现的,参见base\ntos\ke\thredobj.c中的KeSuspendThread和KeFreezeAllThreads函数。
    //自然地,线程的恢复(resume)操作则是通过控制SuspendSemaphore信号量的计数来实现的,参见base\ntos\ke\thredobj.c中的KeResumeThread等函数。
    union {
        KAPC SuspendApc;
        struct {
            UCHAR SuspendApcFill0[KAPC_OFFSET_TO_SPARE_BYTE0];
            SCHAR Quantum;
        };

        struct {
            UCHAR SuspendApcFill1[KAPC_OFFSET_TO_SPARE_BYTE1];
            UCHAR QuantumReset;
        };

        struct {
            UCHAR SuspendApcFill2[KAPC_OFFSET_TO_SPARE_LONG];
            ULONG KernelTime;
        };

        struct {
            UCHAR SuspendApcFill3[KAPC_OFFSET_TO_SYSTEMARGUMENT1];
            PVOID TlsArray;
        };

        struct {
            UCHAR SuspendApcFill4[KAPC_OFFSET_TO_SYSTEMARGUMENT2];
            PVOID BBTData;
        };

        struct {
            UCHAR SuspendApcFill5[KAPC_ACTUAL_LENGTH];
            UCHAR PowerState;
            ULONG UserTime;
        };
    };

    union {
        KSEMAPHORE SuspendSemaphore;
        struct {
            UCHAR SuspendSemaphorefill[KSEMAPHORE_ACTUAL_LENGTH];
            ULONG SListFaultCount;//它记录了在地址SListFaultAddress上发生页面错误的次数。
        };
    };

    LIST_ENTRY ThreadListEntry;//代表了一个双链表上的节点,当一个线程被创建时,它会被加入到进程对象的ThreadListHead链表中,参见KPROCESS的ThreadListHead域的介绍。
    PVOID SListFaultAddress;    //SListFaultAddress域与用户模式互锁单链表POP操作(KeUserPopEntrySListFault函数)的错误处理有关,
                                //它记录了上一次用户模式互锁单链表POP操作发生页面错误的地址;

#if defined(_WIN64)

    LONG64 ReadOperationCount;
    LONG64 WriteOperationCount;
    LONG64 OtherOperationCount;
    LONG64 ReadTransferCount;
    LONG64 WriteTransferCount;
    LONG64 OtherTransferCount;

#endif

} KTHREAD, *PKTHREAD, *PRKTHREAD;
  •  Header说明了内核层的线程对象也是一个分发器对象,线程可以被等待,当线程结束时,在该对象上的等待可被满足。
  • MutantListHead域指向一个链表头,该链表中包含了所有属于该线程的突变体对象(mutant,对应于API中的互斥体[mutex]对象)。由于突变体对象是有所有权的,一旦被某个线程等到,则其所有权归该线程所有,它也被连接到MutantListHead链表中。
  • 有四个域用于内核栈的维护,它们分别是:InitialStack、StackLimit、KernelStack和StackBase。栈是从高地址向低地址方向变化的,InitialStack记录了原始的栈位置(高地址);StackLimit记录了栈的低地址;KernelStack记录了真正内核调用栈的开始位置,由于在内核栈的顶部区域还记录了浮点处理器保存区和一个异常陷阱帧,所以,KernelStack的位置比InitialStack要低一些(KTRAP_FRAME_LENGTH+sizeof(FX_SAVE_AREA))。StackBase记录了当前栈的基位置(高地址)。在线程初始化时,InitialStack和StackBase是相等的,都指向原始的内核栈高地址。
  • ThreadLock域是一个自旋锁,用于保护线程数据成员。ApcState是一个结构成员,指定了一个线程的APC(Asynchronous Procedure Call)信息,包括APC链表、是否正在处理APC或者是否有内核APC或用户APC正在等待等信息。KAPC_STATE中的Process域指向当前线程所属进程的KPROCESS结构。在ApcState所在的union中,ApcState之后还有几个域:ApcQueueable、NextProcessor、DeferredProcessor、AdjustReason和AdjustIncrement,分别指定了以下设置:是否可以插入APC、关于处理器调度的选择,以及优先级调整原因和调整量。ApcQueueLock也是一个自旋锁,用于保护APC队列的操作。
  • Alertable域说明了一个线程是否可以被唤醒,当一个线程正在等待时,如果它的Alertable值为TRUE,则它可以被唤醒。
  • WaitNext域也是一个布尔值,TRUE值表示这个线程马上要调用一个内核等待函数,它的用途是,在发出了一个信号(比如释放了一个信号量对象)以后,接下来该线程会马上调用等待函数,所以,它不必解除线程调度器锁。
  • WaitIrql域与WaitNext一起使用,当WaitNext为TRUE时,WaitIrql记录了原先的IRQL值。WaitReason域记录了一个线程的等待理由,其值的含义见base\ntos\inc\ke.h中的KWAIT_REASON枚举类型。
  • WaitReason基本上只是记录了等待的理由,而并不参与到线程调度或决策中。
  • WaitMode域记录了当线程等待时的处理器模式,即内核模式或用户模式的等待。
  • WaitStatus域,它记录了等待的结果状态。
  • WaitBlockList成员指向一个以KWAIT_BLOCK为元素的链表,其中的KWAIT_BLOCK对象指明了哪个线程在等待哪个分发器对象。对于一个线程而言,WaitBlockList域以及每个KWAIT_BLOCK对象中的WaitListEntry域构成了一个双链表,指明了该线程正在等待哪些分发器对象;而对于每个分发器对象而言,它又有另一个KWAIT_BLOCK链表指明了哪些线程正在等待它.
  • Priority域包含了该线程的优先级值,这是指它的动态优先级,即在执行过程中可能由于某些原因而调整过的优先级。BasePriority域是线程的静态优先级,其初始值是所属进程的BasePriority值,以后可通过KeSetBasePriorityThread函数重新设定。PriorityDecrement域记录了一个线程在优先级动态调整过程中的递减值,每次当重新计算优先级值的时候,它会被减掉。Windows中的优先级值被分成两个区域:0~15是普通线程的优先级,16~31是实时线程的优先级。而且,线程的优先级在调整时,无论如何其Priority值不会跨越区域。Saturation域说明了线程的基本优先级相对于进程的基本优先级的调整量是否超过了整个区间的一半,其值为0、1或-1。
  • EnableStackSwap域是一个布尔值,说明本线程的内核栈是否允许被换出到外存中。
  • SwapBusy域也是一个布尔值,指定了本线程当前是否正在进行上下文环境切换(context swap),其用法是,在将执行环境切换到其他线程以前设置SwapBusy域为TRUE,切换完成以后再设置回FALSE。
  • Alerted域是一个数组,指定了该线程在每一种警告模式下是否可以被唤醒,在WRK中,所谓警告模式实际上也就是内核模式和用户模式,所以,这个数组的含义是指该线程分别在内核模式和用户模式下是否可以被唤醒。
  • WaitListEntry和 SwapListEntry分别是一个双链表节点和一个单链表节点,它们是一个union,分别用于不同的情形。当一个线程正在等待被执行时,WaitListEntry作为一个线程节点加入到某个链表中。例如,本节前文在介绍KPROCESS的ReadyListHead域时曾提到,在进程被换入内存过程中,就绪状态的线程将被加入到以进程的 ReadyListHead域为链表头的双链表中,链表中的节点即为线程的WaitListEntry域。
  • SwapListEntry域则被用于当线程的内核栈需要被换入时,插入到以全局变量KiStackInSwapListHead为链表头的单链表中。另外,当一个线程处于DeferredReady状态时,其SwapListEntry将被插入到某个处理器的DeferredReadyListHead链表中。
  • Queue域是一个队列分发器对象,如果不为NULL,则表示当前线程正在处理此队列对象中的项。
  • WaitTime域记录了一个线程进入等待时刻的时间点(时钟滴答值的低32位),主要用于平衡集管理器,根据一个线程的等待时间的先后来做一些决策。
  • 接下来是KernelApcDisable和SpecialApcDisable域,或者合并成一个CombinedApcDisable域。KernelApcDisable和SpecialApcDisable都是16位的整数值,0表示不禁止APC,负数表示禁止APC,一个线程在执行过程中可以有多种因素要禁止APC,这些因素以负值来表示,并累加起来,当因素消除的时候再减去相应的负值。只有当KernelApcDisable或SpecialApcDisable为0的时候,该线程才允许插入或提交APC。这两个值分别控制普通的内核APC和特殊的内核APC。
  • Teb域是一个特殊的域,它指向进程地址空间中的一个TEB(线程环境块)结构。TEB结构包含了在用户地址空间中需要访问的各种信息,例如与线程相关的GDI信息、系统支持的异常,甚至还有WinSock的信息,等等。
  • 这是附在一个线程上的定时器,当一个线程在执行过程中需要定时器时,比如实现可超时的等待函数(KeWaitForSingleObject或KeWaitForMultipleObjects),就会用到此定时器对象。
  • WaitBlock域是一个包含4个KWAIT_BLOCK成员的数组,其中第4项专门用于可等待的定时器对象。KWAIT_BLOCK结构代表了一个线程正在等待一个分发器对象,或者说一个分发器对象正在被一个线程等待,它会被同时加入到两个双链表结构中。
  • 记录了线程在处理一个队列项时加入到队列对象的线程链表中的节点地址。
  • 记录控制流状态的数据结构,它是一个指向KTRAP_FRAME类型的指针。
  • CallbackStack域包含了线程的回调(callback)栈地址,此栈在该线程从内核模式调用到用户模式时使用。
  • ServiceTable域指向该线程使用的系统服务表(全局变量KeServiceDescriptorTable),如果这是一个图形用户界面(GUI)线程,此域将指向另一个影子系统服务表(全局变量KeServiceDescriptorTableShadow)。
  • IdealProcessor域指明了在多处理器的机器上该线程的理想处理器。
  • Preempted域是一个布尔值,说明这个线程是否被高优先级的线程抢占了,只有当一个线程正在运行或者正在等待运行而被高优先级线程抢占的时候,此值才会置成TRUE。在其他情况下,该值总是FALSE。
  • ProcessReadyQueue域也是一个布尔值,说明一个线程是否在所属进程KPROCESS对象的ReadyListHead链表中,TRUE表示在此链表中,FALSE表示不在。
  • KernelStackResident域也是一个布尔值,说明该线程的内核栈是否驻留在内存中,当内核栈被换出内存时,该值将被置成FALSE;当换入内存时,再置成TRUE。
  • Affinity域指定了线程的处理器亲和性,此值初始时继承自进程对象的Affinity值。为线程指定的处理器集合必须是其进程的亲和性处理器集合的子集。
  • UserAffinity域是线程的用户亲和性,此值初始时也继承自进程对象的Affinity值,以后可通过内核函数KeSetAffinityThread改变。
  • Process域指向线程的进程对象,在线程初始化时指定。
  • ApcStateIndex域是一个索引值,它指明了当前的APC状态在ApcStatePointer域中的索引。
  • ApcStatePointer数组元素的类型是指向KAPC_STATE的指针,其两个元素分别指向线程对象的ApcState和SavedApcState域,而这两个域分别位于两个union中。
  • Win32Thread域是一个指针,指向由Windows子系统管理的区域。
  • SuspendApc和SuspendSemaphore两个union域相互之间有联系,SuspendApc被初始化成一个专门的APC。当该APC被插入并交付时,KiSuspendThread函数被执行,其执行结果是在线程的SuspendSemaphore信号量上等待,直到该信号量对象有信号,然后线程被唤醒并继续执行。线程的挂起(suspend)操作正是通过这一机制来实现的,参见base\ntos\ke\thredobj.c中的KeSuspendThread和KeFreezeAllThreads函数。自然地,线程的恢复(resume)操作则是通过控制SuspendSemaphore信号量的计数来实现的,参见base\ntos\ke\thredobj.c中的KeResumeThread等函数。
  • ThreadListEntry域代表了一个双链表上的节点,当一个线程被创建时,它会被加入到进程对象的ThreadListHead链表中,参见本节前文关于KPROCESS的ThreadListHead域的介绍。
  • SListFaultAddress域与用户模式互锁单链表POP操作(KeUserPopEntrySListFault函数)的错误处理有关,它记录了上一次用户模式互锁单链表POP操作发生页面错误的地址;

从以上介绍的KPROCESS和KTHREAD数据结构我们可以看出,内核层的进程和线程对象只包含了系统资源管理和多控制流并发执行所涉及的基本信息,而没有包含与应用程序相关联的信息,如进程映像文件和线程启动函数地址等。由于Windows的线程调度算法比较复杂(在抢占式调度算法的基础上,还支持优先级局部调整等),且需要支持某些硬件结构特性,所以,在KPROCESS和KTHREAD结构中,有些成员的引入直接跟这些特性有关。尽管如此,从概念上讲,内核层上的进程和线程对象仍然十分清晰,即,进程对象提供了线程的基本执行环境,包括进程地址空间和一组进程范围内公用的参数;线程对象提供了为参与线程调度而必需的各种信息及其维护控制流的状态。

你可能感兴趣的:(windows,驱动开发,内核)