3

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

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显式保存/恢复中断标志,而不是简单地使用POPF恢复以前的 FLAGS 寄存器?

4

1 回答 1

4

您正在查看的代码有优点。写这篇文章的人可能知道,在所有不同的操作模式下,POPF不会以相同的方式处理中断标志 ( IF )。在此处输入图像描述 在此处输入图像描述


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

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

这两种情况是IF实际更改的地方,POPF预计将IF恢复到以前的值。如果您查看我圈出的第一张图N。在这些情况下,您处于保护模式、兼容模式或 64 位模式,并且当前特权级别 (CPL) 为 1、2、3 且 IOPL(IO 特权级别)< CPL。在这些情况下, POPF不会产生一般保护故障,并且会默默地忽略对IF的更改。

因为没有故障,内核不知道有人尝试更改IF,因此没有机会虚拟化IF当STICLI没有适当的 IOPL 权限时,它们会充当特权指令,并且会在 CPU 可以虚拟化IF的地方出现故障。

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


DPMI(DOS 保护模式接口)规范讨论了这个问题并以这种方式描述:

2.3 中断标志​​管理 popf 和 iret 指令可能不会修改中断标志的状态,因为大多数 DPMI 实现将运行 IOPL < DPL 的程序。程序必须执行 cli 或 sti 来修改中断标志状态。

这意味着以下代码序列将禁用中断:

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

请注意,由于 DPMI 的某些实现将为受保护模式的 DOS 程序维护虚拟中断状态,因此中断标志的当前值可能无法反映当前的虚拟中断状态。保护模式程序应使用虚拟中断状态服务来确定当前中断标志状态(参见第 99 页)。

由于 cli 和 sti 是特权指令,它们将导致保护违规,并且 DPMI 提供程序将模拟该指令。由于处理异常涉及开销,因此应尽可能少使用 cli 和 sti。通常,您应该期望这些指令中的任何一个都需要至少 300 个时钟。

于 2019-02-25T12:37:30.480 回答