X86汇编语言:从实模式到保护模式(代码+注释)--c8(硬盘和显卡的访问与控制)

硬盘和显卡的访问与控制

编译好的程序通常存放在硬盘这样的载体中,需要加载到内存之后才能执行。首先要读取硬盘,决定将它加载到内存的什么位置,加载到内存中后,因为程序通常是分段的,因此还需要重新计算段地址,叫做段重定位。
操作系统的功能:处理器管理,内存分配,程序加载、进程调度、外设控制和管理等任务。
8086地址总线的宽度为20位。

  1. 分段、段的汇编地址和段内汇编地址
    • 一个规范的程序应该包括代码段、数据段、附加段和堆栈段
    • 段表达式:section.段名称.start。
    • vstart子句:明确段中的内容地址计算需要从什么地方开始,例如vstart=0,则段内地址计算从该段的开始计算。
    • 加载器需要知道程序的一些必要信息,才能够进行加载。因此用户程序头部必须要包含一下信息:
      ①用户程序的尺寸,以字节为单位,加载器需要通过这一信息来决定要读取多少个扇区。
      ②应用程序的入口点,包括段地址和偏移地址。必须要在头部给出第一条指令的段地址和偏移地址,这就是程序的入口点(等价于代码段内偏移地址为0的位置)。
      ③段重定位表:重新定位用户程序中的多个段的地址,用户程序加载到内存中后,每个段必须重新确定。
  2. 加载器需要明确用户程序所在的扇区,例如:app_lba_start equ 100;用户程序扇区等于100
  3. 用户程序物理地址为0x10000,物理地址0xA0000以上是BIOS和外设的内存空间,因此,用户程序的可用空间为0x10000–0x9FFFF,有最多576KB的空间
  4. 堆栈段段地址为0x0000,偏移值为0x0000–0xFFFF,而且增长方向为从高地址到低地址。主引导程序为512字节,因此从0x7c00加上512字节为0x7E00,还剩0x81FF这些空间,大概32KB左右,因此堆栈的空间还是可以的。
  5. 外部设备必须使用I/O接口才能够与处理器进行交互,例如显示器的显卡、扬声器的声卡,USB键盘的USB接口。接口可以是电路板或者芯片。
  6. I/O外设的端口(寄存器)可以是独立编址的,也可以是内存映射的。
  7. 外设及接口
    • 总线技术解决多个外设与cpu链接的问题:所有外设连接到总线上。
    • 输入输出控制设备集中器芯片(ICH)(控制外设I/O与处理器之间的交互):该芯片的作用是连接不同的总线,并协调各个I/O接口对处理器的访问。个人计算机上这个芯片就是南桥。
    • 链接硬盘的PATA/SATA:有多个端口,如命令端口,状态端口,参数端口,数据端口。分配了8个端口,ICH芯片中集成了两个PATA/SATA接口,分别为主硬盘接口和副硬盘接口。主硬盘端口为0x1f0–0x1f7,副硬盘端口为0x170–0x177。端口访问的命令只能用in或者out(in(从端口读)不能使用内存单元做操作数,目的操作数必须是AL或者AX,源操作数可以是dx或者立即数(大小为1字节,0xf50则不合法),out(向端口写)用法相反,目的操作数是立即数或者dx,源操作数必须是ax或者al)
         ;代码清单8-2
         ;文件名:c08.asm
         ;文件说明:用户程序 
         ;创建日期:2011-5-5 18:17
         
;===============================================================================
SECTION header vstart=0                     ;定义用户程序头部段 
    program_length  dd program_end          ;程序总长度[0x00]
    
    ;用户程序入口点
    code_entry      dw start                ;偏移地址[0x04]
                    dd section.code_1.start ;段地址[0x06] 
    
    realloc_tbl_len dw (header_end-code_1_segment)/4	;下面每个段重定位表项的大小为dd,也就是双字,4字节,因此除以4
                                            ;段重定位表项个数[0x0a],同时code_1_segment为段重定位表的第一个表项
    
    ;段重定位表           
    code_1_segment  dd section.code_1.start ;[0x0c],section.code_1.start是一个32位的地址,地址的长度为20,因此需要使用32位的内存空间保存地址,使用双字来保存
    code_2_segment  dd section.code_2.start ;[0x10]
    data_1_segment  dd section.data_1.start ;[0x14]
    data_2_segment  dd section.data_2.start ;[0x18]
    stack_segment   dd section.stack.start  ;[0x1c]
    
    header_end:                
    
;===============================================================================
SECTION code_1 align=16 vstart=0         ;定义代码段116字节对齐) 
put_string:                              ;显示串(0结尾);输入:DS:BX=串地址
         mov cl,[bx]
         or cl,cl                        ;cl=0 ?
         jz .exit                        ;是的,返回主程序 
         call put_char
         inc bx                          ;下一个字符 
         jmp put_string

   .exit:
         ret

;-------------------------------------------------------------------------------
put_char:                                ;显示一个字符
                                         ;输入:cl=字符ascii
         push ax
         push bx
         push cx
         push dx
         push ds
         push es

         ;读取光标位置的高8位
        mov dx,0x3d4	;通过索引端告诉显卡要操作的寄存器为0x0e,索引寄存器的端口号为0x3d4
        mov al,0x0e
        out dx,al
		mov dx,0x3d5	;读写过程的数据端口
         in al,dx                        ;8位 
         mov ah,al
			;读取光标位置的低8位
         mov dx,0x3d4
         mov al,0x0f
         out dx,al
         mov dx,0x3d5
         in al,dx                        ;8位 
         mov bx,ax                       ;BX=代表光标位置的16位数,AX和BX都保存了光标的位置(16位)

         cmp cl,0x0d                     ;回车符?
         jnz .put_0a                     ;不是。看看是不是换行等字符 
         mov ax,bx                       ;此句略显多余,但去掉后还得改书,麻烦 。AX保存了光标的位置
         mov bl,80                       
         div bl
         mul bl
         mov bx,ax						;重新计算光标的位置到行的开头
         jmp .set_cursor

 .put_0a:
         cmp cl,0x0a                     ;换行符?
         jnz .put_other                  ;不是,那就正常显示字符 
         add bx,80
         jmp .roll_screen

 .put_other:                             ;正常显示字符
         mov ax,0xb800
         mov es,ax
         shl bx,1		;光标位置x2,因为光标位置指定是的要写入字符的位置,光标位置转换到显存地址需要重新计算偏移,一个字符占两个字节,例如将插入第6个字符(从0开始),则显存内存的偏移地址为第10个位置
         mov [es:bx],cl

         ;以下将光标位置推进一个字符
         shr bx,1	;与上述的shl相反的还原bx的操作
         add bx,1	;光标往下移动一个位置

 .roll_screen:
         cmp bx,2000                     ;光标超出屏幕?滚屏
         jl .set_cursor

         mov ax,0xb800
         mov ds,ax
         mov es,ax
         cld
         mov si,0xa0
         mov di,0x00
         mov cx,1920
         rep movsw
         mov bx,3840                     ;清除屏幕最底一行
         mov cx,80
 .cls:
         mov word[es:bx],0x0720
         add bx,2
         loop .cls

         mov bx,1920

 .set_cursor:	;输入为BX
         mov dx,0x3d4
         mov al,0x0e
         out dx,al
         mov dx,0x3d5
         mov al,bh
         out dx,al
         mov dx,0x3d4
         mov al,0x0f
         out dx,al
         mov dx,0x3d5
         mov al,bl
         out dx,al

         pop es
         pop ds
         pop dx
         pop cx
         pop bx
         pop ax

         ret

;-------------------------------------------------------------------------------
  start:
         ;初始执行时,DS和ES指向用户程序头部段
         mov ax,[stack_segment]           ;设置到用户程序自己的堆栈 
         mov ss,ax
         mov sp,stack_end
         
         mov ax,[data_1_segment]          ;设置到用户程序自己的数据段
         mov ds,ax

         mov bx,msg0
         call put_string                  ;显示第一段信息 

         push word [es:code_2_segment]
         mov ax,begin
         push ax                          ;可以直接push begin,80386+
         
         retf                             ;弹出栈中的两个字分别到IP和CS转移到代码段2执行 
         
  continue:
         mov ax,[es:data_2_segment]       ;段寄存器DS切换到数据段2 
         mov ds,ax
         
         mov bx,msg1
         call put_string                  ;显示第二段信息 

         jmp $ 

;===============================================================================
SECTION code_2 align=16 vstart=0          ;定义代码段216字节对齐)

  begin:
         push word [es:code_1_segment]
         mov ax,continue
         push ax                          ;可以直接push continue,80386+
         
         retf                             ;转移到代码段1接着执行 
         
;===============================================================================
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

;===============================================================================
SECTION data_2 align=16 vstart=0

    msg1 db '  The above contents is written by LeeChung. '
         db '2011-05-06'
         db 0

;===============================================================================
SECTION stack align=16 vstart=0
           
         resb 256

stack_end:  

;===============================================================================
SECTION trail align=16
program_end:
         ;代码清单8-1
         ;文件名:c08_mbr.asm
         ;文件说明:硬盘主引导扇区代码(加载程序) 
         ;创建日期:2011-5-5 18:17
         
         app_lba_start equ 100           ;声明常数(用户程序起始逻辑扇区号)
                                         ;常数的声明不会占用汇编地址
                                    
SECTION mbr align=16 vstart=0x7c00      ;这个地方的vstart=0x7c00是为了避免手动加上该值,因为加载器的段地址为0x0000,虽然会将该内容加载到0x7c00偏移处,但在计算中还是需要手动加上该值                               

         ;设置堆栈段和栈指针 
         mov ax,0      
         mov ss,ax
         mov sp,ax
      
         mov ax,[cs:phy_base]            ;计算用于加载用户程序的逻辑段地址 phy_base:0x00--0x04
         mov dx,[cs:phy_base+0x02]
         mov bx,16        
         div bx            				 ;计算用户程序的段地址,计算后的段地址保存在AX中
         mov ds,ax                       ;令DS和ES指向该段以进行操作
         mov es,ax                        
    
         ;以下读取程序的起始部分 
         xor di,di
         mov si,app_lba_start            ;程序在硬盘上的起始逻辑扇区号 
         xor bx,bx                       ;加载到DS:0x0000处 
         call read_hard_disk_0
      
         ;以下判断整个程序有多大
         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                          ;恢复数据段基址到用户程序头部段 
      
         ;计算入口点代码段基址 
   direct:
         mov dx,[0x08]
         mov ax,[0x06]
         call calc_segment_base
         mov [0x06],ax                   ;回填修正后的入口点代码段基址 
      
         ;开始处理段重定位表
         mov cx,[0x0a]                   ;需要重定位的项目数量
         mov bx,0x0c                     ;重定位表首地址
          
 realloc:
         mov dx,[bx+0x02]                ;32位地址的高16位 
         mov ax,[bx]
         call calc_segment_base
         mov [bx],ax                     ;回填段的基址
         add bx,4                        ;下一个重定位项(每项占4个字节) 
         loop realloc 
      
         jmp far [0x04]                  ;转移到用户程序  
 
;-------------------------------------------------------------------------------
read_hard_disk_0:                        ;从硬盘读取一个逻辑扇区
                                         ;输入:DI:SI=起始逻辑扇区号
                                         ;      DS:BX=目标缓冲区地址
         push ax
         push bx
         push cx
         push dx
      
         mov dx,0x1f2
         mov al,1
         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,256                      ;总共要读取的字数
         mov dx,0x1f0
  .readw:
         in ax,dx
         mov [bx],ax
         add bx,2
         loop .readw

         pop dx
         pop cx
         pop bx
         pop ax
      
         ret

;-------------------------------------------------------------------------------
calc_segment_base:                       ;计算16位段地址
                                         ;输入:DX:AX=32位物理地址
                                         ;返回:AX=16位段基地址 
         push dx                          
         
         add ax,[cs:phy_base]
         adc dx,[cs:phy_base+0x02]
         shr ax,4
         ror dx,4
         and dx,0xf000
         or ax,dx
         
         pop dx
         
         ret

;-------------------------------------------------------------------------------
         phy_base dd 0x10000             ;用户程序被加载的物理起始地址,低4位必须是0,起始地址必须是16字节对齐
         
 times 510-($-$$) db 0
                  db 0x55,0xaa

你可能感兴趣的:(汇编语言,汇编)