汇编实例解析--硬件中断显示时间,软中断读取显示键盘输入

1.1.实模式16位下,利用硬件中断显示时间

SECTION header vstart=0       
    ; 头4字节,应用程序尺寸               
    program_length  dd program_end     
    ; 入口点的段内偏移     
    code_entry      dw start     
                    ; 4字节的段起始位置距离应用起始位置的偏移,应用被加载到物理内存时,
                    ; 这里也会被修改为段起始物理内存位置对应的段地址           
                    dd section.code.start    
                    ; 重定位表项数
    realloc_tbl_len dw (header_end-realloc_begin)/4

realloc_begin:
    code_segment    dd section.code.start   
    data_segment    dd section.data.start   
    stack_segment   dd section.stack.start  
header_end:  

; 代码段
SECTION code align=16 vstart=0    
; 这是一个中断处理函数   
; 一个函数定义    
new_int_0x70:
      ; 中断会打断正在运行的应用的执行
      ; 所以需要无痕---结束时回到应用时应用的环境和打断前一致
      push ax 
      push bx
      push cx
      push dx
      push es 
      
  .w0:                                    
      mov al,0x0a     
      ; 0x8a                   
      or al,0x80     
      ; 向0x70写入0x8a                     
      out 0x70,al 
      ; 读取寄存器a的内容
      in al,0x71 
      test al,0x80  
      ; 直到读出是0x80才继续
      jnz .w0  
      ; 到这里表示一段时间内更新周期不会启动,意味着有足够时间做中断处理
      xor al,al 
      or al,0x80 
      ; 将0x80写入0x70
      out 0x70,al 
      ; 读取0x71---读出秒的BCD编码
      in al,0x71 
      ; 秒数据入栈,实际只需1字节存储,但入栈入了2字节
      push ax 

      mov al,2 
      ; 0x82
      or al,0x80
      ; 向端口0x70写入0x82
      out 0x70,al 
      ; 读出BCD编码的分
      in al,0x71 
      ; 分入栈保存
      push ax 

      mov al,4  
      ; 0x84
      or al,0x80
      ; 向0x70写入0x84
      out 0x70,al 
      ; 读出以BCD编码的时
      in al,0x71 
      ; 时入栈保存
      push ax 

      mov al,0x0c  
      ; 向0x70写入0x0c
      out 0x70,al 
      ; 读出寄存器c的1字节数据,每次中断后都要读出寄存器c的内容,才能再次传递中断给处理器
      in al,0x71 

      ; 设置es指向显存
      mov ax,0xb800
      mov es,ax 
      
      ; 出栈得到时
      pop ax 
      ; 将bcd编码的数值转换为ascii码
      call bcd_to_ascii 
      ; 显存第12行,第36个字符处
      mov bx,12*160 + 36*2 
      ; 时间显示
      mov [es:bx],ah 
      mov [es:bx+2],al 
      
      ; 字符':'显示
      mov al,':' 
      mov [es:bx+4],al 
      not byte [es:bx+5] 
      
      ; 出栈得到分
      pop ax 
      ; 编码转换
      call bcd_to_ascii
      ; 显示
      mov [es:bx+6],ah
      mov [es:bx+8],al         
      
      ; 字符':'显示
      mov al,':'
      mov [es:bx+10],al   
      not byte [es:bx+11] 
      
      ; 出栈得到秒
      pop ax 
      ; 编码转换
      call bcd_to_ascii
      ; 显示
      mov [es:bx+12],ah
      mov [es:bx+14],al        
      
      mov al,0x20   
      ; 向端口0xa0写入0x20        
      out 0xa0,al 
      ; 向端口0x20写入0x20
      out 0x20,al 
      ; 上述目的是处理器告知中断处理器当前中断处理结束。以便中断处理器可以协调后续中断。
      
      ; 过程结束,恢复被过程污染的寄存器
      pop es 
      pop dx
      pop cx
      pop bx
      pop ax
      
      ; 中断处理进入前处理器已经做了压栈跳转,所以用iret以便返回被中断进程
      iret 

; 编码转换
; 段内函数
bcd_to_ascii:      
      ; al放入ah备份               
      mov ah,al    
      ; al保留低4位
      and al,0x0f  
      ; 数值上加上0x30,这样就是十进制个位数的ascii编码了
      add al,0x30  
      ; 移除低4位,这样ah现在低4位是原来al的高4位
      shr ah,4     
      and ah,0x0f                        
      ; 这样得到十进制十位数的ascii编码了
      add ah,0x30
      ret

; 入口点
; 转到入口点开始执行时候
; ds已经被设置为应用在物理内存起始位置对应的段地址
; cs为也已经被设置为入口点所在段的内存起始位置的段地址
start: 
      ; 从该处取2字节
      mov ax,[stack_segment] 
      ; 2字节是栈段起始位置在内存中对应的段偏移
      mov ss,ax 
      ; 栈指针,初始时候执行栈空间地址最高处
      mov sp,ss_pointer 
      ; 设置ds为应用数据段在内存起始位置对应的段地址,便于访问段内数据
      mov ax,[data_segment] 
      mov ds,ax 
      ; bx被设置为字符串首个字符的段内偏移
      mov bx,init_msg 
      ; 字符串显示
      call put_string 
     
      ; 字符串显示
      mov bx,inst_msg  
      call put_string
      
      mov al,0x70
      mov bl,4
      ; al * bl,结果在ax
      mul bl 
      mov bx,ax        
      ; 禁止中断
      cli 
      ; es入栈保存
      push es 
      mov ax,0x0000
      ; 设置es为0x0000
      mov es,ax 
      ; 这是将2字节段内偏移设定到指定物理内存位置
      mov word [es:bx],new_int_0x70 
      ; 这是将2字节段地址设置到指定物理内存位置
      mov word [es:bx+2],cs 
      ; 恢复es
      pop es 

      ; 寄存器a用来做多线程同步互斥,因为控制器的时间信息控制器会定时更新,这时处理器不可读
      ; 寄存器b用于控制中断开启,开启方式
      ; 寄存器c用于读取中断信息,上一中断信息未读取出,下一中断就不会产生
      mov al,0x0b 
      ; 结果0x8b
      or al,0x80   
      ; 向端口0x70写入0x8b
      out 0x70,al 
      ; 指示寄存器b
      mov al,0x12 
      ; 向寄存器b写入0x12
      ; 允许每个更新周期结束产生中断+更新周期每秒都发生+24小时格式
      out 0x71,al 

      mov al,0x0c 
      ; 向0x70写入0x0c
      out 0x70,al 
      ; 从寄存器c读出1字节
      in al,0x71 

      ; 从端口0xa1读出1字节
      in al,0xa1 
      ; 清除最低位
      and al,0xfe 
      ; 写回
      ; 目的是让RTC中断可被8259处理
      out 0xa1,al 
      ; 开放设备中断
      sti  

      ; 设置bx指向字符串起始位置段内偏移 
      mov bx,done_msg   
      call put_string
      
      ; 设置bx指向字符串起始位置段内偏移
      mov bx,tips_msg   
      call put_string
      
      mov cx,0xb800
      ; 设置ds指向显存段
      mov ds,cx
      ; 第12行,第33个字符【从0计算】
      mov byte [12*160 + 33*2],'@'    
       
 .idle:
      ; 休眠,中断会唤醒
      hlt 
      ; 对第33个字符的显示属性取反
      not byte [12*160 + 33*2+1]    
      ; 跳回去 
      jmp .idle

put_string:                              
         mov cl,[bx]
         or cl,cl                        
         jz .exit                         
         call put_char
         inc bx 
         jmp put_string
   .exit:
         ret

put_char:                               
         push ax
         push bx
         push cx
         push dx
         push ds
         push es 

         mov dx,0x3d4 
         mov al,0x0e  
         out dx,al    
         mov dx,0x3d5
         in al,dx      
         mov ah,al    

         mov dx,0x3d4
         mov al,0x0f
         out dx,al
         mov dx,0x3d5
         in al,dx      
         mov bx,ax     

         cmp cl,0x0d   
         jnz .put_0a   
         mov ax,bx      
         mov bl,80                       
         div bl        
         mul bl       
         mov bx,ax    
         jmp .set_cursor 

 .put_0a:
         cmp cl,0x0a       
         jnz .put_other    
         add bx,80        
         jmp .roll_screen 

 .put_other:                            
         mov ax,0xb800
         mov es,ax
         shl bx,1 
         mov [es:bx],cl 

         shr bx,1 
         add bx,1 

 .roll_screen:
         cmp bx,2000     
         jl .set_cursor  

         mov ax,0xb800
         mov ds,ax
         mov es,ax
         cld 
         mov si,0xa0
         mov di,0x00
         mov cx,1920
         rep movsw 
         mov bx,3840
         mov cx,80
 .cls:
         mov word[es:bx],0x0720
         add bx,2
         loop .cls
         mov bx,1920

 .set_cursor:
         mov dx,0x3d4
         mov al,0x0e
         out dx,al

         mov dx,0x3d5
         mov al,bh
         out dx,al

         mov dx,0x3d4
         mov al,0x0f
         out dx,al

         mov dx,0x3d5
         mov al,bl
         out dx,al

         pop es
         pop ds
         pop dx
         pop cx
         pop bx
         pop ax
         ret 

SECTION data align=16 vstart=0
    init_msg       db 'Starting...',0x0d,0x0a,0
    inst_msg       db 'Installing a new interrupt 70H...',0
    done_msg       db 'Done.',0x0d,0x0a,0
    tips_msg       db 'Clock is now working.',0
                   
SECTION stack align=16 vstart=0
                 resb 256
ss_pointer:

; 段不指定vstart时候,段内标号是程序起始位置到标号位置的偏移
SECTION program_trail
program_end:

1.2.利用软中断读取键盘输入并显示到屏幕

        实模式16位下

SECTION header vstart=0    
                    ; 4字节的进程尺寸                  
    program_length  dd program_end       
                    ; 2字节的入口点的段内偏移   
    code_entry      dw start     
                    ; 4字节的入口点所在段距离进程起始位置偏移
                    ; 被重定位后,前2字节变为入口点所在段的物理内存起始位置的段地址           
                    dd section.code.start   
                    ; 重定位项数
    realloc_tbl_len dw (header_end-realloc_begin)/4
realloc_begin:
                    ; 代表段起始位置距离进程起始位置的偏移量
                    ; 重定位后变为段起始位置在物理内存位置的段地址
    code_segment    dd section.code.start   
    data_segment    dd section.data.start   
    stack_segment   dd section.stack.start  
header_end:                
    
SECTION code align=16 vstart=0            
start:
      mov ax,[stack_segment]
      ; ss指向栈段的物理位置的段地址
      mov ss,ax
      ; 栈一开始偏移量在高地址处
      mov sp,ss_pointer 
      mov ax,[data_segment] 
      ; 设置ds指向数据段,便于访问段内数据
      mov ds,ax 
      ; cx存储要显示字符数量
      mov cx,msg_end-message 
      ; 待显示字符序列首个字符的段内偏移
      mov bx,message 
      
 .putc:
      ; 高位是字符显示属性
      mov ah,0x0e 
      ; 低位是字符
      mov al,[bx] 
      ; 出发软中断
      ; ah为0x0e下,这个软中断的处理函数会在光标位置显示al中字符,并推进光标位置
      int 0x10 
      ; 更新bx到下一位置
      inc bx 
      loop .putc

 .reps:
      ; 设置ah为0x00
      mov ah,0x00 
      ; 软中断
      ; ah为0x00时,此软中断是从键盘读取字符,字符存储在al
      int 0x16 
      mov ah,0x0e 
      mov bl,0x07
      ; 软中断
      ; ah为0x0e时,此软中断在光标位置显示al中字符,并推进光标到下一位置
      int 0x10 
      jmp .reps

SECTION data align=16 vstart=0
    message       db 'Hello, friend!',0x0d,0x0a
                  db 'This simple procedure used to demonstrate '
                  db 'the BIOS interrupt.',0x0d,0x0a
                  db 'Please press the keys on the keyboard ->'
    msg_end:
                   
SECTION stack align=16 vstart=0
                 resb 256
ss_pointer:

SECTION program_trail
program_end:

猜你喜欢

转载自blog.csdn.net/x13262608581/article/details/125691656