为什么要避免使用 POPF 来恢复中断标志状态?

Why would one avoid using POPF to restore Interrupt Flag state?

我的问题不是关于 BX 被用作 return 值而不是将其放在全局内存位置或堆栈上这一事实。我观察到最近在评论中发布了这段代码。该代码用于使用 BIOS 的实模式鼠标处理程序。 save/restoreFLAGS寄存器状态的两个小函数如下:

EFLAGS_IF        equ 0x200         ; Bit mask for IF flag in FLAGS register

; Function: save_if_flag
;           save the current state of the Interrupt Flag (IF)
;
; Inputs:   None
; Returns:  BX = 0x200 if interrupt flag is set, 0 otherwise

save_if_flag:
    pushf
    pop bx                      ; Get FLAGS into BX
    and bx, EFLAGS_IF           ; BX=0 if IF is clear, BX=0x200 if set
    ret

; Function: restore_if_flag
;           restore Interrupt Flag (IF) state
;
; Inputs:   BX = save Interrupt Flag state
; Clobbers: None
; Returns:  EFLAGS IF flag restored

restore_if_flag:
    test bx, bx                 ; Is saved Interrupt Flag zero?
    jz .if_off                  ;     If zero, then disable interrupts & finish
    sti                         ; Otherwise enable interrupts
    ret                         ; We're finished
.if_off:                        
    cli                         ; Disable interrupts
    ret

我想了解为什么 restore_if_flag 函数会这样做:

restore_if_flag:
    test bx, bx                 ; Is saved Interrupt Flag zero?
    jz .if_off                  ;     If zero, then disable interrupts & finish
    sti                         ; Otherwise enable interrupts
    ret                         ; We're finished
.if_off:                        
    cli                         ; Disable interrupts
    ret

而不是像这样简单地使用 POPF

restore_if_flag:
    push bx
    popf
    ret

为什么使用 STI/CLI 显式 save/restore 中断标志​​而不是简单地使用 POPF[=27= 恢复之前的 FLAGS 寄存器]?

您正在查看的代码很有价值。写这篇文章的人可能知道 POPF doesn't treat the Interrupt Flag (IF) the same way in all the different operating modes.


他们可能试图避免这两种代码模式:

sti
pushf                     ; Save flags including interrupts (IF)
cli
; Do work here with interrupts off
popf                      ; Restore interrupts (re-enable IF) 
; Interrupts may still be off at this point depending on mode and IO Privileges

cli
pushf                     ; Save flags including interrupts (IF)
sti
; Do work here with interrupts on
popf                      ; Restore interrupts to previous state 
; Interrupts may still be on at this point depending on mode and IO privileges

这 2 种情况是 IF 实际发生了变化,POPF 预计会恢复 IF 到它以前的值。如果您查看我圈出的第一个图表 N 。这些是您处于保护模式、兼容模式或 64 位模式且 Current Privilege Level (CPL) 为 1,2,3 且 IOPL(IO Privilege Level)< CPL 的情况。在这些情况下 POPF 不会生成一般保护错误,并且会默默地忽略对 IF.

的更改

因为没有错误,内核不知道有人试图改变 IF 所以没有机会虚拟化 IFSTICLI 在没有适当的 IOPL 权限时充当特权指令,并且会在 IF可以被CPU虚拟化。

关于为什么在原始代码中显式 STI/CLI 的问题? STI/CLI 将保证在您没有适当的 IOPL 权限更新 IF 的情况下内核可以拦截的故障. POPF 可能允许 IF 与程序认为标志应该是什么的概念不同步。通过使用 STICLI 来改变 IF 你可以让内核更容易地保持同步。


DPMI(DOS 保护模式接口)specification 讨论了这个问题并且是这样描述的:

2.3 Interrupt Flag Management The popf and iret instructions may not modify the state of the interrupt flag since most DPMI implementations will run programs with IOPL < DPL. Programs must execute cli or sti to modify the interrupt flag state.

This means that the following code sequence will leave interrupts disabled:

     ;
     ; (Assume interrupts are enabled at this point)
     ;
     pushf
     cli
     .
     .
     popf            ; Interrupts are still OFF!

Note that since some implementations of DPMI will maintain a virtual interrupt state for protected mode DOS programs, the current value of the interrupt flag may not reflect the current virtual interrupt state. Protected mode programs should use the virtual interrupt state services to determine the current interrupt flag state (see page 99).

Since cli and sti are privileged instructions, they will cause a protection violation and the DPMI provider will simulate the instruction. Because of the overhead involved in processing the exception, cli and sti should be used as little as possible. In general, you should expect either of these instructions to require at least 300 clocks.