You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

x86 16位汇编cli/sti指令实用示例需求及相关疑问

x86 16位汇编中CLI/STI的实用演示示例

场景说明:时钟中断下的临界区保护

PC机的8259可编程中断控制器(PIC)默认会把**时钟中断(IRQ0)**映射到int 0x08,这个中断每秒触发约18.2次,会强行打断当前正在执行的代码。如果我们有一段需要原子执行的操作(比如修改全局计数器),不屏蔽中断的话,中断可能会在操作中途插入,导致数据不一致。用cli和sti包裹这段代码,就能屏蔽中断、保护临界区,操作完成后再恢复中断响应。

示例代码(16位实模式内核)

; 16位实模式内核示例,演示CLI/STI对硬件中断的控制
org 0x1000          ; 假设内核加载到0x1000:0000地址处

section .data
counter dw 0        ; 全局计数器,主程序和时钟中断都会修改它
msg_cli db 'CLI: 进入临界区,时钟中断已屏蔽', 0x0d, 0x0a, 0
msg_sti db 'STI: 退出临界区,时钟中断已恢复', 0x0d, 0x0a, 0
msg_interrupt db '时钟中断触发!当前counter值: ', 0

section .text
start:
    ; 保存原时钟中断向量(int 0x08)
    mov ax, 0
    mov es, ax
    mov ax, [es:0x08*4]
    mov [old_int08_off], ax
    mov ax, [es:0x08*4+2]
    mov [old_int08_seg], ax

    ; 注册自定义时钟中断处理函数
    mov word [es:0x08*4], int08_handler
    mov word [es:0x08*4+2], cs

    ; 主循环:反复执行需要保护的临界区操作
main_loop:
    ; 打印进入临界区提示
    mov si, msg_cli
    call print_string

    ; 屏蔽可屏蔽硬件中断,进入临界区
    cli

    ; 模拟原子操作:counter += 1000
    mov ax, [counter]
    ; 插入长延迟,放大被中断打断的概率
    mov cx, 0xffff
delay_loop:
    loop delay_loop
    add ax, 1000
    mov [counter], ax

    ; 恢复可屏蔽硬件中断,退出临界区
    sti

    ; 打印退出临界区提示
    mov si, msg_sti
    call print_string

    ; 短暂延迟,给中断触发的机会
    mov cx, 0x8000
delay_main:
    loop delay_main

    jmp main_loop

; 自定义时钟中断处理函数
int08_handler:
    pusha
    push ds
    push es

    mov ax, cs
    mov ds, ax
    mov es, ax

    ; 打印中断触发提示
    mov si, msg_interrupt
    call print_string
    ; 打印当前counter的十六进制值
    mov ax, [counter]
    call print_hex_word
    mov al, 0x0d
    call print_char
    mov al, 0x0a
    call print_char

    ; 发送EOI信号给PIC,允许后续中断触发
    mov al, 0x20
    out 0x20, al

    pop es
    pop ds
    popa
    ; 跳转到原时钟中断处理函数
    jmp far [old_int08_seg:old_int08_off]

; 字符串打印函数
print_string:
    lodsb
    or al, al
    jz print_string_done
    call print_char
    jmp print_string
print_string_done:
    ret

; BIOS中断打印单个字符
print_char:
    mov ah, 0x0e
    int 0x10
    ret

; 打印16位十六进制数
print_hex_word:
    mov cx, 4
print_hex_loop:
    rol ax, 4
    mov bl, al
    and bl, 0x0f
    add bl, '0'
    cmp bl, '9'
    jbe print_hex_char
    add bl, 7          ; 转换为A-F
print_hex_char:
    mov al, bl
    call print_char
    loop print_hex_loop
    ret

; 存储原时钟中断向量的段和偏移
old_int08_off dw 0
old_int08_seg dw 0

实际演示对比

  1. 去掉CLI/STI的情况:运行代码时,会看到在delay_loop执行过程中,时钟中断频繁触发,打印出的counter值是未完成加1000的中间值(比如初始的0,还没执行add ax,1000就被打断),导致数据逻辑混乱。
  2. 保留CLI/STI的情况:时钟中断只会在临界区执行完成后触发,打印的counter值都是已经加完1000的正确值(比如0→1000→2000…),因为中断被屏蔽,临界区的操作不会被打断。

核心要点总结

  • cli:清除中断标志位(IF),屏蔽所有可屏蔽硬件中断(如时钟、键盘中断),但不可屏蔽中断(NMI)和int指令触发的软件中断不受影响。
  • sti:置位中断标志位(IF),恢复对可屏蔽硬件中断的响应。
  • 临界区:必须原子执行的代码段,一旦被中断打断会导致数据竞争或逻辑错误,cli/sti是实模式下保护临界区的常用手段。
  • 注意:中断处理函数中必须发送EOI(out 0x20, 0x20)给PIC,否则硬件会停止触发后续中断。

内容的提问来源于stack exchange,提问作者Bastien Portigo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 18:54:51