16位cpu下主引导扇区及用户程序的编写
一些约定
- 主引导扇区代码(0面0道1扇区)加载至0x07c00处
- 用户程序头部代码需包含以下信息:程序总长度、程序入口、重定位表等信息
用户程序
当虚拟机启动时,在屏幕上显示以下两句话: This is user program,it just to display basic information.This contents is written in 2014-06-01.
定义各程序段
;用户程序头部信息
SECTION header align= vstart= ;代码段1
SECTION code_1 align= vstart=
;代码段2
SECTION code_2 align= vstart= ;数据段1
SECTION data_1 align= vstart= msg0 db ' This is user program,it just to display basic information',0x0d,0x0a
db ;数据段2
SECTION data_2 align= vstart= msg1 db ' This contents is written in 2014-06-01'
db ;256字节栈段
SECTION stack align= vstart=
resb
stack_end: ;用于统计程序长度
SECTION trail align=
program_end:
编写用户程序头部信息
;用户程序头部信息 SECTION header align= vstart=
;程序长度
program_length dw program_end ;用户程序入口
code_entry dw start
dd section.code_1.start ;重定位表项数
realloc_tbl_len dw (header_end-code_1_segment)/ ;段重定位表
code_1_segment dd section.code_1.start
code_2_segment dd section.code_2.start
data_1_segment dd section.data_1.start
data_2_segment dd section.data_2.start
stack_segment dd section.stack.start header_end:
代码段1及代码段2需要实现显示字符功能,下面分解开了一点点实现。当用户程序获得cpu使用权后,第一步要做的是初始化各寄存器的指向,此时,ds和es都是指向用户程序头部,即程序第一个字节处。
;代码段1
SECTION code_1 align= vstart=
start:
;设置栈段
mov ax,[stack_segment]
mov ss,ax
mov sp,stack_end ;设置ds指向数据段1
mov ax,[data_1_segment]
mov ds,ax
初始化寄存器后,就需要调用显示字符例程以在屏幕上打印字符
;ds:bx指向数据段开始的第一个字符
mov bx,msg0
call put_string
下面编写put_string例程,put_string首先需要判断是否是字符串结尾,若到达结尾则返回主程序,否则调用put_char例程打印字符。jz的意思是说zf表示为等于1则转移,zf标志位的结果受上一条代码影响,若or cl,cl执行后,cl=0则zf=1
put_string: mov cl,[bx]
or cl,cl
jz .exit
call put_char
inc bx
jmp put_string .exit
ret
接下来编写put_char例程,他的功能是显示ds:bx处的一个字符,在编写之前需先了解VGA标准下光标的获取与回车换行的处理。
光标在屏幕上的位置是存储在两个8为寄存器中的,这两个寄存器位于显卡中,为了提高I/O效率,一般通过索引寄存器方位显卡中的寄存器,索引寄存器的端口号是0x3d4,两个8为寄存器的索引值分别为0x0e和0x0f,读写操作需要通过数据端口0x3d5来进行。
put_char:
push ax
push bx
push cx
push dx
push ds
push es ;获取光标位置的高8位,存储在ah中
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
in al,dx
mov ah,al ;获取光标位置的低8位,存储在al中
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
in al,dx ;bx中存储光标位置
mov bx,ax
光标位置获取以后,需要进行下一步判断即想要显示的字符是否是回车或换行符这样的控制字符,回车符(0x0d)、换行符(0x0a)。
put_char:
push ax
push bx
push cx
push dx
push ds
push es ;获取光标位置的高8位,存储在ah中
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
in al,dx
mov ah,al ;获取光标位置的低8位,存储在al中
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
in al,dx ;bx中存储光标位置
mov bx,ax cmp cl,0x0d
;不是回车,跳转到判断是不是换行处
jnz .put_0a
mov bl,
div bl
;此时al中是光标所在行数,再乘以80即得到
;回车后光标在屏幕上的位置
mul bl
mov bx,ax
;重新设置光标位置
jmp .set_cursor .put_0a:
cmp cl,0x0a
jnz .put_other
add bx,
;判断是否滚动屏幕
jmp .roll_screen
下面是重新设置光标的例程.set_cursor
.set_cursor:
;高8位对应bh
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
mov al,bh
out dx,al
;低8位对应bl
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
mov al,bl
out dx,al
.put_others的工作是显示字符,就不细说了
.put_other:
mov ax,0xb800
mov es,ax
;bx是光标的位置,一个字符在显存中是2字节显示
;所以光标位置*2是字符的显示位置
shl bx,
mov [es:bx],cl ;将光标位置推进一个字符
shr bx,
add bx,
接下来就是处理滚屏时的操作,滚屏可以理解为屏幕整体向上一行且最后一行清空
.roll_screen:
cmp bx,
jl .set_cursor mov ax,0xb800
mov ds,ax
mov es,ax
cld
mov si,0xa0
mov di,0x00
mov cx,
rep movsw
mov bx,
mov cx,
.cls:
mov word[es:bx],0x0720
add bx,
loop .cls mov bx,
代码段1执行完毕后需要转到代码段2继续执行
push word [es:code_2_segment]
mov ax,begin
push ax retf
代码段2
SECTION code_2 align= vstart= ;定义代码段2(16字节对齐) begin:
push word [es:code_1_segment]
mov ax,continue
push ax retf
continue例程实现显示第二段信息的功能
continue:
mov ax,[es:data_2_segment] ;段寄存器DS切换到数据段2
mov ds,ax mov bx,msg1
call put_string ;显示第二段信息 jmp $
至此,用户程序编写完毕
主引导扇区代码
首先要做的是定义读取用户程序的逻辑扇区编号、加载到的内存地址以及主引导扇区代码段
SECTION mbr align= vstart=0x7c00 ;用户程序所在逻辑扇区编号
app_lba_start equ
;用户程序将要加载的内存地址
phy_base dd 0x10000
下一步编写引导代码,我们电脑加点启动后主引导扇区代码会被加载到内存地址0x07c00处,所以上面的代码中有vstart=0x7c00语句方便下面的操作。引导扇区代码第一步要做是获取用户程序头部信息,根据程序长度从逻辑扇区把用户程序字节码加载到指定的内存地址处
;主引导扇区代码
SECTION mbr align= vstart=0x7c00
mov ax,
mov ss,ax
mov sp,ax ;20位内存地址高16位存储在dx中
mov ax,[cs:phy_base]
mov dx,[cs:phy_base+]
;除以16得到逻辑段地址
mov bx,
div bx
;ds,es指向16位用户程序逻辑段地址
mov ds,ax
mov es,ax
下一步,从硬盘中读取用户程序字节码至指定的内存地址处
;清空di,ds:si代表逻辑扇区编号
xor di,di
mov si,app_lba_start
;清空bx,ds:bx指向加载内存地址
xor bx,bx
call read_hard_disk_0
read_hard_disk_0例程用于读取硬盘上的内容,硬盘内容的读写也是通过端口进行的,具体见下面的代码
read_hard_disk_0: ;从硬盘读取一个逻辑扇区
;输入:DI:SI=起始逻辑扇区号
; DS:BX=目标缓冲区地址
push ax
push bx
push cx
push dx mov dx,0x1f2
mov al,
out dx,al ;读取的扇区数 inc dx ;0x1f3
mov ax,si
out dx,al ;LBA地址7~0 inc dx ;0x1f4
mov al,ah
out dx,al ;LBA地址15~8 inc dx ;0x1f5
mov ax,di
out dx,al ;LBA地址23~16 inc dx ;0x1f6
mov al,0xe0 ;LBA28模式,主盘
or al,ah ;LBA地址27~24
out dx,al inc dx ;0x1f7
mov al,0x20 ;读命令
out dx,al .waits:
in al,dx
and al,0x88
cmp al,0x08
jnz .waits ;不忙,且硬盘已准备好数据传输 mov cx, ;总共要读取的字数
mov dx,0x1f0
.readw:
in ax,dx
mov [bx],ax
add bx,
loop .readw pop dx
pop cx
pop bx
pop ax ret
用户程序头部信息读取后,就可以根据头部信息判断程序大小然后读取剩余的字节码
mov dx,[]
mov ax,[]
mov bx, ;512字节每扇区
div bx
cmp dx,
jnz @1 ;未除尽,因此结果比实际扇区数少1
dec ax ;已经读了一个扇区,扇区总数减1
@1:
:实际长度小于512字节,直接计算入口程序入口段地址
cmp ax,
jz direct ;读取剩余的扇区
push ds ;以下要用到并改变DS寄存器 mov cx,ax ;循环次数(剩余扇区数)
@2:
mov ax,ds
add ax,0x20 ;得到下一个以512字节为边界的段地址
mov ds,ax xor bx,bx ;每次读时,偏移地址始终为0x0000
inc si ;下一个逻辑扇区
call read_hard_disk_0
loop @2 ;循环读,直到读完整个功能程序 pop ds ;恢复数据段基址到用户程序头部段
direct例程实现入口代码段地址的计算
mov dx,[0x08]
mov ax,[0x06] push dx
add ax,[cs:phy_base]
adc dx,[cs:phy_base+0x02]
shr ax,
ror dx,
and dx,0xf000
or ax,dx
pop dx mov [0x06],ax ;回填修正后的入口点代码段基址
下面处理段重定位表,原理和处理入口地址一样
;开始处理段重定位表
mov cx,[0x0a] ;需要重定位的项目数量
mov bx,0x0c ;重定位表首地址 realloc:
mov dx,[bx+0x02] ;32位地址的高16位
mov ax,[bx] push dx
add ax,[cs:phy_base]
adc dx,[cs:phy_base+0x02]
shr ax,
ror dx,
and dx,0xf000
or ax,dx
pop dx mov [bx],ax ;回填段的基址
add bx, ;下一个重定位项(每项占4个字节)
loop realloc jmp far [0x04] ;转移到用户程序
注意最有一行代码jmp far [0x04],此时ds是指向用户程序首地址的,取出[ds:0x04]处的2个字数据,分别赋予cs和ip.[0x04]处是一个字数据即用户程序开始的标号的偏移地址,下一个数据是回填以后的16位入口程序逻辑段地址。
16位cpu下主引导扇区及用户程序的编写的更多相关文章
- Linux磁盘管理之逻辑结构主引导扇区02
一.主引导扇区 主引导扇区位于硬盘的0磁道0柱面1扇区,共占用了63个扇区,但实际上只使用了512字节,由三大部分组成: 1.主引导记录MBR(Master Boot Record):占446字节. ...
- MBR主引导扇区解析
最近在制作镜像的时候由于需要简单研究了下MBR主引导扇区的结构,这里记录下便于后期温习,下面就直接进入正题: MBR主引导扇区位于磁盘的第一个扇区,即0号扇区,主要由引导代码.分区表.结束标志三部分构 ...
- 主引导扇区MBR的解析
http://blog.chinaunix.net/uid-24774106-id-3340397.html 最近排查一个USB相关的故障,由于信息安全就不多说工作上的事情了,顺路学习了MBR的相关知 ...
- verilog实现的16位CPU单周期设计
verilog实现的16位CPU单周期设计 这个工程完成了16位CPU的单周期设计,模块化设计,包含对于关键指令的仿真与设计,有包含必要的分析说明. 单周期CPU结构图 单周期CPU设计真值表与结构图 ...
- 用纯c语言完成16位模式下的引导程序
gcc在16位模式下做引导程序遇到的问题太多了,不过费了很大劲逐一解决了. 自己的小demo也从一开始的1个扇区增加到了20几个扇区. 先上图做个标记,后面有时间再上代码.
- verilog实现的16位CPU设计
verilog实现的16位CPU设计 整体电路图 CPU状态图 idle代表没有工作,exec代表在工作 实验设计思路 五级流水线,增加硬件消耗换取时间的做法. 具体每一部分写什么将由代码部分指明. ...
- 16位CPU多周期设计
16位CPU多周期设计 这个工程完成了16位CPU的多周期设计,模块化设计,有包含必要的分析说明. 多周期CPU结构图 多周期CPU设计真值表 对应某一指令的情况,但仅当对应周期时才为对应的输出,不是 ...
- 对10进制16位长的主键的缩短处理 NULL
# 对问题表去除旧有主键,新建自增主键:ALTER TABLE `question`CHANGE COLUMN `id` `id16` bigint(20) NULL COMMENT 'id_to_d ...
- MBR主引导扇区解析2
unsigned AnsiChar data[] = { 0x33, 0xC0, 0x8E, 0xD0, 0xBC, 0x00, 0x7C, 0x8E, 0xC0, 0x8E, 0xD8, 0xBE, ...
随机推荐
- Library string type(2)——关于String的操作
关于string的定义,请参阅博文http://blog.csdn.net/larry233/article/details/51483827 string的操作 s.empty() //Return ...
- 九度OJ 1433 FatMouse -- 贪心算法
题目地址:http://ac.jobdu.com/problem.php?pid=1433 题目描述: FatMouse prepared M pounds of cat food, ready to ...
- 实体框架(Entity Framework)简介
实体框架(Entity Framework)简介 简称EF,与ADO.NET关系 ADO.NET Entity Framework 是微软以 ADO.NET 为基础所发展出来的对象关系对应 (O/R ...
- 菜鸟的MySQL学习笔记(四)
MySQL中的运算符和函数: 1.字符函数: 2.数值运算符与函数: 3.比较运算符与函数: 4.日期时间函数: 5.信息函数: 6.聚合函数: 7.加密函数等: 6-1.字符函数: CONCAT ...
- 移动端reset.css
* { margin:; padding:; } article, aside, details, figcaption, figure, footer, header, hgroup, main, ...
- CentOS使用sudo提示用户不在sudoers文件中的解决方法
1切换到root用户[linux@localhost ~]$ su root密码:[root@localhost ~]# 2查看/etc/sudoers文件权限,如果只读权限,修改为可写权限 [roo ...
- SDWebImage 详解
一.SDWebImage介绍 1.在项目的开发过程中,我们经常会用到异步加载图片的功能,先从网络上异步下载图片,然后通过UIImageView显示在屏幕上.这是一个经常使用的功能,基本上所有的联网应用 ...
- Android开发者指南-方位传感器-Position Sensor
Android开发者指南-方位传感器-Position Sensor 转载自:http://blog.sina.com.cn/s/blog_48d4913001010zsu.html Position ...
- docker安装caffe
[最近一直想要学习caffe,但是苦苦纠结于环境安装不上,真的是第一步都迈不出去,还好有docker的存在!下面,对本人如何利用docker安装caffe做以简单叙述,不属于教程,只是记录自己都做了什 ...
- vs2010 使用SignalR 提高B2C商城用户体验(一)
vs2010 使用SignalR 提高B2C商城用户体验(一) 1.需求简介,做为新时代的b2c商城,没有即时通讯,怎么提供用户粘稠度,怎么增加销量,用户购物的第一习惯就是咨询,即时通讯,应运而生.这 ...