4. CMOS RAM读写与系统时间获取

任务目标

本案例通过x86 I/O端口访问CMOS RAM,实现:读取系统当前时间(时:分:秒)与日期(年-月-日),并修改CMOS中某个字节(如秒寄存器)以观察写入效果。代码运行于实模式环境(如QEMU + FreeDOS或裸机引导),直接操作端口0x70/0x71。

CMOS端口访问原理

CMOS RAM通过两个端口访问:0x70写入要访问的寄存器地址,0x71读写该寄存器数据。时间寄存器以BCD码存储,需转换为十进制显示。注意:访问CMOS前应关闭NMI中断(将0x70最高位置1),读取时若正在更新(寄存器0x0A的bit 7为1)需等待。

关键完整代码


; cmos_time.asm - 读取并修改CMOS时间
; 编译: nasm -f bin cmos_time.asm -o cmos_time.com
; 运行: 在DOS/FreeDOS中执行 cmos_time.com,或放入引导扇区

org 0x100
cpu 8086

; 等待CMOS更新完成
wait_update:
    mov al, 0x0A
    out 0x70, al
    in al, 0x71
    test al, 0x80
    jnz wait_update
    ret

; 读取指定CMOS寄存器,返回BCD值在AL
read_cmos:
    out 0x70, al
    in al, 0x71
    ret

; BCD转ASCII并打印
print_bcd:
    push ax
    mov al, ah
    shr al, 4
    add al, '0'
    mov ah, 0x0E
    int 0x10
    pop ax
    push ax
    mov al, ah
    and al, 0x0F
    add al, '0'
    mov ah, 0x0E
    int 0x10
    pop ax
    ret

start:
    ; 关闭NMI并等待更新完成
    mov al, 0x80
    out 0x70, al
    call wait_update

    ; 读取秒 (寄存器0x00)
    mov al, 0x00
    call read_cmos
    mov ah, al
    call print_bcd

    ; 打印冒号
    mov al, ':'
    mov ah, 0x0E
    int 0x10

    ; 读取分 (寄存器0x02)
    mov al, 0x02
    call read_cmos
    mov ah, al
    call print_bcd

    ; 打印冒号
    mov al, ':'
    mov ah, 0x0E
    int 0x10

    ; 读取时 (寄存器0x04)
    mov al, 0x04
    call read_cmos
    mov ah, al
    call print_bcd

    ; 换行
    mov al, 0x0D
    mov ah, 0x0E
    int 0x10
    mov al, 0x0A
    mov ah, 0x0E
    int 0x10

    ; 修改秒寄存器:写入BCD值0x30(即30秒)
    call wait_update
    mov al, 0x80 | 0x00    ; 关闭NMI,选择秒寄存器
    out 0x70, al
    mov al, 0x30
    out 0x71, al

    ; 等待1秒后重新读取验证
    mov cx, 0xFFFF
delay:
    loop delay

    call wait_update
    mov al, 0x00
    call read_cmos
    mov ah, al
    call print_bcd

    ; 程序结束(返回DOS)
    mov ax, 0x4C00
    int 0x21

编译与运行

  1. 安装NASM:sudo apt install nasm(Linux)或下载Windows版。
  2. 编译:nasm -f bin cmos_time.asm -o cmos_time.com
  3. 运行方式(任选其一):
    • 使用QEMU + FreeDOS:qemu-system-i386 -fda fdos.img,将cmos_time.com放入镜像后执行。
    • 裸机引导:将生成的.com文件作为引导扇区(需额外处理,本案例不展开)。

验证方法与判定标准

  • 预期输出:程序首先打印当前时间(格式HH:MM:SS),随后将秒强制设为30,延时后再次打印秒值,应显示30(或30±1,若恰好跨越秒边界)。
  • 通过条件:两次打印的秒值中第二次为30,且时间格式正确、无乱码。
  • 观察修改效果:若在修改后立即用其他工具(如DOS的DEBUG)读取CMOS寄存器0x00,其值应为0x30(BCD),证明写入成功。

注意事项

  • 修改CMOS秒寄存器会导致系统时间跳变,在真实硬件上操作需谨慎;本案例建议在虚拟机中执行。
  • 若在Windows/Linux现代操作系统下直接运行,会因端口权限受限而失败;必须使用实模式环境。
  • BCD转换仅适用于二进制时间格式(寄存器0x0B的bit 2为0时);若系统设为二进制格式,需改用二进制转换逻辑。