汇编实验

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_31481187/article/details/54561383

实验报告

题目

编写一个可以自行启动的计算机,不需要在现有操作系统环境中运行的程序

实验内容

  • 裸机编程,不需要操作系统
  • 能够实现查看系统时间,修改系统时间,重启,进入硬盘操作系统等功能

实验要求

提交源码,实验报告

实验原理

系统启动初始化完成后最终调用19h,该中断读取软盘的第一扇区或硬盘的第一扇区到0:7c00开始的512个字节的内存空间中,并将 CS:IP 指向0:7C00h执行

实验步骤

实验主要可以分为两部分:

  • 主引导程序,存在于软盘第0扇区,由BIOS的19h中断读取到0:7c00开始的内存单元中,并执行0:7c00的第一行代码。这一段的任务是将软盘1,2扇区的数据读入内存,并执行。(这里读入到了0800:0开始的内存中)
  • 系统程序,存放所有运行所需的程序和子程序

实验环境

  • RadASM
  • VMware Workstation Pro(Windows XP操作系统虚拟机)
  • BOOTICEx64

设计细节及程序讲解

设计细节

  • 系统程序启动后,首先打印系统菜单,即多个字符串,打印完毕后,调用int16h中断,等待用户输入,输入只有1~5是有效的,其他输入不会被接受,五个数字对应的功能分别是

    1 查看系统时间
    2 修改系统时间
    3 重启系统
    4 进入磁盘系统
    5 改变颜色

  • 输入编号进入对应功能

  • 查看系统时间
    首先进行清屏操作,然后通过端口读取CMOS的不同存储地址的信息得到系统时间,并居中显示。
    同样具有按下5键改变颜色的功能,在循环刷新时间的过程中判断输入即可。因为一直在循环刷新时间所以按下5键改变颜色时,刷新速度会非常快,出现颜色闪烁的效果
    按下q键返回主菜单,实现方法是跳转到程序开头处,实际上相当于重新显示了菜单

  • 修改系统时间
    首先进行清屏操作,并显示提示输入格式的字符串
    用户键盘进行输入,将输入存入预先申请的内存空间的同时,在屏幕的左上角对输入进行显示,这是通过写入显存实现的
    读取用户输入完毕后,进行ASCII与BCD码的转换,存入另一段预先申请的内存空间
    转换完毕后,通过端口将参数送入CMOS,修改时间
    修改完毕后,调用显示时间函数,立刻显示了修改后的时间(此时具有按下5键改变颜色的功能,闪烁)
    此时,可以按下q键返回主界面

  • 重启系统
    通过重写 CS:IP 为0ffffh:0000h,重新启动系统

  • 进入磁盘操作系统
    通过调用int13h中断的2号功能实现的,具体做法是初始化内存地址并设置好用于启动硬盘系统的参数,将硬盘系统的引导写入0000:700c,并置CS:IP为0000:700c,从此处执行硬盘系统的引导程序,即可进入硬盘操作系统

  • 修改颜色
    当能捕捉到5键时,进入改变颜色函数,将显存部分颜色位进行修改

代码讲解

  • 系统启动,显示菜单
start:
  jmp sys_start

 ;菜单选项字符串(menuoptions)
mo0 db 'A Simple System Meum',0
mo1 db '1. Show System Time',0
mo2 db '2. Set System Time',0
mo3 db '3. Restart System',0
mo4 db '4. Go Disk System',0
mo5 db 'Created By ChickSheep',0

 ;函数偏移
s_function dw showtime,settime,restart_sys,go_disksys
sys_start:
  mov ax,run_sys
  mov ds,ax
  call Clearall                ;系统启动时先执行清屏操作

  ;显示菜单
show_meum:
  ;设定显示的行列数
  mov cl,01h                  ;设定颜色,初始为蓝色
  mov dl,30                   ;初始化列数

  mov dh,9                    ;初始化行数
  mov si,offset mo0           ;定位到字符串mo0偏移
  call show_str

  mov dh,10                   ;初始化行数
  mov si,offset mo1           ;定位到字符串mo1偏移
  call show_str

  mov dh,11                   ;初始化行数
  mov si,offset mo2           ;定位到字符串mo2偏移
  call show_str

  mov dh,12                   ;初始化行数
  mov si,offset mo3           ;定位到字符串mo3偏移
  call show_str

  mov dh,13                   ;初始化行数
  mov si,offset mo4           ;定位到字符串mo4偏移
  call show_str

  mov dh,14                   ;初始化行数
  mov si,offset mo5           ;定位到字符串mo5偏移
  call show_str
  • 监测用户输入
 sys_input:
  mov ah,00h
  int 16h                        ;调用int 16h中断0号功能,查看键盘输入,为阻塞状态
  mov bx,0
  mov bl,al                      ;将键盘的ASCII码存入bl
  mov al,30H
  sub bl,al                      ;将得到的ASCII码转换为数字
  cmp bx,1                       ;若小于1则返回等待输入
  jb short sys_input
  cmp bx,5
  ja short sys_input             ;若大于5则返回等待输入
  cmp bx,5                       ;若为1~4,则进入选择功能函数部分
  jne func
  call changecolor               ;若等于5则改变颜色
  jmp sys_input                  ;结果处理完毕,返回监测输入

 func:
  call Clearall                  ;执行各个指令前先清屏
  sub bx,1
  add bx,bx                      ;得到对应功能的函数偏移值
  call s_function[bx]            ;进入功能函数
  call Clearall                  ;功能执行完毕后,清屏,等待重新显示主界面
  jmp show_meum
  • 显示时间
showtime proc                   ;显示时间的函数
  jmp short showtime1
 s1:
    db 9,8,7,4,2,0              ;CMOS中时间参数的存储位置
 s2:
    db '/','/',' ',':',':',' '  ;时间参数分隔符号

 showtime1:
    mov cx,6                    ;CX=6
    mov bx,offset s1            ;BX指向s,  s为70端口每次所需要的入口参数的首地址
    mov si,offset s2            ;SI指向s1, s1为显示格式的分隔符号
    mov di,0                    ;屏幕定位下标DI,初始为0,运行中自增
 showtime2:
    push cx                     ;保存循环次数
    mov al,ds:[bx]              ;取当前需要的70端口的入口参数
    mov dl,ds:[si]              ;取当前需要的分隔符号
    out 70h,al                  ;al->PORT 70,指示要访问单元的地址
    in al,71h                   ;PORT 71->al,读出数据
    mov ah,al                   
    mov cl,4    
    shr ah,cl                   ;ah取右移取高四位
    and al,00001111b            ;al取低四位   
    add ah,30h
    add al,30h                  ;数字转ASCII,相当于将十位与个位数分离,并转换为字符(BCD->两位ASCII)
    mov bp,0b800h               ;定位到显存位置
    mov es,bp
    mov byte ptr es:[1980+di],ah
    mov byte ptr es:[1982+di],al
    mov byte ptr es:[1984+di],dl;送B800显示
    inc bx
    inc si                      ;2个缓冲区指针分别加1,指向下一个入口参数以及分隔符
    add di,6                    ;每次显示3个字符,加偏移为3*2
    pop cx                      ;恢复当前计数
    loop showtime2              ;CX-1!=0,循环到showtime2,继续读取时间参数
    in al,60h                   ;读键盘
    cmp al,06h                  ;5键的扫描码
    je ccolor2                  ;当输入为5时会跳转到修改颜色函数,否则跳过
    jmp notccolor
 ccolor2:
    call changecolor
 notccolor:
    cmp al,10h                  ;Q键的扫描码
    je quit                     ;Q键退出
    jmp showtime1               ;跳转到showtime1
 quit:
    ret                   
showtime endp
  • 修改时间
settime proc
  jmp short begin
  s db 9,8,7,4,2,0             ;CMOS中时间参数的存储位置
  msg0 db 30 DUP('!')          ;用于存储用户输入
  msg1 db 'please input the time format:xx/xx/xx xx:xx:xx',0    ;时间格式字符串
  msg2 db 30 DUP('$')          ;用于存储转换后的BCD码

 begin:
  mov dh,10
  mov dl,15
  mov si,offset msg1
  mov cl,01h
  call show_str                ;显示msg1,提示用户输入
  mov bx, offset msg0
  mov ax,0b800h
  mov es,ax                    ;定位到显存位置,用户显示用户输入
  mov di,0                     ;字符串位置初始化
  mov si,0                     ;显存位置初始化

 int21:
  mov ah,00h                   ;循环调用int 16h中断,获取用户输入
  int 16h
  cmp ah,0eh
  je set_back                  ;若为退格键,进行相应的处理
  cmp ah,1ch
  je set_enter                 ;若为回车键,进行相应的处理
  cmp di,17                    ;若输入已经为17个字符,则不再读取输入
  je int21

  mov cs:[bx+di],al            ;存储数据
  mov es:[si],al               ;显示数据
  inc di                       ;定位到下一个字符存储位置
  add si,2                     ;定位到下一个显存位置
  jmp int21

 set_back:
  cmp di,0                     ;若此时处于起始位置,则不进行删除操作
  je con
  sub si,2                     ;显存位置定位到前一个
  sub di,1                     ;存储位置定位到前一个
  mov al,20h
  mov es:[si],al               ;将前一个显存置为空格
con:
  jmp int21

 set_enter:
  cmp di,17                    ;若输入长度不足17,则返回继续监测输入
  jb int21
  mov bx,offset msg0           ;定位到输入字符串
  mov di,offset msg2           ;定位到数据存储段
  call ASCIItoBCD              ;将用户输入转为BCD码
  mov di,offset s              ;CMOS中时间参数的存储位置
  mov dx,offset msg2           ;存储处理后的用户输入
  call settime_s               ;处理用户收入完毕,调用设置时间函数
  call showtime                ;时间设置完毕,显示当前时间
  ret
settime endp

 ASCIItoBCD proc                ;输入参数为al 输出结果为al
  push cx                      ;保存现场
  push di
  push ax
  push bp
  push bx

  mov cx,18                    ;设置循环处理次数

 A1:                           ;转为BCD码 第一步
  mov al,cs:[bx]
  sub al,30h                   ;ASCII转为数字
  mov cs:[bx],al               ;存入原位置
  inc bx
  loop A1
  pop bx                       ;恢复输入字符串初始位置,准备第二步操作
  push bx                      ;字符串初始位置入栈
  mov cx,6

 A2:                           ;转为BCD码 第二步
  mov ah,cs:[bx]
  mov al,cs:[bx+1]             ;得到前两个输入数据
  push cx
  mov cl,4
  shl ah,cl                    ;左移四位,存入高位
  pop cx
  xor ah,al                    ;ah即为ah与al拼接的BCD码
  mov cs:[di],ah
  inc di
  add bx,3                     ;定位到下一个有效数据
  loop A2

  pop bx                       ;恢复现场
  pop bp
  pop ax
  pop di
  pop cx
  ret
 ASCIItoBCD endp

 settime_s proc  
  push cx                     ;保存现场
  push di
  push ax
  push bp
  push bx   

  mov cx,6                     ;CX=6
  mov bx,di                    ;BX指向s首地址,  s70端口每次所需要的入口参数数组的首地址
  mov di,dx                    ;di指向存储处理后数据

 set:
  mov al,cs:[bx]               ;取当前需要的70端口的入口参数
  out 70h,al                   ;al->PORT 70,指示71端口要取的数据   
  mov al,cs:[di]
  out 71h, al                  ;PORT 71->al,数据输入CMOS
  inc di
  inc bx                       ;自增后,继续设置
  loop set

  pop bx                       ;恢复现场
  pop bp
  pop ax
  pop di
  pop cx
  ret
 settime_s endp
  • 重启系统
restart_sys proc               ;系统重启函数
  mov ax,0ffffh
  push ax
  mov ax,00h
  push ax
  retf                         ;将CS:IP恢复为0ffff:0000,重新启动
restart_sys endp
  • 进入磁盘系统
go_disksys proc                ;进入操作系统函数
  call Clearall

  mov ax,00h
  mov es,ax
  mov bx,7c00h                 ;设置存储数据的内存初始地址为0:7c00h

  ;设置用于启动硬盘系统的参数
  mov al,1                     ;读取的扇区数
  mov ch,0                     ;0磁道
  mov cl,1                     ;1扇区
  mov dl,80h                   ;C盘
  mov dh,0                     ;0面

  mov ah,2 
  int 13h                      ;调用int 13h的2号功能,根据以上参数读取磁盘信息到指定位置

  mov ax,00h
  push ax
  mov ax,7c00h
  push ax
  retf                         ;将CS:IP设置为0000:7c00h,启动磁盘系统
go_disksys endp
  • 修改颜色
changecolor proc                ;用于改变颜色的函数
  push ax                       ;保存现场,函数中要用到的参数
  push es
  push bp
  push bx
  push cx

  mov ax,0b800h
  mov es,ax
  mov bp,1                      ;定位到显存第一个颜色位置

  mov bl,es:[bp]                ;读取此时屏幕的颜色
  inc bl
  and bl,00000111b              ;防止超出7
  cmp bl,0                      ;这里的判断是因为程序启动时,
  jne ccolor1                   ;一部分颜色位可能没有初始化,
  inc bl                        ;会导致显示不稳定,颜色首先为黑色
  and bl,00000111b              ;通过判断,改变这种情况

 ccolor1:
  mov cx,2000

 changecolor_s:
  mov es:[bp],bl                ;循环修改颜色位,作用范围是全屏
  add bp,2
  loop changecolor_s

  pop cx                        ;恢复现场,函数调用前的参数出栈
  pop bx
  pop bp
  pop es
  pop ax
  ret
changecolor endp
  • 其他所需子函数
  • 清屏
Clearall proc                  ;清屏函数
  push ax                      ;保存现场
  push es
  push bp
  push cx

  mov ax,0b800h                ;定位到显存位置
  mov es,ax
  mov bp,00h
  mov cx,2000

 clear_s:
  mov es:[bp],byte ptr ' '     ;全屏写入“ ”
  add bp,2
  loop clear_s

  pop cx                       ;恢复现场
  pop bp
  pop es
  pop ax
  ret
Clearall endp
  • 显示一个以0为结尾标志的字符串
show_str proc                  ;显示一段0标识结束的字符串
  push ax                      ;保存现场
  push cx
  push dx
  push si
  push bp
  push es

  mov ax,0b800h                ;定位到显存位置
  mov es,ax
  mov al,80*2;                 ;处理行号dh*80*2
  mul dh
  mov dh,0                     ;接下来处理列号dl
  add dx,dx                    ;dx*2位一行中的偏移
  add ax,dx
  mov bp,ax                    ;总的偏移

 showoption:
  mov ch,ds:[si]               ;定位到字符串
  cmp ch,0                     ;当数据为0时,显示完毕,退出
  je showstr_back
  mov es:[bp],ch
  inc bp
  mov es:[bp],cl
  inc bp
  inc si                       ;修改显存以及字符指向,准备显示下一字符
  jmp showoption

 showstr_back:
  pop es                       ;恢复现场
  pop bp
  pop si
  pop dx
  pop cx
  pop ax
  ret
show_str endp

其他注意点

  • 在显示菜单时,本来使用了循环结构,但是显示总是乱码,于是采用了六次显示字符串的操作
  • 在修改时间时,本来想调用int21,接收用户的一个字符串的,但是没有个发生中断,因此采用了int16中断,通过循环以及容错控制模仿了近似int21的功能,允许用户输入或删除字符,并在左上角显示,必须输入17个字符后,点击回车才能执行修改时间的操作。
  • 为了显示用户的输入,需要修改显存,编程过程中,因为乱用寄存器,导致一直出错,修改显存时改为采用es段寄存器就不会出问题了

实验过程记录

准备工作

  1. 加载软盘并格式化
    这里写图片描述
    这里写图片描述
  2. 烧录ULoader以及SYSTEM_U
    这里写图片描述
    SYSTEM_U需要两个扇区
    这里写图片描述

系统操作

  1. 启动系统
    这里写图片描述
    按下5键修改颜色,采取的办法是避开黑色以及当前颜色代码加1
    这里写图片描述
  2. 按下1键查看系统时间

    按下5键修改颜色因为扫描用户输入的速度非常快,所以按下时会出现多彩闪烁的效果
    这里写图片描述
    按下Q键退回主菜单
  3. 修改系统时间
    初始界面:
    这里写图片描述
    根据格式输入时间(可以进行退格删除的操作):
    这里写图片描述
    位数正确的情况下,按下回车,时间修改成功并立刻显示
    这里写图片描述
    此时,可以按5键修改颜色,或按Q键退回主菜单
    查看此时的系统时间:
    这里写图片描述
    修改成功
  4. 主菜单按下3键系统重启,依然来到主菜单
  5. 主菜单按下4键启动磁盘系统
    这里写图片描述

实验总结

  1. 对系统是如何启动的有了更多的理解,了解了裸机编程的原理
  2. 学习了查看以及修改时间的方法,对CMOS也更加了解,学习了端口读取数据,中断的使用方法
  3. 对汇编指令进行实践,增加了熟练度
  4. 同时,认识到在汇编编程中,寄存器使用要规范,不可以乱用,各个寄存器有各个寄存器的主要用途
阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页