实验要求
使用 nasm 汇编编程实现一个 MBR 程序,每隔 1 秒钟左右输出一个 (组)字符,具体内容包括:
1)实现一个时钟中断处理例程,按规定时间间隔输出字符(可使用 BIOS 提供的 int 10h 实现字符输 出);
2)修改中断向量表,将时钟中断处理例程起始地址填入中断向量表对应表项;
3)设置 8253/4 定时器芯片,每隔 20ms 左右(自定 < 1 秒)产生一次时钟中断。
基本数据设置
TIMER_FREQ equ 1193182
TARGET_FREQ equ 50
DIVISOR equ TIMER_FREQ / TARGET_FREQ
[BITS 16]
[ORG 0x7C00]
这段代码是用于设置定时器频率的。首先定义了两个常数:TIMER_FREQ(定时器频率)和 TAR_GET_FREQ(目标频率)。然后计算这两个频率之间的比率,并将结果存储在 DIVISOR(除数)变量 中。
[BITS 16] 表示汇编器以 16 位模式工作。在这种模式下,寄存器的大小为 16 位,寻址范围为 0x0000 到 0xFFFF。
[ORG 0x7C00] 表示代码段从地址 0x7C00 开始。在实模式下,程序从地址 0x0000 开始,但是由于 BIOS 将 CS:IP 初始化为 0x0000:0x0000,所以程序实际上从地址 0x7C00 开始执行。
基本设置
clear:;清屏
mov ax, 0x600 ;ah中输入功能号
mov bx, 0x700 ;设置上卷行属性,0x70表示用黑底白字的属性填充空白行
mov cx, 0 ;左上角: (0, 0)
mov dx, 0x184f ;右下角: (80,25),
int 0x10 ;int 0x10
mouse:;让光标处于最左上角
mov ah, 2 ;输入: 2号子功能是设置光标位置,需要存入ah寄存器
mov bh, 0 ;bh寄存器存储的是待设置光标的页号
mov dh, 0 ;dh寄存器存储的是行号
mov dl, 0 ;dl寄存器存储的是列号
int 0x10 ;输出: 无
因为我们需要在屏幕上打印字符串,而原先的页面上有 booting from hard disk 等等内容,所以我们 需要先将原先的页面清屏,之后我们需要将光标移到左上角,这样子可以使得打印字符串从页面的最上面 开始打印。
初始化中断处理程序
set_interrupt_handler_assembly: ;初始化中断处理程序
CLI
xor ax, ax
mov ds, ax ; 将ds设置为0,因为IVT位于内存的最低端
mov word [8*4], handle_interrupt_assembly ; 设置中断处理程序的偏移地址
mov word [8*4+2], cs ; 设置中断处理程序的段地址
STI ; 重新开启中断
首先,关闭中断(CLI),然后将一些寄存器清零,并将数据段(DS)设置为 0。这是因为中断向量 表(IVT)位于内存的最低端,所以我们需要将段地址(CS)设置为 0。接下来,它将中断处理程序的偏 移地址(timer_interrupt)写入 IVT 中的位置 84,并将中断处理程序的段地址(CS)写入 IVT 中的位 置 84+2。最后,重新开启中断(STI).
初始化计时器
init_timer:
mov al, 0x36 ; 控制字到0x43
out 0x43, al
mov ax, DIVISOR ; 加载除数值
out 0x40, al ; 通道0,用于产生时钟中断
mov al, ah
out 0x40, al
这段代码是用于初始化一个定时器。定时器用于产生时钟中断,从而实现时间控制。
计数器
wait_for_interrupt:
hlt ; 等待中断
jmp wait_for_interrupt ; 无限循环
handle_interrupt_assembly:
INC CX
CMP CX, 50
JE Print
mov al, 0x20 ; AL = 0x20 表示发送EOI信号给PIC
out 0x20, al ; 发送EOI信号
IRET ; 从中断返回
这段代码实现了一个简单的定时器中断。
首先,程序进入 wait_for_interrupt 函数,这里使用 hlt 指令使 CPU 暂停,然后使用 jmp 指令无限 循环等待中断。
当定时器中断发生时,程序会跳转到 timer_interrupt 函数。首先,程序会递增计数器 CX 的值,然 后比较 CX 和 50 的大小。如果 CX 等于 50,那么就跳转到 Print 函数。
在 timer_interrupt 函数中,程序将 AL 寄存器的值设置为 0x20,表示发送 EOI 信号给 PIC(可编 程中断控制器)。然后使用 out 指令将 EOI 信号发送给 PIC。最后,使用 IRET 指令从中断返回。
打印函数
Print:
CLI
XOR CX, CX
mov ah, 0x0E ; teletype输出
mov al, 'I' ; 显示字符
int 0x10 ; BIOS视频服务
mov ah, 0x0E ; teletype输出
mov al, ' ' ; 显示字符
int 0x10 ; BIOS视频服务
mov ah, 0x0E ; teletype输出
mov al, 'l' ; 显示字符
int 0x10 ; BIOS视频服务
这边选择了逐个字符打印“I love OS”,最后打印一个换行即可,当然也可以将需要打印的data整体传入打印。
实现效果
将该汇编程序编译成bin文件后,用qemu运行该bin文件,实现效果如下:
程序源码如下:
TIMER_FREQ equ 1193182
TARGET_FREQ equ 50
DIVISOR equ TIMER_FREQ / TARGET_FREQ
[BITS 16]
[ORG 0x7C00]
clear:;清屏
mov ax, 0x600 ;ah中输入功能号
mov bx, 0x700 ;设置上卷行属性,0x70表示用黑底白字的属性填充空白行
mov cx, 0 ;左上角: (0, 0)
mov dx, 0x184f ;右下角: (80,25),
int 0x10 ;int 0x10
mouse:;让光标处于最左上角
mov ah, 2 ;输入: 2号子功能是设置光标位置,需要存入ah寄存器
mov bh, 0 ;bh寄存器存储的是待设置光标的页号
mov dh, 0 ;dh寄存器存储的是行号
mov dl, 0 ;dl寄存器存储的是列号
int 0x10 ;输出: 无
set_interrupt_handler_assembly: ;初始化中断处理程序
CLI
xor ax, ax
mov ds, ax ; 将ds设置为0,因为IVT位于内存的最低端
mov word [8*4], handle_interrupt_assembly ; 设置中断处理程序的偏移地址
mov word [8*4+2], cs ; 设置中断处理程序的段地址
STI ; 重新开启中断
init_timer:
mov al, 0x36 ; 控制字到0x43
out 0x43, al
mov ax, DIVISOR ; 加载除数值
out 0x40, al ; 通道0,用于产生时钟中断
mov al, ah
out 0x40, al
wait_for_interrupt:
hlt ; 等待中断
jmp wait_for_interrupt ; 无限循环
handle_interrupt_assembly:
INC CX
CMP CX, 50
JE Print
mov al, 0x20 ; AL = 0x20 表示发送EOI信号给PIC
out 0x20, al ; 发送EOI信号
IRET ; 从中断返回
Print:
CLI
XOR CX, CX
mov ah, 0x0E ; teletype输出
mov al, 'I' ; 显示字符
int 0x10 ; BIOS视频服务
mov ah, 0x0E ; teletype输出
mov al, ' ' ; 显示字符
int 0x10 ; BIOS视频服务
mov ah, 0x0E ; teletype输出
mov al, 'l' ; 显示字符
int 0x10 ; BIOS视频服务
mov ah, 0x0E ; teletype输出
mov al, 'o' ; 显示字符
int 0x10 ; BIOS视频服务
mov ah, 0x0E ; teletype输出
mov al, 'v' ; 显示字符
int 0x10 ; BIOS视频服务
mov ah, 0x0E ; teletype输出
mov al, 'e' ; 显示字符
int 0x10 ; BIOS视频服务
mov ah, 0x0E ; teletype输出
mov al, ' ' ; 显示字符
int 0x10 ; BIOS视频服务
mov ah, 0x0E ; teletype输出
mov al, 'O' ; 显示字符
int 0x10 ; BIOS视频服务
mov ah, 0x0E ; teletype输出mov ah, 0x0E ; teletype输出
mov al, 'S' ; 显示字符
int 0x10 ; BIOS视频服务
;换行
mov ah, 0x0E ; teletype输出
mov al, 0x0D ; 显示字符
int 0x10 ; BIOS视频服务
mov ah, 0x0E ; teletype输出
mov al, 0x0A ; 显示字符
int 0x10 ; BIOS视频服务
STI
mov al, 0x20 ; AL = 0x20 表示发送EOI信号给PIC
out 0x20, al ; 发送EOI信号
IRET ; 从中断返回
TIMES 510-($-$$) DB 0
DW 0xAA55