一些约定
  • 主引导扇区代码(0面0道1扇区)加载至0x07c00处
  • 用户程序头部代码需包含以下信息:程序总长度、程序入口、重定位表等信息
用户程序

当虚拟机启动时,在屏幕上显示以下两句话: This is user program,it just to display basic information.This contents is written in 2014-06-01.

定义各程序段

  1. ;用户程序头部信息
  2. SECTION header align= vstart=
  3.  
  4. ;代码段1
  5. SECTION code_1 align= vstart=
  6. ;代码段2
  7. SECTION code_2 align= vstart=
  8.  
  9. ;数据段1
  10. SECTION data_1 align= vstart=
  11.  
  12. msg0 db ' This is user program,it just to display basic information',0x0d,0x0a
  13. db
  14.  
  15. ;数据段2
  16. SECTION data_2 align= vstart=
  17.  
  18. msg1 db ' This contents is written in 2014-06-01'
  19. db
  20.  
  21. ;256字节栈段
  22. SECTION stack align= vstart=
  23. resb
  24. stack_end:
  25.  
  26. ;用于统计程序长度
  27. SECTION trail align=
  28. program_end:

编写用户程序头部信息

  1. ;用户程序头部信息
  2.  
  3. SECTION header align= vstart=
  4. ;程序长度
  5. program_length dw program_end
  6.  
  7. ;用户程序入口
  8. code_entry dw start
  9. dd section.code_1.start
  10.  
  11. ;重定位表项数
  12. realloc_tbl_len dw (header_end-code_1_segment)/
  13.  
  14. ;段重定位表
  15. code_1_segment dd section.code_1.start
  16. code_2_segment dd section.code_2.start
  17. data_1_segment dd section.data_1.start
  18. data_2_segment dd section.data_2.start
  19. stack_segment dd section.stack.start
  20.  
  21. header_end:

代码段1及代码段2需要实现显示字符功能,下面分解开了一点点实现。当用户程序获得cpu使用权后,第一步要做的是初始化各寄存器的指向,此时,ds和es都是指向用户程序头部,即程序第一个字节处。

  1. ;代码段1
  2. SECTION code_1 align= vstart=
  3. start:
  4. ;设置栈段
  5. mov ax,[stack_segment]
  6. mov ss,ax
  7. mov sp,stack_end
  8.  
  9. ;设置ds指向数据段1
  10. mov ax,[data_1_segment]
  11. mov ds,ax

初始化寄存器后,就需要调用显示字符例程以在屏幕上打印字符

  1. ;ds:bx指向数据段开始的第一个字符
  2. mov bx,msg0
  3. call put_string

下面编写put_string例程,put_string首先需要判断是否是字符串结尾,若到达结尾则返回主程序,否则调用put_char例程打印字符。jz的意思是说zf表示为等于1则转移,zf标志位的结果受上一条代码影响,若or cl,cl执行后,cl=0则zf=1

  1. put_string:
  2.  
  3. mov cl,[bx]
  4. or cl,cl
  5. jz .exit
  6. call put_char
  7. inc bx
  8. jmp put_string
  9.  
  10. .exit
  11. ret

接下来编写put_char例程,他的功能是显示ds:bx处的一个字符,在编写之前需先了解VGA标准下光标的获取与回车换行的处理。

光标在屏幕上的位置是存储在两个8为寄存器中的,这两个寄存器位于显卡中,为了提高I/O效率,一般通过索引寄存器方位显卡中的寄存器,索引寄存器的端口号是0x3d4,两个8为寄存器的索引值分别为0x0e和0x0f,读写操作需要通过数据端口0x3d5来进行。

  1. put_char:
  2. push ax
  3. push bx
  4. push cx
  5. push dx
  6. push ds
  7. push es
  8.  
  9. ;获取光标位置的高8位,存储在ah
  10. mov dx,0x3d4
  11. mov al,0x0e
  12. out dx,al
  13. mov dx,0x3d5
  14. in al,dx
  15. mov ah,al
  16.  
  17. ;获取光标位置的低8位,存储在al
  18. mov dx,0x3d4
  19. mov al,0x0f
  20. out dx,al
  21. mov dx,0x3d5
  22. in al,dx
  23.  
  24. ;bx中存储光标位置
  25. mov bx,ax

光标位置获取以后,需要进行下一步判断即想要显示的字符是否是回车或换行符这样的控制字符,回车符(0x0d)、换行符(0x0a)。

  1. put_char:
  2. push ax
  3. push bx
  4. push cx
  5. push dx
  6. push ds
  7. push es
  8.  
  9. ;获取光标位置的高8位,存储在ah
  10. mov dx,0x3d4
  11. mov al,0x0e
  12. out dx,al
  13. mov dx,0x3d5
  14. in al,dx
  15. mov ah,al
  16.  
  17. ;获取光标位置的低8位,存储在al
  18. mov dx,0x3d4
  19. mov al,0x0f
  20. out dx,al
  21. mov dx,0x3d5
  22. in al,dx
  23.  
  24. ;bx中存储光标位置
  25. mov bx,ax
  26.  
  27. cmp cl,0x0d
  28. ;不是回车,跳转到判断是不是换行处
  29. jnz .put_0a
  30. mov bl,
  31. div bl
  32. ;此时al中是光标所在行数,再乘以80即得到
  33. ;回车后光标在屏幕上的位置
  34. mul bl
  35. mov bx,ax
  36. ;重新设置光标位置
  37. jmp .set_cursor
  38.  
  39. .put_0a:
  40. cmp cl,0x0a
  41. jnz .put_other
  42. add bx,
  43. ;判断是否滚动屏幕
  44. jmp .roll_screen

下面是重新设置光标的例程.set_cursor

  1. .set_cursor:
  2. ;高8位对应bh
  3. mov dx,0x3d4
  4. mov al,0x0e
  5. out dx,al
  6. mov dx,0x3d5
  7. mov al,bh
  8. out dx,al
  9. ;低8位对应bl
  10. mov dx,0x3d4
  11. mov al,0x0f
  12. out dx,al
  13. mov dx,0x3d5
  14. mov al,bl
  15. out dx,al

.put_others的工作是显示字符,就不细说了

  1. .put_other:
  2. mov ax,0xb800
  3. mov es,ax
  4. ;bx是光标的位置,一个字符在显存中是2字节显示
  5. ;所以光标位置*2是字符的显示位置
  6. shl bx,
  7. mov [es:bx],cl
  8.  
  9. ;将光标位置推进一个字符
  10. shr bx,
  11. add bx,

接下来就是处理滚屏时的操作,滚屏可以理解为屏幕整体向上一行且最后一行清空

  1. .roll_screen:
  2. cmp bx,
  3. jl .set_cursor
  4.  
  5. mov ax,0xb800
  6. mov ds,ax
  7. mov es,ax
  8. cld
  9. mov si,0xa0
  10. mov di,0x00
  11. mov cx,
  12. rep movsw
  13. mov bx,
  14. mov cx,
  15. .cls:
  16. mov word[es:bx],0x0720
  17. add bx,
  18. loop .cls
  19.  
  20. mov bx,

代码段1执行完毕后需要转到代码段2继续执行

  1. push word [es:code_2_segment]
  2. mov ax,begin
  3. push ax
  4.  
  5. retf

代码段2

  1. SECTION code_2 align= vstart= ;定义代码段216字节对齐)
  2.  
  3. begin:
  4. push word [es:code_1_segment]
  5. mov ax,continue
  6. push ax
  7.  
  8. retf

continue例程实现显示第二段信息的功能

  1. continue:
  2. mov ax,[es:data_2_segment] ;段寄存器DS切换到数据段2
  3. mov ds,ax
  4.  
  5. mov bx,msg1
  6. call put_string ;显示第二段信息
  7.  
  8. jmp $

至此,用户程序编写完毕

主引导扇区代码

首先要做的是定义读取用户程序的逻辑扇区编号、加载到的内存地址以及主引导扇区代码段

  1. SECTION mbr align= vstart=0x7c00
  2.  
  3. ;用户程序所在逻辑扇区编号
  4. app_lba_start equ
  5. ;用户程序将要加载的内存地址
  6. phy_base dd 0x10000

下一步编写引导代码,我们电脑加点启动后主引导扇区代码会被加载到内存地址0x07c00处,所以上面的代码中有vstart=0x7c00语句方便下面的操作。引导扇区代码第一步要做是获取用户程序头部信息,根据程序长度从逻辑扇区把用户程序字节码加载到指定的内存地址处

  1. ;主引导扇区代码
  2. SECTION mbr align= vstart=0x7c00
  3. mov ax,
  4. mov ss,ax
  5. mov sp,ax
  6.  
  7. ;20位内存地址高16位存储在dx
  8. mov ax,[cs:phy_base]
  9. mov dx,[cs:phy_base+]
  10. ;除以16得到逻辑段地址
  11. mov bx,
  12. div bx
  13. ;ds,es指向16位用户程序逻辑段地址
  14. mov ds,ax
  15. mov es,ax

下一步,从硬盘中读取用户程序字节码至指定的内存地址处

  1. ;清空di,ds:si代表逻辑扇区编号
  2. xor di,di
  3. mov si,app_lba_start
  4. ;清空bx,ds:bx指向加载内存地址
  5. xor bx,bx
  6. call read_hard_disk_0

read_hard_disk_0例程用于读取硬盘上的内容,硬盘内容的读写也是通过端口进行的,具体见下面的代码

  1. read_hard_disk_0: ;从硬盘读取一个逻辑扇区
  2. ;输入:DI:SI=起始逻辑扇区号
  3. ; DS:BX=目标缓冲区地址
  4. push ax
  5. push bx
  6. push cx
  7. push dx
  8.  
  9. mov dx,0x1f2
  10. mov al,
  11. out dx,al ;读取的扇区数
  12.  
  13. inc dx ;0x1f3
  14. mov ax,si
  15. out dx,al ;LBA地址7~0
  16.  
  17. inc dx ;0x1f4
  18. mov al,ah
  19. out dx,al ;LBA地址15~8
  20.  
  21. inc dx ;0x1f5
  22. mov ax,di
  23. out dx,al ;LBA地址23~16
  24.  
  25. inc dx ;0x1f6
  26. mov al,0xe0 ;LBA28模式,主盘
  27. or al,ah ;LBA地址27~24
  28. out dx,al
  29.  
  30. inc dx ;0x1f7
  31. mov al,0x20 ;读命令
  32. out dx,al
  33.  
  34. .waits:
  35. in al,dx
  36. and al,0x88
  37. cmp al,0x08
  38. jnz .waits ;不忙,且硬盘已准备好数据传输
  39.  
  40. mov cx, ;总共要读取的字数
  41. mov dx,0x1f0
  42. .readw:
  43. in ax,dx
  44. mov [bx],ax
  45. add bx,
  46. loop .readw
  47.  
  48. pop dx
  49. pop cx
  50. pop bx
  51. pop ax
  52.  
  53. ret

用户程序头部信息读取后,就可以根据头部信息判断程序大小然后读取剩余的字节码

  1. mov dx,[]
  2. mov ax,[]
  3. mov bx, ;512字节每扇区
  4. div bx
  5. cmp dx,
  6. jnz @1 ;未除尽,因此结果比实际扇区数少1
  7. dec ax ;已经读了一个扇区,扇区总数减1
  8. @1:
  9. :实际长度小于512字节,直接计算入口程序入口段地址
  10. cmp ax,
  11. jz direct
  12.  
  13. ;读取剩余的扇区
  14. push ds ;以下要用到并改变DS寄存器
  15.  
  16. mov cx,ax ;循环次数(剩余扇区数)
  17. @2:
  18. mov ax,ds
  19. add ax,0x20 ;得到下一个以512字节为边界的段地址
  20. mov ds,ax
  21.  
  22. xor bx,bx ;每次读时,偏移地址始终为0x0000
  23. inc si ;下一个逻辑扇区
  24. call read_hard_disk_0
  25. loop @2 ;循环读,直到读完整个功能程序
  26.  
  27. pop ds ;恢复数据段基址到用户程序头部段
  1. direct例程实现入口代码段地址的计算
  1. mov dx,[0x08]
  2. mov ax,[0x06]
  3.  
  4. push dx
  5. add ax,[cs:phy_base]
  6. adc dx,[cs:phy_base+0x02]
  7. shr ax,
  8. ror dx,
  9. and dx,0xf000
  10. or ax,dx
  11. pop dx
  12.  
  13. mov [0x06],ax ;回填修正后的入口点代码段基址

下面处理段重定位表,原理和处理入口地址一样

  1. ;开始处理段重定位表
  2. mov cx,[0x0a] ;需要重定位的项目数量
  3. mov bx,0x0c ;重定位表首地址
  4.  
  5. realloc:
  6. mov dx,[bx+0x02] ;32位地址的高16
  7. mov ax,[bx]
  8.  
  9. push dx
  10. add ax,[cs:phy_base]
  11. adc dx,[cs:phy_base+0x02]
  12. shr ax,
  13. ror dx,
  14. and dx,0xf000
  15. or ax,dx
  16. pop dx
  17.  
  18. mov [bx],ax ;回填段的基址
  19. add bx, ;下一个重定位项(每项占4个字节)
  20. loop realloc
  21.  
  22. jmp far [0x04] ;转移到用户程序

注意最有一行代码jmp far [0x04],此时ds是指向用户程序首地址的,取出[ds:0x04]处的2个字数据,分别赋予cs和ip.[0x04]处是一个字数据即用户程序开始的标号的偏移地址,下一个数据是回填以后的16位入口程序逻辑段地址。

16位cpu下主引导扇区及用户程序的编写的更多相关文章

  1. Linux磁盘管理之逻辑结构主引导扇区02

    一.主引导扇区 主引导扇区位于硬盘的0磁道0柱面1扇区,共占用了63个扇区,但实际上只使用了512字节,由三大部分组成: 1.主引导记录MBR(Master Boot Record):占446字节. ...

  2. MBR主引导扇区解析

    最近在制作镜像的时候由于需要简单研究了下MBR主引导扇区的结构,这里记录下便于后期温习,下面就直接进入正题: MBR主引导扇区位于磁盘的第一个扇区,即0号扇区,主要由引导代码.分区表.结束标志三部分构 ...

  3. 主引导扇区MBR的解析

    http://blog.chinaunix.net/uid-24774106-id-3340397.html 最近排查一个USB相关的故障,由于信息安全就不多说工作上的事情了,顺路学习了MBR的相关知 ...

  4. verilog实现的16位CPU单周期设计

    verilog实现的16位CPU单周期设计 这个工程完成了16位CPU的单周期设计,模块化设计,包含对于关键指令的仿真与设计,有包含必要的分析说明. 单周期CPU结构图 单周期CPU设计真值表与结构图 ...

  5. 用纯c语言完成16位模式下的引导程序

    gcc在16位模式下做引导程序遇到的问题太多了,不过费了很大劲逐一解决了. 自己的小demo也从一开始的1个扇区增加到了20几个扇区. 先上图做个标记,后面有时间再上代码.

  6. verilog实现的16位CPU设计

    verilog实现的16位CPU设计 整体电路图 CPU状态图 idle代表没有工作,exec代表在工作 实验设计思路 五级流水线,增加硬件消耗换取时间的做法. 具体每一部分写什么将由代码部分指明. ...

  7. 16位CPU多周期设计

    16位CPU多周期设计 这个工程完成了16位CPU的多周期设计,模块化设计,有包含必要的分析说明. 多周期CPU结构图 多周期CPU设计真值表 对应某一指令的情况,但仅当对应周期时才为对应的输出,不是 ...

  8. 对10进制16位长的主键的缩短处理 NULL

    # 对问题表去除旧有主键,新建自增主键:ALTER TABLE `question`CHANGE COLUMN `id` `id16` bigint(20) NULL COMMENT 'id_to_d ...

  9. MBR主引导扇区解析2

    unsigned AnsiChar data[] = { 0x33, 0xC0, 0x8E, 0xD0, 0xBC, 0x00, 0x7C, 0x8E, 0xC0, 0x8E, 0xD8, 0xBE, ...

随机推荐

  1. can't find -lsocket的解决办法

    在UNIX/LINUX当中是不存在libsocket的.传说中,socket的功能库是放在libc当中的,所以如果需要连接的话修改成-lc就可以了.

  2. 解决Mysql的主从数据库没有同步的两种方法

    今天发现Mysql的主从数据库没有同步 先上Master库: mysql>show processlist;   查看下进程是否Sleep太多.发现很正常.show master status; ...

  3. 2015-01-27-从实验出发理解buffer与cache区别-吴伟顺

        通过du(find) 与 cat 体现buffer与cache差异实验: 实验表明: 1 通常 buffer << cache 2 "文件系统"相关内容(ino ...

  4. 构建 struts2 spring3 mybatis 的maven项目 构建 pom.xml

    学习maven项目时 搭建个ssm项目 算是给自己留个备份吧 环境说明: MyEclipse10 Maven   3.2.3 框架: struts2    2.3.24.1 spring3    3. ...

  5. 基于jQuery的图片左右轮播,基本原理通用

    毕竟新人,写点基础的小东西,希望能和大家沟通交流,提高自己的水平. 这个是应用较多的轮播部分,希望能和大家分享一下思路,拓宽视野. 话不多说,上内容. 我的思路很简单就是通过判断index值的大小变化 ...

  6. javascript tab onclick

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org ...

  7. GDI画验证码

    Random r = new Random(); string str = ""; for (int i = 0; i < 5; i++) { int a= r.Next(0 ...

  8. java String字符串——进度1

    String字符串    在JAVA中提供了多种创建字符串对象的方法,这里介绍最简单的两种,    第一种是直接赋值,    第二种是使用String类的构造方法:    如下所示:    Strin ...

  9. #Leet Code# Sqrt

    描述:log(n) 代码: class Solution: # @param x, an integer # @return an integer def getVal(self, begin, en ...

  10. CGDataCmd

    1,"Get Inf Joint from file" 选择文件中储存的骨骼信息; 2,"Export skinWeight"   导出权重;  3," ...