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
实际演示对比
- 去掉CLI/STI的情况:运行代码时,会看到在
delay_loop执行过程中,时钟中断频繁触发,打印出的counter值是未完成加1000的中间值(比如初始的0,还没执行add ax,1000就被打断),导致数据逻辑混乱。 - 保留CLI/STI的情况:时钟中断只会在临界区执行完成后触发,打印的
counter值都是已经加完1000的正确值(比如0→1000→2000…),因为中断被屏蔽,临界区的操作不会被打断。
核心要点总结
cli:清除中断标志位(IF),屏蔽所有可屏蔽硬件中断(如时钟、键盘中断),但不可屏蔽中断(NMI)和int指令触发的软件中断不受影响。sti:置位中断标志位(IF),恢复对可屏蔽硬件中断的响应。- 临界区:必须原子执行的代码段,一旦被中断打断会导致数据竞争或逻辑错误,
cli/sti是实模式下保护临界区的常用手段。 - 注意:中断处理函数中必须发送EOI(
out 0x20, 0x20)给PIC,否则硬件会停止触发后续中断。
内容的提问来源于stack exchange,提问作者Bastien Portigo
相关产品推荐
相关产品推荐

