call指令和jmp指令不一样的地方在于,call指令可以用ret(配对近调用)和retf(配对远调用),ret执行的时候,处理器会从栈中弹出一个字的指令到ip中并替换ip的值,retf会弹出两个字,分别是段地址和偏移地址分别替代ip的值和cs的值。
事实上所有的call指令都会有压栈动作,如果是近转移,那么就把ip的值压栈,如果是远转移,那么就把ip的值和cs的值压栈。而事实上,我们如果不执行call指令,直接用push指令也可以产生相同的效果,ret/retf只是负责弹出内容到相应的地方而已。(也就是说,call要和ret或者retf配对,ret和retf不一定要和call配对),call指令不对任何标志位产生影响,ret/retf也不会对任何标志位产生影响。
所以现在我们就调用我们在代码段2的显示字符串的代码,为了内存利用最大化,可以利用了retf的特性,把代码段2的地址和代码段2的第一条指令的偏移地址压栈,然后直接用retf跳转。
2. 对光标的控制
在文本模式下,光标是一个很重要的东西,光标可以指示下一个字符的写入位置,在第九章对光标的控制用更快的方法就是利用中断就可以自动将光标放到对应的位置,在这里我们直接用最简单的方法来实现一些光标的简单的功能就可以了,在教材上的这一章上,光标是不能动的,所以指示清屏换行和回车这些特殊符号就可以了。
显卡的操作很复杂,为了不过多地占用主机的I/O空间,很多寄存器只能通过索引寄存器间接访问,索引寄存器的端口号是0x3d4,可以向他写入一个值来只是内部的某个寄存器,比如写入0x0e就可以获取光标位置的高8位,写入0x0f就可以获得光标位置的低8位。所以我们可以直接这么写获取光标位置到ax寄存器:
★PART3:本章的所有代码:
1. 主引导程序
1 ;-----------------------实模式下主引导扇区代码---------------------------------- 2 SECTION code_mbr_start align=16 vstart=0x7c00 ;一定不要忘记要给vstart=0x7c00 3 start: 4 app_lba_start equ 100 ;声明常数(用户程序起始逻辑扇区号) 5 6 mov ax,0 7 mov ss,ax 8 mov sp,ax 9 10 mov ax,[cs:phy_base] 11 mov dx,[cs:phy_base+0x02] 12 mov bx,16 13 div bx 14 mov ds,ax ;让ds指向内存段 15 mov es,ax ;因为等下用户程序要用到es,顺便一起设置了 16 17 xor di,di ;逻辑扇区的16-27位 18 mov si,app_lba_start ;逻辑扇区的0-15位 19 xor bx,bx 20 call read_harddisk_0 21 22 mov ax,[0x00] ;得到总长度 23 mov dx,[0x02] 24 mov bx,0x200 25 div bx ;ax得到占用多少个扇区 26 27 cmp dx,0 28 jne read_last_content ;如果不等于0,那就不需要减去1了 29 dec ax 30 31 read_last_content: ;读取剩余的扇区 32 cmp ax,0 ;等于0就不用再读取了 33 je begin 34 35 push ds ;先存一下指向用户程序头部的ds 36 37 mov cx,ax 38 xor bx,bx 39 @loop1: 40 mov ax,ds 41 add ax,0x20 ;因为每次只读一个扇区,所以要把段地址每次都移动512个字节 42 mov ds,ax 43 inc si ;读下一个扇区,一定要记得+1! 44 call read_harddisk_0 45 loop @loop1 46 47 pop ds 48 49 begin: 50 ;然后开始给所有的段重定位 51 ;先回填用户程序起始代代码段 52 mov bx,0x06 53 call realloc_segement 54 55 mov cx,[0x0a] 56 mov bx,0x0c ;注意bx是偏移地址 57 realloc: 58 call realloc_segement 59 add bx,4 60 loop realloc 61 62 jmp far [0x04] ;远转移指令,直接读取32个字,前16位是偏移地址,后16位是段地址 63 64 ;------------------------------------------------------------------------------- 65 ;---------------------------------函数部分-------------------------------------- 66 ;------------------------------------------------------------------------------- 67 read_harddisk_0: 68 push ax 69 push bx 70 push cx 71 push dx 72 73 mov dx,0x1f2 74 mov al,0x01 75 out dx,al ;请求读一个硬盘 76 77 mov ax,si ;0~7位,端口0x1f3 78 inc dx 79 out dx,al 80 81 mov al,ah ;8~15位,端口0x1f4 82 inc dx 83 out dx,al 84 85 inc dx ;16-23位,端口0x1f5 86 mov ax,di 87 out dx,al 88 89 inc dx 90 mov al,0xe0 ;LBA28模式,主盘 91 and ah,0x0f ;清掉高4位,24-27位,端口0x1f6 92 or al,ah 93 out dx,al 94 95 inc dx 96 mov al,0x20 ;读命令,端口0x1f7(命令端口) 97 out dx,al 98 99 .wait: 100 in al,dx 101 and al,0x88 102 cmp al,0x08 103 jnz .wait 104 105 mov cx,256 106 mov dx,0x1f0 107 108 .read: 109 in ax,dx 110 mov [bx],ax 111 add bx,2 112 loop .read 113 114 pop dx 115 pop cx 116 pop bx 117 pop ax 118 119 ret 120 ;------------------------------------------------------------------------------- 121 realloc_segement: 122 push ax 123 124 mov ax,[bx] ;注意地址是32位的! 125 mov dx,[bx+0x02] 126 127 push bx 128 add ax,[cs:phy_base] 129 adc dx,[cs:phy_base+0x02] ;直接用除法指令就好了不需要用左移那么复杂 130 mov bx,16 131 div bx 132 133 pop bx 134 mov [bx],ax 135 136 pop ax 137 138 ret 139 ;------------------------------------------------------------------------------- 140 ;----------------------------------数据区--------------------------------------- 141 ;------------------------------------------------------------------------------- 142 phy_base dd 0x10000 ;用户程序被加载的物理起始地址 143 ;------------------------------------------------------------------------------- 144 tail: 145 times 510-($-$$) db 0 146 dw 0xaa55