为什么提高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...  

TPR

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

向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 destina­tion of the IPI. This destination shorthand al­lows software to interrupt the processor on which it is executing. An APIC implementa­tion is free to deliver the self-interrupt mes­sage 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-deas­sert, 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.