Windows XP 中断与异常(段内容补充二)

ʕ •̀ o •́ ʔ

6 时钟中断与定时器

定时器(timer)是一种被广泛使用的软件机制,应用软件可以用定时器来实现动画和其他各种周期性任务等,设备驱动程序利用定时器来控制超时,操作系统利用定时器来执行线程调度、时间计最等。Windows 提供了一种定时器内核对象,本章后面5.4.2 节将会介绍内核提供的各种同步对象,其中就包括定时器对象。这一节我们先讨论 Windows 如何管理定时器和实现定时任务。

6.1 时钟中断时限处理

Windows 中最底层的定时器机制是通过时钟中断加上DPC对象来实现的。我们首先从时钟中断入手来检查最原始的时间控制点。时钟中断是由本地 APIC 来控制的,HAL模块负责设置时钟中断向量、中断发生的频率,具体的时钟间隔与 HAL 模块有关,通常为 10~15 ms。在 Intel x86 处理器的 Windows 系统中,时钟中断的 IRQL为28。时钟中断的服务例程由 HAL 提供,它调用了内核模块的 KeUpdateSystemTime 函数。

5.png

6.png

KeUpdateSystemTime功能

  1. 它首先更新中断时间和系统时间(位于用户共享数据区一个用户模式和内核模式代码均可访问的内存页面),以及滴答计数(全局变量_KeTickCount)。
  2. 然后,KeUpdateSystemTime 判断定时器数组中是否有定时器到期,如果有,则设置 KPRCB 的 TimerRequest 标志,并通过调用 HalRequestSoftwarelnterrupt 函数请求 DISPATCH_LEVEL 软件中断。最后,KeupdateSystemTime 调用 KeUpdateRunTime 函数,更新线程和进程的运行时间。

KeUpdateRunTime 函数功能

  1. 它首先更新内核模式的总时间、中断所花的时间(如果当前在中断例程中),然后更新当前线程和进程的内核模式时间或用户模式时间。
  2. 接着,KeUpdateRunTime 函数刷新 DPC 请求率,如果当前 DPC 链表不为空,则有必要的话调用 HalRequestSofiwarelnterrupt 函数请求 DISPATCH_LEVEL 软件中断。
  3. 最后,KeUpdateRunTime 扣除当前线程的时限,扣除的单位为 3(CLOCK_QUANTUM_DECREMENT),参考 3.5.3 节中关于线程时限管理的介绍。
  4. 如果扣除以后线程的时限仍大于0,则函数返回,否则设置 KPRCB 的 QuantumEnd 标志(若QuantumEnd != 0表示时限用完),并请求一个 DISPAT_CHLEVEL 的软件中断。

值得一提的是,在多处理器系统中,第一个处理器(即 P0)上的时钟中断例程调用 KeUpdateSystemTime 函数,而其他处理器上的时钟中断例程调用 KeUpdateRunTime 函数。从上述两个函数的功能不难理解,系统全局的时间信息由第一个处理器来维护,而其他的处理器只负责处理该处理器上的 DPC 以及与线程调度有关的事项

KeUpdateSystemTimeKeUpdateRunTime 两数中,有三件事情可以导致立即请求一个 DISPATCH_LEVEL 的软件中断:有 DPC 需要处理定时器到期,以及当前线程的时限已结束

根据上一节的介绍,DISPATCH_LEVEL 软件中断的处理函数 KiDispatchInterrupt,以及它所调用的函数 KiRetireDpcList,正好可以完成这些事情。

6.2 定时器处理

系统中所有的定时器构成了一个链表数组,他们都将会挂入全局数组 KiTimerTableListHlead 包含的 256 个定时器链表中,链表串着定时器 KTIMER 对象的 TimerListEntry 成员。相关的C定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#define TIMER_TABLE_SIZE 256

LIST_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE];

typedef struct _KTIMER {
DISPATCHER_HEADER Header; //可等待对象
ULARGE_INTEGER DueTime; //指定的到期时间
LIST_ENTRY TimerListEntry;
struct _KDPC *Dpc;
LONG Period;
} KTIMER, *PKTIMER, *RESTRICTED_POINTER PRKTIMER;

kd> dt _KTIMER
nt!_KTIMER
+0x000 Header : _DISPATCHER_HEADER
+0x010 DueTime : _ULARGE_INTEGER
+0x018 TimerListEntry : _LIST_ENTRY
+0x020 Dpc : Ptr32 _KDPC
+0x024 Period : Int4B

DueTime:任何一个定时器都有一个指定的到期时间,即 KTIMER 结构中的 DueTime 值。当一个定时器被插入到 KiTimerTableListHead 中时,它被插入到哪个链表是由 DueTime 决定的,即根据 DueTime 的值,计算得到一个下标 IndexKiComputeTimerTableIndex 函数完成这一计算。此计算式的基本思路是,**将 DueTime 除以每个滴答(tick)的最大间隔时间,即把 DueTime 转换成以滴答为单位,然后模上TIMER_TABLE_SIZE**(在代码中使用了 and 指令,对于2的幂次来说,这是等价的)。

因此,每次时钟中断经过一个滴答,它只需检查 KiTimerTableListHead 数组中的一个链表,即符合当前滴答时间值作为到期时间的定时器链表,同时也判断属于下一个滴答的定时器链表。我们在 KeUpdateSystemTime 函数中可以看到,如果检测到当前滴答计数(全局变量 KeTickCount)或下一个滴答计数所对应的链表中的 Time 域已经小于等于当前中断时间值,则设置当前处理器的 KPRCB 中的 TimerRequest标志,并且设置 KPRCB 的 TimerHand 域指向刚刚到期的定时器链表的数组下标,然后调用 HalRequestSoftwarelInterrupt 函数请求 DISPATCH_LEVEL 软件中断。

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
kd> dd KeTickCount
80555000 000f11b6 00000000 00000000 0002625a
80555010 0002625a ffff8d8f 00000000 00000000
80555020 00000000 00000000 00000000 00000000
80555030 00000000 00000000 00000000 00000000
80555040 00000000 00000000 00000000 00000000

typedef struct _KSYSTEM_TIME {
ULONG LowPart;
LONG High1Time;
LONG High2Time;
} KSYSTEM_TIME, *PKSYSTEM_TIME;

#if defined(_NTDRIVER_) || defined(_NTDDK_) || defined(_NTIFS_)
// begin_wdm
#define KeQueryTickCount(CurrentCount ) {
volatile PKSYSTEM_TIME _TickCount = *((PKSYSTEM_TIME *)(&KeTickCount));
while (TRUE) {
(CurrentCount)->HighPart = _TickCount->High1Time;
(CurrentCount)->LowPart = _TickCount->LowPart;
if ((CurrentCount)->HighPart == _TickCount->High2Time) break;
_asm { rep nop }
}
}
// end_wdm
#else
// end_ntddk end_nthal end_ntosp
#define KiQueryTickCount(CurrentCount)
while (TRUE) {
(CurrentCount)->HighPart = KeTickCount.High1Time;
(CurrentCount)->LowPart = KeTickCount.LowPart;
if ((CurrentCount)->HighPart == KeTickCount.High2Time) break;
_asm { rep nop }
}
// begin_ntddk begin_nthal begin_ntosp

⚠️注意:Windows XP中的没有 WRK 关于定时器这部分的变化:

  1. WRK 中说的数组KiTimerTableListHead的每一项是一个_KTIMER_TABLE_ENTRY结构,XP中直接就是一个KTIMER对象。
  2. 数组长度不同
    • XP:#define TIMER_TABLE_SIZE 256
    • WRK:#define TIMER_TABLE_SIZE 512
  3. Index计算方法不同
    • XP:没有handIndex = KiComputeTimerTableIndex(IN LARGE_INTEGER Interval,IN LARGE_INTEGER CurrentTime, IN PKTIMER Timer)
    • WRK:KiInsertTimerTable数组中的每一个链表都有一个Hand值,然后Index = Hand = KiComputeTimerTableIndex(DueTime)
  4. 关于 KiTimerExpiration 函数
    • XP: KiTimerExpiration 函数是在KiInitSystem系统初始化时调用。
    • WRK:如果 KPRCB 中的 TimerRequest 标志已被置上,则 KiRetireDpcList 调用 KiTimerExpiration 函数,并且将 KPRCB 中的 TimerHand 传递给它。所以,KiTimerExpiration 函数知道该从 KiTimerTableListHead 数组的哪个定时器链表开始处理。

现在我们来看定时器被插入链表的做法。KiInsertTimerTable 函数定位到待插入的定时器链表,依据定时器的 DueTime 值的先后顺序,插入到链表中合适的位置上。

综上所述,我们可以知道,如果将定时器的到期时间 DueTime 换算成滴答计数,则 KiTimerTableListHlead数组的每个链表中的定时器对模 TIMER_TABLE_SIZE 同余。

Windows 之所以使用一个二维数组来维护系统中所有的定时器对象,是从效率的角度考虑的,KiComputeTimerTableIndex 计算得到一个下标或索引,相当于做了一个优雅的散列处理。不同索引对应的链表的到期时间不一样。

与单个长链表相比,定时器的插入和到期处理都可以在相对较短的链表上进行,而且,在 KeUpdateSystemTime 函数中判断是否有到期定时器也可以非常快捷地实现。

7.png

从以上的介绍可以看出,定时器对象既具有普通同步对象的特征,也具有 DPC 对象的特征。本节仅仅介绍了定时器对象的到期处理,后面5.4.2 节还将进一步从同步对象的角度介绍定时器对象。

6.3 定时器对象

定时器对象既是一个像 DPC 这样的例程的包裝对象,也是一个分发器对象,并且分为定时器通知对象和定时器同步对象。
在前面5.2.5 节介绍定时器管理时,我们看到定时器数据结构 KTIMER 的第一个成员是 DISPATCHER_ HEADER 结构,因此,KTIMER 对象也可被用于等待函数,换句话说,一个线程可以等待一个定时器对象。有关定时器的操作的实现代码位于 baseintostketimerobj.c 和 timersup.c 文件中。下面是主要的定时器操作:

  • KelnitializeTimer/KeInitializeTimerEx,初始化一个定时器对象。KelnitializeTimer 调用 KelnitializeTimerEx 函数来初始化一个通知类型的定时器。
  • KeClearTimer,清除一个定时器对象的信号状态。
  • KeCancelTimer,取消一个已经设定的定时器。如果该定时器对象尚未被设定,即DISPATCHER_ HEADER 中的 Inserted 域为 FALSE,则什么也不做,否则将它从系统的定时器链表中移除。
  • KeSetTimer/KeSetTimerEx,设定一个定时器。若它已经被插人到了系统的定时器链表中,则首先将它从定时器链表中移除。然后,计算定时器的到期时间,若当前已到调用 KiSignalTimer,以唤醒那些正在等待该定时器的线程,并且以 DPC 方式交付定时器中的到期例程。若当前尚未到期,则设定定时器为无信号状态,并调用KilnsertOrSignalTimer 函数将它插人到系统的定时器链表中。
  • KiSignalTimer,定时器对象变为有信号状态。对于定时器通知对象,调用KiWaitTestWithoutSideEffeets 函数唤醒所有的等待线程;对于定时器同步对象,则调用 KiWait TestSynchronizationObject,唤醒一个正在等待该对象的线程。对于周期性的定时器,即 KTIMER 的 Period 域不为0,则再次插入该定时器对象到系统的定时器链表中。最后,若定时器对象关联了一个 DPC 例程,则调用 KelnsertQueueDpc 函数,将其转化成一个真正的 DPC 对象。
  • KilnsertOrSignalTimer,如果定时器对象尚未到期,则将它插入到系统的定时器链表中,否则,调用 KiComplete Timer 函数,KiCompleteTimer 进一步调用 KiSignalTimer函数完成定时器对象的到期处理。
  • KiTimerExpiration,该函数是系统的定时器到期处理函数,如图 5.9 所示。它遍历特定的定时器链表,对于每个到期的定时器,使之变成有信号状态,并调用KiWaitTestWithoutSideEffects(针对定时器通知对象)或 KiWaitTestSynchronizationObject 函数(针对定时器同步对象)。

定时器对象是-种常用的分发器对象,内核和设备驱动程序都可以使用定时器对象来实现超时和定时处理。Windows 提供的定时器对象非常灵活:既可以通过到期例程来通知使用者,也允许使用者通过等待函数获得通知;既可以是一次性的,也可以是周期性的;既可以是通知类型,也可以是同步类型。值得指出的一点是,定时器对象的精度受限于系统处理器时钟中断的精度,根据 5.2.5 节的介绍,Windows仅在时钟中断或 DISPATCH_ LEVEL软件中断发生时,才会处理定时器链表中的到期定时器对象。

最后,我们注意到,每当由于一个分发器对象导致其他的线程被解除等待时,被唤醒的线程都会获得优先级的提升,不同的分发器对象带给等待线程的优先级提升值可能有所不同,事件、信号量和突变体对象的优先级提升值为1,定时器对象不会提升等待线程的优先级。有关线程的优先级和优先级提升的情形,参见 3.5.1节的介绍。

由于分发器对象的状态变化会直接影响到系统的线程调度,所以,在以上这些分发器对象的操作函数的代码中,我们常常会看到 RQL 提升至 DISPATCHL LEVEL 或 SYNCH LEVEL,并且要锁住调度器数据库,这表明,当一个线程调用这些操作时,它实际上将控制权暂时交给了线程调度器。事实上,系统的线程调度器也正是通过这种方式来工作的,它不仅从代码位置上散布在内核模块各处,而且从运行时间上,也插人在各个线程、中断或异常的执行过程中。绝大多数情况下,进出线程调度器是在同一个函数中完成的,也就是说,如果一个丽数进人了调度器,则该函数在返回以前,还会退出调度器。但也有例外,比如 KeSetEvent、 KeReleaseMutant、 KeReleaseSemaphore 等函数的 Wait 参数就指明了该函数调用以后是否要紧接着调用一个等待函数,若如此,则不退出调度器,而是把这一信息记录在线程对象 KTHREAD 的 WaitNext 和 Waitlrql 成员中,然后在等待函数中再判断线程的状态。

6.4 源码KeUpdateSystemTime

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
.text:0046DFA4 // =============== S U B R O U T I N E =======================================
.text:0046DFA4
.text:0046DFA4 // _KUSER_SHARED_DATA:FFDF0000
.text:0046DFA4
.text:0046DFA4 // _DWORD __stdcall KeUpdateSystemTime()
.text:0046DFA4 public _KeUpdateSystemTime@0
.text:0046DFA4 _KeUpdateSystemTime@0 proc near
.text:0046DFA4 mov ecx, 0FFDF0000h
.text:0046DFA9 mov edi, [ecx+_KUSER_SHARED_DATA.InterruptTime.LowPart]
.text:0046DFAC mov esi, [ecx+_KUSER_SHARED_DATA.InterruptTime.High1Time]
.text:0046DFAF add edi, eax
.text:0046DFB1 adc esi, 0
.text:0046DFB4 mov [ecx+_KUSER_SHARED_DATA.InterruptTime.High2Time], esi
.text:0046DFB7 mov [ecx+_KUSER_SHARED_DATA.InterruptTime.LowPart], edi
.text:0046DFBA mov [ecx+_KUSER_SHARED_DATA.InterruptTime.High1Time], esi
.text:0046DFBD sub ds:_KiTickOffset, eax
.text:0046DFC3 mov eax, ds:_KeTickCount.LowPart
.text:0046DFC8 mov ebx, eax
.text:0046DFCA jg loc_46E054
.text:0046DFD0 mov ebx, 0FFDF0000h
.text:0046DFD5 mov ecx, [ebx+_KUSER_SHARED_DATA.SystemTime.LowPart]
.text:0046DFD8 mov edx, [ebx+_KUSER_SHARED_DATA.SystemTime.High1Time]
.text:0046DFDB add ecx, ds:_KeTimeAdjustment
.text:0046DFE1 adc edx, 0
.text:0046DFE4 mov [ebx+_KUSER_SHARED_DATA.SystemTime.High2Time], edx
.text:0046DFE7 mov [ebx+_KUSER_SHARED_DATA.SystemTime.LowPart], ecx
.text:0046DFEA mov [ebx+_KUSER_SHARED_DATA.SystemTime.High1Time], edx
.text:0046DFED mov ebx, eax
.text:0046DFEF mov ecx, eax
.text:0046DFF1 mov edx, ds:_KeTickCount.High1Time
.text:0046DFF7 add ecx, 1
.text:0046DFFA adc edx, 0
.text:0046DFFD mov ds:_KeTickCount.High2Time, edx
.text:0046E003 mov ds:_KeTickCount.LowPart, ecx
.text:0046E009 mov ds:_KeTickCount.High1Time, edx
.text:0046E00F push eax
.text:0046E010 mov eax, ds:0FFDF0000h
.text:0046E015 add eax, 1
.text:0046E018 jnb short loc_46E020
.text:0046E01A inc ds:_ExpTickCountAdjustmentCount
.text:0046E020
.text:0046E020 loc_46E020: // CODE XREF: KeUpdateSystemTime()+74↑j
.text:0046E020 mov eax, ds:_ExpTickCountAdjustment
.text:0046E025 imul eax, ds:_ExpTickCountAdjustmentCount
.text:0046E02C add eax, ecx
.text:0046E02E mov ds:0FFDF0000h, eax
.text:0046E033 pop eax
.text:0046E034 and eax, 0FFh
.text:0046E039 lea ecx, _KiTimerTableListHead[eax*8]
.text:0046E040 mov edx, [ecx]
.text:0046E042 cmp ecx, edx
.text:0046E044 jz short loc_46E052
.text:0046E046 cmp esi, [edx-4]
.text:0046E049 jb short loc_46E052
.text:0046E04B ja short loc_46E072
.text:0046E04D cmp edi, [edx-8]
.text:0046E050 jnb short loc_46E072
.text:0046E052
.text:0046E052 loc_46E052: // CODE XREF: KeUpdateSystemTime()+A0↑j
.text:0046E052 // KeUpdateSystemTime()+A5↑j
.text:0046E052 inc eax
.text:0046E053 inc ebx
.text:0046E054
.text:0046E054 loc_46E054: // CODE XREF: KeUpdateSystemTime()+26↑j
.text:0046E054 and eax, 0FFh
.text:0046E059 lea ecx, _KiTimerTableListHead[eax*8]
.text:0046E060 mov edx, [ecx]
.text:0046E062 cmp ecx, edx
.text:0046E064 jz short loc_46E0C5
.text:0046E066 cmp esi, [edx-4]
.text:0046E069 jb short loc_46E0C5
.text:0046E06B ja short loc_46E072
.text:0046E06D cmp edi, [edx-8]
.text:0046E070 jb short loc_46E0C5
.text:0046E072
.text:0046E072 loc_46E072: // CODE XREF: KeUpdateSystemTime()+A7↑j
.text:0046E072 // KeUpdateSystemTime()+AC↑j ...
.text:0046E072 mov ecx, large fs:_KPCR.Prcb
.text:0046E079 lea eax, _KiTimerExpireDpc.DpcListEntry // -----
.text:0046E079 // KiTimerExpireDpc - This is the Deferred Procedure Call (DPC) object that
.text:0046E079 // is used to process the timer queue when a timer has expired.
.text:0046E079 //
.text:0046E079 // KDPC KiTimerExpireDpc;
.text:0046E079 // ----
.text:0046E07F lea edx, [ecx+_KPRCB.DpcLock]
.text:0046E085 cmp dword ptr [eax+18h], 0 // KDPC->Lock
.text:0046E089 jnz short loc_46E0C5
.text:0046E08B
.text:0046E08B loc_46E08B: // CODE XREF: KeUpdateSystemTime()+17F↓j
.text:0046E08B cli
.text:0046E08C lock bts dword ptr [edx], 0
.text:0046E091 jb loc_46E11C
.text:0046E097 inc [ecx+_KPRCB.DpcQueueDepth]
.text:0046E09D mov [eax+18h], edx // Lock
.text:0046E0A0 mov [eax+10h], ebx // SystemArgument1
.text:0046E0A3 add ecx, _KPRCB.DpcListHead
.text:0046E0A9 mov ebx, [ecx+4]
.text:0046E0AC mov [ecx+4], eax
.text:0046E0AF mov [ebx], eax
.text:0046E0B1 mov [eax], ecx
.text:0046E0B3 mov [eax+4], ebx
.text:0046E0B6 mov byte ptr [edx], 0
.text:0046E0B9 sti
.text:0046E0BA mov ecx, 2
.text:0046E0BF call ds:__imp_@HalRequestSoftwareInterrupt@4 // HalRequestSoftwareInterrupt(x)
.text:0046E0C5
.text:0046E0C5 loc_46E0C5: // CODE XREF: KeUpdateSystemTime()+C0↑j
.text:0046E0C5 // KeUpdateSystemTime()+C5↑j ...
.text:0046E0C5 cmp ds:_KdDebuggerEnabled, 0
.text:0046E0CC jnz short loc_46E109
.text:0046E0CE
.text:0046E0CE loc_46E0CE: // CODE XREF: KeUpdateSystemTime()+16C↓j
.text:0046E0CE // KeUpdateSystemTime()+175↓j
.text:0046E0CE cmp ds:_KiTickOffset, 0
.text:0046E0D5 jg short loc_46E0F6
.text:0046E0D7 mov eax, ds:_KeMaximumIncrement
.text:0046E0DC add ds:_KiTickOffset, eax
.text:0046E0E2 push dword ptr [esp+0]
.text:0046E0E5 call _KeUpdateRunTime@4 // KeUpdateRunTime(x)
.text:0046E0EA cli
.text:0046E0EB call ds:__imp__HalEndSystemInterrupt@8 // HalEndSystemInterrupt(x,x)
.text:0046E0F1 jmp Kei386EoiHelper@0 // Kei386EoiHelper()
.text:0046E0F6 // ---------------------------------------------------------------------------
.text:0046E0F6
.text:0046E0F6 loc_46E0F6: // CODE XREF: KeUpdateSystemTime()+131↑j
.text:0046E0F6 inc large dword ptr fs:5C4h
.text:0046E0FD cli
.text:0046E0FE call ds:__imp__HalEndSystemInterrupt@8 // HalEndSystemInterrupt(x,x)
.text:0046E104 jmp Kei386EoiHelper@0 // Kei386EoiHelper()
.text:0046E109 // ---------------------------------------------------------------------------
.text:0046E109
.text:0046E109 loc_46E109: // CODE XREF: KeUpdateSystemTime()+128↑j
.text:0046E109 call _KdPollBreakIn@0 // KdPollBreakIn()
.text:0046E10E or al, al
.text:0046E110 jz short loc_46E0CE
.text:0046E112 push 1 // Status
.text:0046E114 call _DbgBreakPointWithStatus@4 // DbgBreakPointWithStatus(x)
.text:0046E119 jmp short loc_46E0CE
.text:0046E119 // ---------------------------------------------------------------------------
.text:0046E11B align 4
.text:0046E11C
.text:0046E11C loc_46E11C: // CODE XREF: KeUpdateSystemTime()+ED↑j
.text:0046E11C sti
.text:0046E11D
.text:0046E11D loc_46E11D: // CODE XREF: KeUpdateSystemTime()+187↓j
.text:0046E11D test dword ptr [edx], 1
.text:0046E123 jz loc_46E08B
.text:0046E129 pause
.text:0046E12B jmp short loc_46E11D
.text:0046E12B _KeUpdateSystemTime@0 endp // sp-analysis failed
.text:0046E12B
.text:0046E12B // ---------------------------------------------------------------------------

6.5 ia64版本KeUpdateSystemTime

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
VOID KeUpdateSystemTime (
IN PKTRAP_FRAME TrFrame,
IN ULONG Increment
)
/*++
Routine Description:
This routine is executed on a single processor in the processor complex.
It's function is to update the system time and to check to determine if a
timer has expired.
N.B. This routine is executed on a single processor in a multiprocess system.
The remainder of the processors in the complex execute the quantum end and
runtime update code.
Arguments:
TrFrame - Supplies a pointer to a trap frame.
Increment - The time increment to be used to adjust the time slice for the next
tick. The value is supplied in 100ns units.
Return Value: None.
--*/
{
ULONG LowTime;
LONG HighTime;
LONG SaveTickOffset;

//
// Update the interrupt time in the shared region.
//

LowTime = SharedUserData->InterruptTime.LowPart + Increment;
HighTime = SharedUserData->InterruptTime.High1Time + (LowTime < Increment);
SharedUserData->InterruptTime.High2Time = HighTime;
SharedUserData->InterruptTime.LowPart = LowTime;
SharedUserData->InterruptTime.High1Time = HighTime;

KiTickOffset -= Increment;

SaveTickOffset = KiTickOffset;

if ((LONG)KiTickOffset > 0)
{
//
// Tick has not completed (100ns time units remain).
//
// Determine if a timer has expired at the current hand value.
//

KiChkTimerExpireSysDpc(KeTickCount.QuadPart);

} else {

//
// Tick has completed, tick count set to maximum increase plus any
// residue and system time is updated.
//
// Compute next tick offset.
//

KiTickOffset += KeMaximumIncrement;
LowTime = SharedUserData->SystemTime.LowPart + KeTimeAdjustment;
HighTime = SharedUserData->SystemTime.High1Time + (LowTime < KeTimeAdjustment);
SharedUserData->SystemTime.High2Time = HighTime;
SharedUserData->SystemTime.LowPart = LowTime;
SharedUserData->SystemTime.High1Time = HighTime;
++KeTickCount.QuadPart;
SharedUserData->TickCountLow = (ULONG)KeTickCount.QuadPart;

//
// Determine if a timer has expired at either the current hand value or
// the next hand value.
//

if (!KiChkTimerExpireSysDpc(KeTickCount.QuadPart - 1))
KiChkTimerExpireSysDpc(KeTickCount.QuadPart);

}

if (SaveTickOffset <= 0) {
KeUpdateRunTime(TrFrame);
}
}

6.6 ROS:KeUpdateSystemTime

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/*
* NOTE: On Windows this function takes exactly zero parameters and EBP is
* guaranteed to point to KTRAP_FRAME. Also [esp+0] contains an IRQL.
* The function is used only by HAL, so there's no point in keeping
* that prototype.
*
* @implemented
*/
VOID NTAPI KeUpdateSystemTime(IN PKTRAP_FRAME TrapFrame,
IN KIRQL Irql,
IN ULONG Increment)
{
LONG OldOffset;
LARGE_INTEGER Time;
ASSERT(KeGetCurrentIrql() == PROFILE_LEVEL);

/* Update interrupt time */
Time.LowPart = SharedUserData->InterruptTime.LowPart;
Time.HighPart = SharedUserData->InterruptTime.High1Time;
Time.QuadPart += Increment;
SharedUserData->InterruptTime.High2Time = Time.u.HighPart;
SharedUserData->InterruptTime.LowPart = Time.u.LowPart;
SharedUserData->InterruptTime.High1Time = Time.u.HighPart;

/* Increase the tick offset */
KiTickOffset -= Increment;
OldOffset = KiTickOffset;

/* Check if this isn't a tick yet */
if (KiTickOffset > 0)
{
/* Expire timers */
KeInsertQueueDpc(&KiExpireTimerDpc, 0, 0);
}
else
{
/* Setup time structure for system time */
Time.LowPart = SharedUserData->SystemTime.LowPart;
Time.HighPart = SharedUserData->SystemTime.High1Time;
Time.QuadPart += KeTimeAdjustment;
SharedUserData->SystemTime.High2Time = Time.HighPart;
SharedUserData->SystemTime.LowPart = Time.LowPart;
SharedUserData->SystemTime.High1Time = Time.HighPart;

/* Setup time structure for tick time */
Time.LowPart = KeTickCount.LowPart;
Time.HighPart = KeTickCount.High1Time;
Time.QuadPart += 1;
KeTickCount.High2Time = Time.HighPart;
KeTickCount.LowPart = Time.LowPart;
KeTickCount.High1Time = Time.HighPart;
SharedUserData->TickCount.High2Time = Time.HighPart;
SharedUserData->TickCount.LowPart = Time.LowPart;
SharedUserData->TickCount.High1Time = Time.HighPart;

/* Queue a DPC that will expire timers */
KeInsertQueueDpc(&KiExpireTimerDpc, 0, 0);
}

/* Update process and thread times */
if (OldOffset <= 0)
{
/* This was a tick, calculate the next one */
KiTickOffset += KeMaximumIncrement;
KeUpdateRunTime(TrapFrame, Irql);
}
}


/*
* NOTE: On Windows this function takes exactly one parameter and EBP is
* guaranteed to point to KTRAP_FRAME. The function is used only
* by HAL, so there's no point in keeping that prototype.
*
* @implemented
*/
VOID NTAPI KeUpdateRunTime(IN PKTRAP_FRAME TrapFrame,
IN KIRQL Irql)
{
PKPRCB Prcb = KeGetCurrentPrcb();
PKTHREAD CurrentThread;
PKPROCESS CurrentProcess;

/* Make sure we don't go further if we're in early boot phase. */
if (!(Prcb) || !(Prcb->CurrentThread)) return;

/* Get the current thread and process */
CurrentThread = Prcb->CurrentThread;
CurrentProcess = CurrentThread->ApcState.Process;

/* Check if we came from user mode */
if (TrapFrame->PreviousMode != KernelMode)
{
/* Update user times */
CurrentThread->UserTime++;
InterlockedIncrement((PLONG)&CurrentProcess->UserTime);
Prcb->UserTime++;
}
else
{
/* Check IRQ */
if (Irql > DISPATCH_LEVEL)
{
/* This was an interrupt */
Prcb->InterruptTime++;
}
else if ((Irql < DISPATCH_LEVEL) || !(Prcb->DpcRoutineActive))
{
/* This was normal kernel time */
CurrentThread->KernelTime++;
InterlockedIncrement((PLONG)&CurrentProcess->KernelTime);
}
else if (Irql == DISPATCH_LEVEL)
{
/* This was DPC time */
Prcb->DpcTime++;
}

/* Update CPU kernel time in all cases */
Prcb->KernelTime++;
}

/* Set the last DPC Count and request rate */
Prcb->DpcLastCount = Prcb->DpcData[0].DpcCount;
Prcb->DpcRequestRate = ((Prcb->DpcData[0].DpcCount - Prcb->DpcLastCount) +
Prcb->DpcRequestRate) / 2;

/* Check if we should request a DPC */
if ((Prcb->DpcData[0].DpcQueueDepth) && !(Prcb->DpcRoutineActive))
{
/* Request one */
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);

/* Update the depth if needed */
if ((Prcb->DpcRequestRate < KiIdealDpcRate) &&
(Prcb->MaximumDpcQueueDepth > 1))
{
/* Decrease the maximum depth by one */
Prcb->MaximumDpcQueueDepth--;
}
}
else
{
/* Decrease the adjustment threshold */
if (!(--Prcb->AdjustDpcThreshold))
{
/* We've hit 0, reset it */
Prcb->AdjustDpcThreshold = KiAdjustDpcThreshold;

/* Check if we've hit queue maximum */
if (KiMaximumDpcQueueDepth != Prcb->MaximumDpcQueueDepth)
{
/* Increase maximum by one */
Prcb->MaximumDpcQueueDepth++;
}
}
}

/*
* If we're at end of quantum request software interrupt. The rest
* is handled in KiDispatchInterrupt.
*
* NOTE: If one stays at DISPATCH_LEVEL for a long time the DPC routine
* which checks for quantum end will not be executed and decrementing
* the quantum here can result in overflow. This is not a problem since
* we don't care about the quantum value anymore after the QuantumEnd
* flag is set.
*/
if ((CurrentThread->Quantum -= 3) <= 0)
{
Prcb->QuantumEnd = TRUE;
HalRequestSoftwareInterrupt(DISPATCH_LEVEL);
}
}

6.7 KeInitializeTimer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
VOID __stdcall KeInitializeTimer (
__out PKTIMER Timer
)
/*++
Routine Description: This function initializes a kernel timer object.
Arguments: Timer - Supplies a pointer to a dispatcher object of type timer.
Return Value: None.
--*/
{
// Initialize extended timer object with a type of notification and a
// period of zero.
KeInitializeTimerEx(Timer, NotificationTimer);
return;
}

6.8 KeInitializeTimerEx

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
VOID __stdcall KeInitializeTimerEx (
__out PKTIMER Timer,
__in TIMER_TYPE Type
)
/*++
Routine Description: This function initializes an extended kernel timer object.
Arguments:
Timer - Supplies a pointer to a dispatcher object of type timer.
Type - Supplies the type of timer object; NotificationTimer or SynchronizationTimer;
Return Value: None.
--*/
{
// Initialize standard dispatcher object header and set initial state of timer.
Timer->Header.Type = (UCHAR)(TimerNotificationObject + Type);
Timer->Header.Inserted = FALSE;
Timer->Header.Size = sizeof(KTIMER) / sizeof(LONG);
Timer->Header.SignalState = FALSE;

#if DBG

Timer->TimerListEntry.Flink = NULL;
Timer->TimerListEntry.Blink = NULL;

#endif

InitializeListHead(&Timer->Header.WaitListHead);
Timer->DueTime.QuadPart = 0;
Timer->Period = 0;
return;
}

6.9 KeSetTimer

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
BOOLEAN __stdcall KeSetTimer (
__inout PKTIMER Timer,
__in LARGE_INTEGER DueTime,
__in_opt PKDPC Dpc
)
/*++
Routine Description:
This function sets a timer to expire at a specified time. If the timer is
already set, then it is implicitly canceled before it is set to expire at
the specified time. Setting a timer causes its due time to be computed,
its state to be set to Not-Signaled, and the timer object itself to be
inserted in the timer list.
Arguments:
Timer - Supplies a pointer to a dispatcher object of type timer.
DueTime - Supplies an absolute or relative time at which the timer
is to expire.
Dpc - Supplies an optional pointer to a control object of type DPC.
Return Value:
A boolean value of TRUE is returned if the the specified timer was
currently set. Else a value of FALSE is returned.
--*/
{

// Set the timer with a period of zero.
return KeSetTimerEx(Timer, DueTime, 0, Dpc);
}

6.a KeSetTimerEx

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
BOOLEAN __stdcall KeSetTimerEx (
__inout PKTIMER Timer,
__in LARGE_INTEGER DueTime,
__in LONG Period,
__in_opt PKDPC Dpc
)
/*++
Routine Description:
This function sets a timer to expire at a specified time. If the timer is
already set, then it is implicitly canceled before it is set to expire at
the specified time. Setting a timer causes its due time to be computed,
its state to be set to Not-Signaled, and the timer object itself to be
inserted in the timer list.
Arguments:
Timer - Supplies a pointer to a dispatcher object of type timer.
DueTime - Supplies an absolute or relative time at which the timer
is to expire.
Period - Supplies an optional period for the timer in milliseconds.
Dpc - Supplies an optional pointer to a control object of type DPC.
Return Value:
A boolean value of TRUE is returned if the the specified timer was
currently set. Otherwise, a value of FALSE is returned.
--*/
{
ULONG Hand;
BOOLEAN Inserted;
KIRQL OldIrql;
BOOLEAN RequestInterrupt;

ASSERT_TIMER(Timer);

ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

// Raise IRQL to dispatcher level and lock dispatcher database.
//
// Capture the timer inserted status and if the timer is currently
// set, then remove it from the timer list.
KiLockDispatcherDatabase(&OldIrql);
Inserted = Timer->Header.Inserted;
if (Inserted != FALSE) {
KiRemoveTreeTimer(Timer);
}

// Set the DPC address, set the period, and compute the timer due time.
// If the timer has already expired, then signal the timer. Otherwise,
// set the signal state to false and attempt to insert the timer in the
// timer table.
//
// N.B. The signal state must be cleared before it is inserted in the
// timer table in case the period is not zero.
Timer->Dpc = Dpc;
Timer->Period = Period;
if (KiComputeDueTime(Timer, DueTime, &Hand) == FALSE) {
RequestInterrupt = KiSignalTimer(Timer);
KiUnlockDispatcherDatabaseFromSynchLevel();
if (RequestInterrupt == TRUE) {
KiRequestSoftwareInterrupt(DISPATCH_LEVEL);
}

} else {
Timer->Header.SignalState = FALSE;
KiInsertOrSignalTimer(Timer, Hand);
}

KiExitDispatcher(OldIrql);

// Return boolean value that signifies whether the timer was set or not.
return Inserted;
}

6.b KiTimerExpiration

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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
VOID __stdcall KiTimerExpiration (
IN PKDPC TimerDpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
/*++
Routine Description:
This function is called when the clock interupt routine discovers that
a timer has expired.
N.B. This function executes on the same processor that receives clock
interrupts.
Arguments:
TimerDpc - Not used.
DeferredContext - Not used.
SystemArgument1 - Supplies the starting timer table index value to
use for the timer table scan.
SystemArgument2 - Not used.
Return Value: None.
--*/
{
ULARGE_INTEGER CurrentTime;
ULONG DpcCount;
PKDPC Dpc;
DPC_ENTRY DpcTable[MAXIMUM_TIMERS_PROCESSED];
KIRQL DummyIrql;
LONG HandLimit;
LONG Index;
LARGE_INTEGER Interval;
PLIST_ENTRY ListHead;
PKSPIN_LOCK_QUEUE LockQueue;
PLIST_ENTRY NextEntry;
KIRQL OldIrql;
LONG Period;

#if !defined(NT_UP) || defined(_WIN64)
PKPRCB Prcb = KeGetCurrentPrcb();
#endif
ULARGE_INTEGER SystemTime;
PKTIMER Timer;
ULONG TimersExamined;
ULONG TimersProcessed;

UNREFERENCED_PARAMETER(TimerDpc);
UNREFERENCED_PARAMETER(DeferredContext);
UNREFERENCED_PARAMETER(SystemArgument2);

// Capture the timer expiration time, the current interrupt time, and
// the low tick count.
//
// N.B. Interrupts are disabled to ensure that interrupt activity on the
// current processor does not cause the values read to be skewed.
_disable();
KiQuerySystemTime((PLARGE_INTEGER)&SystemTime);
KiQueryInterruptTime((PLARGE_INTEGER)&CurrentTime);
HandLimit = (LONG)KiQueryLowTickCount();
_enable();

// If the timer table has not wrapped, then start with the specified
// timer table index value, and scan for timer entries that have expired.
// Otherwise, start with the specified timer table index value and scan
// the entire table for timer entries that have expired.
//
// N.B. This later condition exists when DPC processing is blocked for a
// period longer than one round trip throught the timer table.
//
// N.B. The current instance of the timer expiration execution will only
// process the timer queue entries specified by the computed index
// and hand limit. If another timer expires while the current scan
// is in progress, then another scan will occur when the current one
// is finished.
Index = PtrToLong(SystemArgument1);
if ((ULONG)(HandLimit - Index) >= TIMER_TABLE_SIZE) {
HandLimit = Index + TIMER_TABLE_SIZE - 1;
}

Index -= 1;
HandLimit &= (TIMER_TABLE_SIZE - 1);

// Acquire the dispatcher database lock and read the current interrupt
// time to determine which timers have expired.
DpcCount = 0;
TimersExamined = MAXIMUM_TIMERS_EXAMINED;
TimersProcessed = MAXIMUM_TIMERS_PROCESSED;
KiLockDispatcherDatabase(&OldIrql);
do {
Index = (Index + 1) & (TIMER_TABLE_SIZE - 1);
ListHead = &KiTimerTableListHead[Index].Entry;
while (ListHead != ListHead->Flink) {
LockQueue = KiAcquireTimerTableLock(Index);
NextEntry = ListHead->Flink;
Timer = CONTAINING_RECORD(NextEntry, KTIMER, TimerListEntry);
TimersExamined -= 1;
if ((NextEntry != ListHead) &&
(Timer->DueTime.QuadPart <= CurrentTime.QuadPart)) {

// The next timer in the current timer list has expired.
// Remove the entry from the timer tree and set the signal
// state of the timer.
TimersProcessed -= 1;
KiRemoveEntryTimer(Timer);
Timer->Header.Inserted = FALSE;
KiReleaseTimerTableLock(LockQueue);
Timer->Header.SignalState = 1;

// Capture the DPC and period fields from the timer object.
// Once wait test is called, the timer must not be touched
// again unless it is periodic. The reason for this is that
// a thread may allocate a timer on its local stack and wait
// on it. Wait test can cause that thread to immediately
// start running on another processor on an MP system. If
// the thread returns, then the timer will be corrupted.
Dpc = Timer->Dpc;
Period = Timer->Period;
if (IsListEmpty(&Timer->Header.WaitListHead) == FALSE) {
if (Timer->Header.Type == TimerNotificationObject) {
KiWaitTestWithoutSideEffects(Timer, TIMER_EXPIRE_INCREMENT);

} else {
KiWaitTestSynchronizationObject(Timer, TIMER_EXPIRE_INCREMENT);
}
}
// If the timer is periodic, then compute the next interval
// time and reinsert the timer in the timer tree.
//
// N.B. Even though the timer insertion is relative, it can
// still fail if the period of the timer elapses in
// between computing the time and inserting the timer.
// If this happens, then the insertion is retried.
if (Period != 0) {
Interval.QuadPart = Int32x32To64(Period, - 10 * 1000);
do {
} while (KiInsertTreeTimer(Timer, Interval) == FALSE);
}

// If a DPC is specified, then insert it in the target
// processor's DPC queue or capture the parameters in
// the DPC table for subsequent execution on the current
// processor.
if (Dpc != NULL) {
#if defined(NT_UP)
DpcTable[DpcCount].Dpc = Dpc;
DpcTable[DpcCount].Routine = Dpc->DeferredRoutine;
DpcTable[DpcCount].Context = Dpc->DeferredContext;
DpcCount += 1;
#else
if (((Dpc->Number >= MAXIMUM_PROCESSORS) &&
(((LONG)Dpc->Number - MAXIMUM_PROCESSORS) != Prcb->Number)) ||
((Dpc->Type == (UCHAR)ThreadedDpcObject) &&
(Prcb->ThreadDpcEnable != FALSE))) {

KeInsertQueueDpc(Dpc,
ULongToPtr(SystemTime.LowPart),
ULongToPtr(SystemTime.HighPart));

} else {
DpcTable[DpcCount].Dpc = Dpc;
DpcTable[DpcCount].Routine = Dpc->DeferredRoutine;
DpcTable[DpcCount].Context = Dpc->DeferredContext;
DpcCount += 1;
}
#endif
}
// If the maximum number of timers have been processed or
// the maximum number of timers have been examined, then
// drop the dispatcher lock and process the DPC table.
if ((TimersProcessed == 0) || (TimersExamined == 0)) {
KiProcessTimerDpcTable(&SystemTime, &DpcTable[0], DpcCount);
// Initialize the DPC count, the scan counters, and
// acquire the dispatcher database lock.
//
// N.B. Control is returned with the dispatcher database unlocked.
DpcCount = 0;
TimersExamined = MAXIMUM_TIMERS_EXAMINED;
TimersProcessed = MAXIMUM_TIMERS_PROCESSED;

#if defined(_WIN64)

if (KiTryToLockDispatcherDatabase(&DummyIrql) == FALSE) {
Prcb->TimerHand = 0x100000000I64 + Index;
return;
}

#else
KiLockDispatcherDatabase(&DummyIrql);
#endif
}
} else {

// If the timer table list is not empty, then set the due time
// to the first entry in the respective timer table entry.
//
// N.B. On the x86 the write of the due time is not atomic.
// Therefore, interrupts must be disabled to synchronize
// with the clock interrupt so the clock interupt code
// does not observe a partial update of the due time.
//
// Release the timer table lock.
if (NextEntry != ListHead) {
ASSERT(KiTimerTableListHead[Index].Time.QuadPart <= Timer->DueTime.QuadPart);
#if defined(_X86_)
_disable();
KiTimerTableListHead[Index].Time.QuadPart = Timer->DueTime.QuadPart;
_enable();
#else
KiTimerTableListHead[Index].Time.QuadPart = Timer->DueTime.QuadPart;
#endif
}

KiReleaseTimerTableLock(LockQueue);

// If the maximum number of timers have been scanned, then
// drop the dispatcher lock and process the DPC table.
if (TimersExamined == 0) {
KiProcessTimerDpcTable(&SystemTime, &DpcTable[0], DpcCount);

// Initialize the DPC count, the scan counters, and
// acquire the dispatcher database lock.
//
// N.B. Control is returned with the dispatcher database unlocked.
DpcCount = 0;
TimersExamined = MAXIMUM_TIMERS_EXAMINED;
TimersProcessed = MAXIMUM_TIMERS_PROCESSED;

#if defined(_WIN64)
if (KiTryToLockDispatcherDatabase(&DummyIrql) == FALSE) {
Prcb->TimerHand = 0x100000000I64 + Index;
return;
}
#else
KiLockDispatcherDatabase(&DummyIrql);
#endif
}

break;
}
}

} while(Index != HandLimit);

#if DBG
if (KeNumberProcessors == 1) {
KiCheckTimerTable(CurrentTime);
}
#endif

// If the DPC table is not empty, then process the remaining DPC table
// entries and lower IRQL. Otherwise, unlock the dispatcher database.
//
// N.B. Control is returned from the DPC processing routine with the
// dispatcher database unlocked.
if (DpcCount != 0) {
KiProcessTimerDpcTable(&SystemTime, &DpcTable[0], DpcCount);
if (OldIrql != DISPATCH_LEVEL) {
KeLowerIrql(OldIrql);
}
} else {
KiUnlockDispatcherDatabase(OldIrql);
}

return;
}

7 异常分发

8 线程调度

9 注册表

10 文件系统

11 内核模式回用户模式

Windows内核原理与实现 8.1.2 P550

异常 驱动开发–常规/非常规驱动与3环通信

调试 x86内核CVE:1900飞哥、0day第二版 内核部分

Windows回调机制