19、硬盘和显卡的访问与控制


上一节:18、INTEL8086处理器的寻址方式
下一节:20、中断和动态时钟显示

01、离开主引导分区

下一步计划:
在这里插入图片描述

02、给汇编程序分段

在这里插入图片描述
NASM编译器使用SECTION或着SEGMENT来定义段,使用ALIGN规定段的对齐方式。

SECTION data1 ALIGN=16
	mydata dw 0xFACE

SECTION data2 ALIGN=16
	string db 'hello'

section code ALIGN=16
	mov bx, mydata
	mov si, string

03、控制段内元素的汇编地址

使用VSTART指定段的起始汇编地址。

SECTION data1 ALIGN=16 VSTART=0
	mydata dw 0xFACE

SECTION data2 ALIGN=16 VSTART=0x100
	string db 'hello'

section code ALIGN=16 VSTART=0
	mov bx, mydata
	mov si, string

在这里插入图片描述

04、加载器和用户程序头部段

计算段的汇编地址
在这里插入图片描述
加载器和用户程序的关系:加载器需要用户程序头部段提供的信息决定如何加载并执行用户程序。
在这里插入图片描述
其中用户程序头部段的内容如下所示:
在这里插入图片描述
用户程序头部段的内容如下:
在这里插入图片描述

05、加载器的工作流程和常数声明

加载器的工作流程:
在这里插入图片描述
代码可参考配套程序中的c08_mbr.asm
其中常数使用equ定义,而且常数是不占用内存空间的。

...
app_lba_start equ 100	;声明常数(用户程序起始逻辑扇区号)
                        ;常数的声明不会占用汇编地址
...

06、确定用户程序的加载位置

此小节程序见c08_mbr.asm

本程序中内存分布:
在这里插入图片描述
可用空间为0x10000~~0x0FFFF中。

将用户程序的起始物理地址定义在phy_base标号处的4字节处:
在这里插入图片描述
在这里插入图片描述
将用户程序地址从内存中取出,传入DXAX寄存器中,用以计算16位的段地址:
在这里插入图片描述

07、外围设备及其接口

此小节从硬盘上将用户程序读入并加载到用户程序地址0x100处。

处理器使用总线和外围设备进行数据交换:
在这里插入图片描述
在这里插入图片描述

08、输入输出端口的访问

此小节程序见c08_mbr.asm

Intel的系统中,最多只有65536个端口,端口号位0~~65535

使用in指令从端口读取数据:使用AX(端口数据为16位)、AL(端口数据为8位)做为目的操作数,DX做为源操作数。
在这里插入图片描述
其中源操作数中的端口号也可不用DX存储,可使用8位的立即数直接给出:
在这里插入图片描述
使用out指令向端口发送数据:使用DX(端口数据为16位)、imm8(端口数据为8位)做为目的操作数,AXAL做为源操作数。
在这里插入图片描述
inout指令都不影响任何标志位。

09、通过硬盘控制器端口读取扇区数据

硬盘读写的基本单位是扇区
CHS模式:向硬盘发送磁头号、柱面号、扇区号;
LBA模式:硬盘所有扇区统一编址。最早使用28个比特表示逻辑扇区号,即LBA28
在这里插入图片描述
发送0表示读取256个扇区、1~255表示读取1~255个扇区。
主硬盘控制器被分配了8个端口,从0x1F00x1F7,以下是从硬盘读数据的具体过程:

  • 第1步:设置要读取的扇区数量
    在这里插入图片描述
  • 第2步:设位置起始的LBA扇区号
    在这里插入图片描述
  • 第3步:向端口0x1f7写入读命令0x20
    在这里插入图片描述
  • 第4步:等待读写操作完成
    0x1f7即是命令端口也是状态端口。
    在这里插入图片描述
  • 第5步:连续的取出数据
    在这里插入图片描述

10、过程和过程调用

在这里插入图片描述具体过程见程序:c08_mbr.asm

11、过程调用和返回的原理

具体过程见程序:c08_mbr.asm

调用过程:
在这里插入图片描述
在这里插入图片描述
16位的相对近调用,调用当前代码段内的程序;
其中相对偏移量为:标号处的汇编地址 减去 call指令下一条指令的汇编地址。
call指令执行时:处理器用IP内容压栈保存之后 加上 call指令中的16位偏移量得到子程序的偏移地址,之后转到此偏移地址处执行。

过程调用之后栈的变化:
在这里插入图片描述
过程返回之后栈的变化:
在这里插入图片描述

...
	;以下读取程序的起始部分 
	xor di,di
	mov si,app_lba_start		;程序在硬盘上的起始逻辑扇区号 
	xor bx,bx					;加载到DS:0x0000处 
	call read_hard_disk_0		;执行call指令时处理器会将IP寄存器指向下一条指令的位置
								;并将IP压栈,由于这条call指令为十六位的近调用
								;只能调用当前代码段内的过程,不能调用其他段内过程
								;也就是代码调用前后代码段CS内的值不改变,就不去要压栈
								;
								;入栈之后就会将IP当前内容加上指令中的相对偏移量得到子程序的
								;偏移地址,同时处理器将此地址取代IP的原有内容
...   

12、加载整个用户程序

具体过程见程序:c08_mbr.asm
用户程序头部段的组成:
在这里插入图片描述
在程序中就是使用DX:AX读取用户程序总长度。

其中一个段的大小最大位64KB,若使用以下方法加载用户程序,则当程序大于64KB时,会从头在加载一边用户程序。
在这里插入图片描述
每读完一个扇区,就将DS的内容加1,构造一个新的段开始加载剩余的用户程序,这样就可以解决当程序大于64KB时从头开始的情况:
在这里插入图片描述
使用以下程序读取用户程序剩余扇区:

...
     ;以下判断整个程序有多大
     mov dx,[2]                      ;曾经把dx写成了ds,花了二十分钟排错 
     mov ax,[0]
     mov bx,512                      ;512字节每扇区
     div bx
     cmp dx,0
     jnz @1                          ;未除尽,因此结果比实际扇区数少1 
     dec ax                          ;已经读了一个扇区,扇区总数减1 
@1:
     cmp ax,0                        ;考虑实际长度小于等于512个字节的情况 
     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                          ;恢复数据段基址到用户程序头部段 
...

13、用户程序的重定位

用户程序被读入内存之后,下一步就是计算用户程序中每个段的段基地址。
段的汇编地址是相对于整个程序开头处的偏移量,用户程序各段的汇编地址:
在这里插入图片描述
段的汇编地址不是他们在内存中的物理地址,也不是段地址;
用户程序被加载到内存中之后各个段的物理地址为:
在这里插入图片描述
将物理地址右移4位得到逻辑段地址,这个过程就是段的重定位。

重定位之前的内存分布,此时DS指向头部段,此时入口点所在代码段的逻辑地址还是其汇编地址,需要转换为逻辑段地址。
在这里插入图片描述
使用下列代码修改程序的段地址:

...
	;计算入口点代码段基址 
direct:
	mov dx,[0x08]
	mov ax,[0x06]
	call calc_segment_base
	mov [0x06],ax	;回填修正后的入口点代码段基址 
...
...
;-------------------------------------------------------------------------------
calc_segment_base:	;计算16位段地址
					;输入:DX:AX=32位物理地址
					;返回:AX=16位段基地址 
	push dx                          
	
	add ax,[cs:phy_base]		;产生进位,标志位CF置1、否则CF置0
	adc dx,[cs:phy_base+0x02]	;除了将操作数相加,还要加上进位标志CF的值
	shr ax,4					;逻辑右移,CF标志位 = 最后一个移出的比特位
	ror dx,4					;循环右移,CF标志位 = 最后一个移出的比特位
	and dx,0xf000
	or ax,dx
	
	pop dx
	
	ret
...

14、比特位的移动指令

逻辑右移指令shr
在这里插入图片描述
在这里插入图片描述
循环右移指令ror
在这里插入图片描述
在这里插入图片描述
代码如下:

;-------------------------------------------------------------------------------
calc_segment_base:	;计算16位段地址
					;输入:DX:AX=32位物理地址
					;返回:AX=16位段基地址 
	push dx                          
	
	add ax,[cs:phy_base]		;产生进位,标志位CF置1、否则CF置0
	adc dx,[cs:phy_base+0x02]	;除了将操作数相加,还要加上进位标志CF的值
	shr ax,4					;逻辑右移,CF标志位 = 最后一个移出的比特位
	ror dx,4					;循环右移,CF标志位 = 最后一个移出的比特位
	and dx,0xf000
	or ax,dx
	
	pop dx
	
	ret
...

逻辑左移、循环左移:
在这里插入图片描述

15、转到用户程序内部执行

第13节中完成了用户程序头部段的重定位,这一节完成用户程序剩余所有段的重定位:

....
realloc:
	mov dx,[bx+0x02]		;32位地址的高16位 
	mov ax,[bx]
	call calc_segment_base	;用段的汇编地址生成逻辑段地址,保存在AX中
	mov [bx],ax             ;回填段的基址
	add bx,4                ;下一个重定位项(每项占4个字节) 
	loop realloc 
....

16、8086的无条件转移指令

相对短转移(8位:-128~127)、相对近转移(16位:-65536~65535)。
这两个指令都是段内转移指令,只能转移到当前代码段的另一个地方,不能转移到其他段内。这两个指令执行时,都是使用指令指针寄存去IP中的值加上指令中的相对偏移量(目标代码处的汇编地址 减去 当前指令的吓下一条指令汇编地址)得到目标位置的偏移地址,使用这个偏移地址修改IP的值,以此转移到目标位置执行。
如果省略了shortnear,则由编译器根据目标位置的远近来决定使用哪个形式的转移指令。
在这里插入图片描述
16位间接绝对近转移:也是段内近转移(16位:-65536~65535),因为目标位置的地址通过寄存器或者地址间接给出,并替换IP内的值。所以叫间接绝对近转移。
在这里插入图片描述
16位直接绝对远转移:使用段地址替换CS内容、偏移地址替换IP内容,此方式位段间转移
在这里插入图片描述
16位间接绝对远转移:给出的目标位置为4个字节,高地址2字节处存放段地址、低地址2字节存放偏移地址。使用段地址取代CS、偏移地址取代IP
在这里插入图片描述
程序中:在重定位用户程序所有段地址之后,使用间接绝对远转移跳转到用户程序中执行。

  • 处理器使用DS内容左移4位加上0x04构成20位的有效物理地址,取出此地址出的数据给jmp far指令使用;
  • jmp far指令从上面获得的高16位地址处取出数据替换CS内容;
  • jmp far指令从上面获得的低16位地址处取出数据替换IP内容;
  • 使用CS中内容左移4位 加上 IP内容构成有效的物理地址,程序将会跳转到此地址处执行。
....
realloc:
	mov dx,[bx+0x02]		;32位地址的高16位 
	mov ax,[bx]
	call calc_segment_base	;用段的汇编地址生成逻辑段地址,保存在AX中
	mov [bx],ax             ;回填段的基址
	add bx,4                ;下一个重定位项(每项占4个字节) 
	loop realloc 
jmp far [0x04]				;转移到用户程序  
...

17、用户程序的执行过程

声明未初始化数据的指令:resbreswresd,都是跳过此段空间,未初始化,里面的值不确定。
resb 256 = resw 128 = resd 64,都是保留256个字节的内存空间。

用户程序的具体工作流程看视频,基本就是:
1、设置用户程序自己的栈、代码段、数据段
2、显示数据段中定义的字符
具体以代码查看c08章中的userapp.asm

		;包含代码段、数据段和栈段的用户程序
;===============================================================================
SECTION header vstart=0							;用户程序头部段
	program_length	dd	program_end				;程序总长度[0x00]

;用户程序入口点
	code_entry	dw	start						;偏移地址[0x04]
				dd	section.code.start			;段地址[0x06] 

realloc_tbl_len dw	(segtbl_end-segtbl_begin)/4	;段重定位表项个数[0x0A]

;段重定位表
	segtbl_begin:
	code_segment	dd	section.code.start		;[0x0C]
	data_segment	dd	section.data.start		;[0x10]
	stack_segment   dd	section.stack.start		;[0x14]
	segtbl_end:

;===============================================================================
SECTION code align=16 vstart=0					;代码段 
start:
	;初始执行时,DS和ES指向用户程序头部段
	mov ax,[stack_segment]						;设置到用户程序自己的堆栈 
	mov ss,ax
	mov sp,stack_pointer						;设置初始的栈顶指针

	mov ax,[data_segment]						;设置到用户程序自己的数据段
	mov ds,ax

	mov ax,0xb800
	mov es,ax

	mov si,message
	mov di,0

next:
	mov al,[si]
	cmp al,0
	je exit
	mov byte [es:di],al
	mov byte [es:di+1],0x07
	inc si
	add di,2
	jmp next

exit:
	jmp $ 

;===============================================================================
SECTION data align=16 vstart=0					;数据段
	message	db	'hello world.',0

;===============================================================================
SECTION stack align=16 vstart=0					;栈段
	resb 256
	stack_pointer:

;===============================================================================
SECTION trail align=16							;尾部
	program_end:

18、验证加载器加载和执行用户程序的过程

xp命令是显示内存中的数据:xp /30xh ds:0x表示16进制、h表示字节、30表示30个数据、ds:0表示在ds偏移地址为0处的数据。
在这里插入图片描述

19、原书第8章程序概述

具体以代码查看c08章中的c08.asm

20、与文本有关的回车、换行与光标控制

具体以代码查看c08章中的c08.asm

在数据段中0x0d0x0a分别代表回车和换行,其中段data_1使用了vstart=0字句,则标号msg0的汇编地址就是其段内偏移地址,均为0

;===============================================================================
SECTION data_1 align=16 vstart=0

    msg0 db '  This is NASM - the famous Netwide Assembler. '
         db 'Back at SourceForge and in intensive development! '
         db 'Get the current versions from http://www.nasm.us/.'
         db 0x0d,0x0a,0x0d,0x0a
         db '  Example code for calculate 1+2+...+1000:',0x0d,0x0a,0x0d,0x0a
         db '     xor dx,dx',0x0d,0x0a
         db '     xor ax,ax',0x0d,0x0a
         db '     xor cx,cx',0x0d,0x0a
         db '  @@:',0x0d,0x0a
         db '     inc cx',0x0d,0x0a
         db '     add ax,cx',0x0d,0x0a
         db '     adc dx,0',0x0d,0x0a
         db '     inc cx',0x0d,0x0a
         db '     cmp cx,1000',0x0d,0x0a
         db '     jle @@',0x0d,0x0a
         db '     ... ...(Some other codes)',0x0d,0x0a,0x0d,0x0a
         db 0

;===============================================================================

光标在屏幕上的位置保存在光标寄存器(2个8位寄存器构成的16位)中:
在这里插入图片描述
c08章中的c08.asm代码中子程序put_char的运行流程:
在这里插入图片描述

21、回车的光标处理和乘法指令MUL

具体以代码查看c08章中的c08.asm

索引寄存器(索引端口0x3D4)、数据寄存器(数据端口0x3D5),光标寄存器位置的索引值(高8位:0x0E、低8位:0x0F);
索引端口用来选择显卡内部的寄存器,需要向其提供一个及寄存器的编号,比如是0x0F;
那么数据端口将会和0x0F端口接通,就可以通过数据端口读写0x0F的数据。

代码如下:其中光标的范围是列(0~79)、行(0~24

...
	;以下取当前光标位置
	mov dx,0x3d4
	mov al,0x0e
	out dx,al
	mov dx,0x3d5
	in al,dx                        ;高8位 
	mov ah,al
	
	mov dx,0x3d4
	mov al,0x0f
	out dx,al
	mov dx,0x3d5
	in al,dx                        ;低8位 
...	

mul指令:无符号数乘法指令
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
imul指令:有符号数乘法指令
在这里插入图片描述
用法与mul一致。

22、换行和普通字符的处理过程与滚屏操作

具体以代码查看c08章中的c08.asm
滚屏操作:原先的0~23行上移一行变为1~24
在这里插入图片描述

23、8086的过程调用方式CALL

CALL指令形式:
1、16位相对近调用:段内调用
在这里插入图片描述
其中相对偏移量为:标号处的汇编地址 减去 call指令下一条指令的汇编地址。
call指令执行时:处理器用IP内容压栈保存之后 加上 call指令中的16位偏移量得到子程序的偏移地址,CS左移4位 加上IP内容形成20位有效物理地址,之后转到目标位置处执行。

2、16位间接绝对近调用:段内调用
在这里插入图片描述
call指令执行时:处理器将IP内容压栈保存,将操作数中的内容传送到IP,之后将段寄存器DS内容左移4位 加上 IP中的偏移地址 形成20位有效物理地址,即处理器转移到目标位置执行。

3、16位直接绝对远调用:段间调用
在这里插入图片描述
call指向执行时:处理器将代码段CSIP内容压栈,接着用指令中给出的段地址、偏移地址代替CSIP的内容,之后CS左移4位加上IP形成20位有效物理地址,即处理器转移到目标位置执行。

4、16位间接绝对远调用:段间调用
在这里插入图片描述
call指向执行时:处理器将代码段CSIP内容压栈,CS左移4位 加上从指令中给出的偏移地址处取出实际的段地址(高位)和偏移地址(低位),分别传送到CSIP,之后CS左移4位加上IP形成20位有效物理地址,即处理器转移到目标位置执行。

24、通过RETF指令转到另一代码段执行

1、近过程调用和返回方式:
在这里插入图片描述
ret指令导致处理器从栈中将返回点的偏移地址弹出到IP,导致处理器从过程返回。

2、远过程调用和返回方式:
在这里插入图片描述
retf指令导致处理器从栈中将返回点的偏移地址和段地址弹出到IPCS,导致处理器从过程返回。

代码如下:具体以代码查看c08章中的c08.asm

...
	push word [es:code_2_segment]	;压入目标位置的 段地址
	mov ax,begin
	push ax							;压入目标位置的 偏移地址,可以直接push begin, ;80386+
	
	retf							;转移到代码段2执行 
									;可使jmp far、call far跳转执行
									;这里使用retf来模拟段间调用返回的过程
...	

25、在程序中访问不同的数据段

代码如下:具体以代码查看c08章中的c08.asm和视频。

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

26、使用新版的FixVhdWr软件写虚拟硬盘并运行程序

使用fixvhdw64.exe软件加载程序并执行,软件在配套QQ群里。

virtualBox虚拟机运行:
在这里插入图片描述
Bochs虚拟机运行:
跳转到用户程序执行之前栈的状态:
在这里插入图片描述
使用n命令持续执行代码:可以看出程序已经在用户程序中执行
在这里插入图片描述
转到第2个代码段执行之前:
在这里插入图片描述
之后:
在这里插入图片描述
其他内容看视频即可。

27、原始第8章习题

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第1题:
data_1段内定义一个双字的标号entry
在这里插入图片描述
在代码段code_1中修改程序:
在这里插入图片描述
上一节:18、INTEL8086处理器的寻址方式
下一节:20、中断和动态时钟显示

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值