为什么提高IRQL到DISPATCH_LEVEL级就能禁止线程切换?
KeRaiseIrqlToDpcLevel 会调用KfRaiseIrql ,而KfRaiseIrql 会根据传进来 的 IRQL在HalpIRQLToTPR得到相应的TPR, 并将TPR值设置到apic 的TPR寄存器中。
代码如下:
__forceinline
KIRQL
KeRaiseIrqlToDpcLevel(
VOID
)
{
return KfRaiseIrql(DISPATCH_LEVEL);
}
KIRQL
FORCEINLINE
KfRaiseIrql (
__in KIRQL NewIrql
)
{
KIRQL oldIrql;
ULONG tprValue;
oldIrql = KeGetCurrentIrql();
ASSERT( NewIrql >= oldIrql );
tprValue = HalpIRQLToTPR[NewIrql];
KeMemoryBarrier();
*APIC_TPR = tprValue;
KeMemoryBarrier();
return oldIrql;
}
kd> uf hal!KfRaiseIrql
hal!KfRaiseIrql:
806d3278 0fb6d1 movzx edx,cl
806d327b 0fb68a58326d80 movzx ecx,byte ptr hal!HalpIRQLtoTPR (806d3258)[edx] ; 以IRQL为索引在HalpIRQLtoTPR表中找到对应的TPR
806d3282 a18000feff mov eax,dword ptr ds:[FFFE0080h] ; FFFE0080h 对应APIC内部寄存器 TPR
806d3287 890d8000feff mov dword ptr ds:[0FFFE0080h],ecx ; 存放 任务优先级到 TPR
806d328d c1e804 shr eax,4 ; TPR /16
806d3290 0fb68088e06d80 movzx eax,byte ptr hal!HalpVectorToIRQL (806de088)[eax] ; 根据向量号找到对应的IRQL
806d3297 c3 ret
; 以2(DISPATCH_LEVEL)为索引得到TPR为41
kd> db HalpIRQLToTPR
806d3258 00 3d 41 41 51 61 71 81-91 a1 b1 b1 b1 b1 b1 b1 .=AAQaq.........
806d3268 b1 b1 b1 b1 b1 b1 b1 b1-b1 b1 b1 c1 d1 e1 ef ff ................
806d3278 0f b6 d1 0f b6 8a 58 32-6d 80 a1 80 00 fe ff 89 ......X2m.......
806d3288 0d 80 00 fe ff c1 e8 04-0f b6 80 88 e0 6d 80 c3 .............m..
806d3298 8b 15 80 00 fe ff c7 05-80 00 fe ff 41 00 00 00 ............A...
806d32a8 c1 ea 04 0f b6 82 88 e0-6d 80 c3 90 8b 15 80 00 ........m.......
806d32b8 fe ff c7 05 80 00 fe ff-41 00 00 00 c1 ea 04 0f ........A.......
806d32c8 b6 82 88 e0 6d 80 c3 90-33 c0 8a c1 33 c9 8a 88 ....m...3...3...
kd> .formats 41
Evaluate expression:
Hex: 00000041
Decimal: 65
Octal: 00000000101
Binary: 00000000 00000000 00000000 01000001
Chars: ...A
Time: Thu Jan 01 08:01:05 1970
Float: low 9.10844e-044 high 0
Double: 3.21143e-322
Task Priority : 100b, 即4
根据TPR格式得到任务优先级为4
而线程调度分为两种:
在当前代码环境下,只能是被迫放弃执行权,而被迫放弃执行权只能通过KiRequestDispatchInterrupt发送DISPATCH_LEVEL软件中断, 在中断处理函数KiDispatchInterrupt中来进行线程切换。
时限用完的情况: 在时钟中断处理函数KeUpdateSystemTime中会调用KeUpdateRunTime扣除当前线程的时限 , 并调用 HalRequestSoftwareInterrupt请求一个DISPATCH_LEVEL 软件中断,
反汇编代码如下:
; HalRequestSoftwareInterrupt(DISPATCH_LEVEL) 发送一个软件中断
kd> uf hal!HalRequestSoftwareInterrupt
hal!HalRequestSoftwareInterrupt:
806d38b0 3a0d95f0dfff cmp cl,byte ptr ds:[0FFDFF095h]
806d38b6 7434 je hal!HalRequestSoftwareInterrupt+0x3c (806d38ec)
hal!HalRequestSoftwareInterrupt+0x8:
806d38b8 33c0 xor eax,eax
806d38ba 8ac1 mov al,cl ; DISPATCH_LEVEL 2
806d38bc 33c9 xor ecx,ecx
806d38be 8a8858326d80 mov cl,byte ptr hal!HalpIRQLtoTPR (806d3258)[eax] ; 根据IRQL得到TPR, 41
806d38c4 81c900000400 or ecx,40000h ; ecx = 40041h
806d38ca 9c pushfd
806d38cb fa cli ; 关中断
hal!HalRequestSoftwareInterrupt+0x1c:
806d38cc f7050003feff00100000 test dword ptr ds:[0FFFE0300h],1000h
; 测试apic ICR寄存器第12位 (Delivery status)
806d38d6 75f4 jne hal!HalRequestSoftwareInterrupt+0x1c (806d38cc)
; 测试 Delivery Status位, 直到为0(即Idle)为止
; 即没有Send Pending
hal!HalRequestSoftwareInterrupt+0x28:
806d38d8 890d0003feff mov dword ptr ds:[0FFFE0300h],ecx ;ICR设置为40041h
hal!HalRequestSoftwareInterrupt+0x2e:
806d38de f7050003feff00100000 test dword ptr ds:[0FFFE0300h],1000h
806d38e8 75f4 jne hal!HalRequestSoftwareInterrupt+0x2e (806d38de) ; 循环直到 中断发送完成
hal!HalRequestSoftwareInterrupt+0x3a:
806d38ea 9d popfd
806d38eb c3 ret
hal!HalRequestSoftwareInterrupt+0x3c:
806d38ec c60596f0dfff01 mov byte ptr ds:[0FFDFF096h],1
806d38f3 c3 ret
向ICR写入0x40041h,即发送一个中断,
kd> .formats 40041h
Evaluate expression:
Hex: 00040041
Decimal: 262209
Octal: 00001000101
Binary: 00000000 00000100 00000000 01000001
Chars: ...A
Time: Sun Jan 04 08:50:09 1970
Float: low 3.67433e-040 high 0
Double: 1.29548e-318
Vector: 41h Destination Shorthand: 01h, Self
01: (Self) The issuing APIC is the one and only destination of the IPI. This destination shorthand allows software to interrupt the processor on which it is executing. An APIC implementation is free to deliver the self-interrupt message internally or to issue the message to the bus and “snoop” it as with any other IPI message.
kd> !idt
Dumping IDT:
37: 806d2728 hal!PicSpuriousService37
3d: 806d3b70 hal!HalpApcInterrupt
41: 806d39cc hal!HalpDispatchInterrupt
50: 806d2800 hal!HalpApicRebootService
62: 82153a5c atapi!IdePortInterrupt (KINTERRUPT 82153a20)
63: 8247d41c USBPORT!USBPORT_InterruptService (KINTERRUPT 8247d3e0)
73: 822c5924 SCSIPORT!ScsiPortInterrupt (KINTERRUPT 822c58e8)
USBPORT!USBPORT_InterruptService (KINTERRUPT 824
根据Vector=41h, 得到hal!HalpDispatchInterrupt, 最终调用 _KiDispatchInterrupt, 在 _KiDispatchInterrupt函数中会根据(prcb.PbQuantumEnd) 时限值是否用完调用 _KiQuantumEnd,在_KiQuantumEnd中会进行线程切换。
而在HalRequestSoftwareInterrupt(DISPATCH_LEVEL) 发送软件中断过程中, 即向 local apic 发送ipi 时,由于vector 41对应的tpr等于当前apic的tpr值,所以 被block,即hal!HalpDispatchInterrupt不会响应,也就不会发生线程切换。
关于TPR, intel手册如下描述:
The task priority allows software to set a priority threshold for interrupting the processor. The processor will service only those interrupts that have a priority higher than that specified in the TPR. If software sets the task priority in the TPR to 0, the processor will handle all interrupts; it is it set to 15, all interrupts are inhibited from being handled, except those delivered with the NMI, SMI, INIT, ExtINT, INIT-deassert, and start-up delivery mode. This mechanism enables the operating system to temporarily block specific interrupts (generally low priority interrupts) from disturbing high-priority work that the processor is doing.