第8章 硬盘和显卡的访问与控制

来源:互联网 发布:java生成六位验证码 编辑:程序博客网 时间:2024/05/21 18:41

首先声明,这一章非常重要,如果刚开始读不懂,读不下去,一定要坚持,还有读这本书的一个要求是王爽《汇编语言》看两遍,并做完所有的课后实验。这一章其实是操作系统的的加载和引导过程。其中涉及的有硬盘读写,程序加载,程序重定位等,令人沮丧的是,这三个东西都不是那么容易,你有可能看了几遍还是不十分明晰,令人高兴的是,操作系统引导只需要这三个重要的东西,因为下一步就是进入操作系统的“森林”了。这些东西不难,你感觉难只是不熟悉而已。本章代码不完全明白熟悉,不要进入下一章,因为遗留的疑问会让你更弄不明白后边的问题。

下面分而治之,结合载入程序代码请思考如下问题:

a)程序在哪里?

程序放在100扇区

b)程序存移动到哪里?

0x10000 这个地址没有什么特别的意思,你可以放到其它合适的地址,唯一的要求是该地址的最低4位必须是 0,换句话说,加载的起始地址必须是16字节对齐的,这样将来才能形成一个 有效的段地址。

c)怎样读磁盘?

read_hard_disk0

d)怎样放到目的内存?

e)怎样“搬运”程序?

f)为什么重定位,怎样重定位?

载入程序

         ;代码清单8-1         ;文件名:c08_mbr.asm         ;文件说明:硬盘主引导扇区代码(加载程序)          ;创建日期:2011-5-5 18:17                  app_lba_start equ 100           ;声明常数(用户程序起始逻辑扇区号)                                         ;常数的声明不会占用汇编地址                                    SECTION mbr align=16 vstart=0x7c00                                              ;设置堆栈段和栈指针          mov ax,0               mov ss,ax         mov sp,ax               mov ax,[cs:phy_base]            ;计算用于加载用户程序的逻辑段地址          mov dx,[cs:phy_base+0x02]         mov bx,16                 div bx                     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             ;用户程序被加载的物理起始地址          times 510-($-$$) db 0                  db 0x55,0xaa




用户程序

         ;代码清单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                                            ;段重定位表项个数[0x0a]        ;段重定位表               code_1_segment  dd section.code_1.start ;[0x0c]    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         ;定义代码段1(16字节对齐) 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         ;以下取当前光标位置         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位          mov bx,ax                       ;BX=代表光标位置的16位数         cmp cl,0x0d                     ;回车符?         jnz .put_0a                     ;不是。看看是不是换行等字符          mov ax,bx                       ;此句略显多余,但去掉后还得改书,麻烦          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         mov [es:bx],cl         ;以下将光标位置推进一个字符         shr bx,1         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:         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                             ;转移到代码段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          ;定义代码段2(16字节对齐)  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 256stack_end:  ;===============================================================================SECTION trail align=16program_end:

实验现象:


实验体会:

本章包含的信息量特别大,重点关注四个东西:a)怎样读磁盘;b)怎样加载程序;c)怎样重定位;d)从bios启动到陷入用户程序的死循环整个流程cs:ip是怎样变化的,相应的,像字符串的显示也是比较复杂的,但不紧急,可以暂时不了解这些细节,放在平时慢慢揣摩熟悉。













原创粉丝点击