操作系统实践(3)——火箭助推器

来源:互联网 发布:成绩分析软件 编辑:程序博客网 时间:2024/05/15 02:19

本次实践的目的:打破开机引导程序512字节的限制,并从实模式切换到保护模式。

我们知道,bios开机自检、找到启动设备后,把启动设备的第一个扇区加载内存0x7c00位置开始执行。前两次实践中,我们的引导程序小于512字节,这没造成什么问题。如果我们的引导程序超过512字节怎么办呢?我的第一个想法就是,利用加载到内存的这512字节,写个程序,把启动盘中真正的引导程序继续加载到内存中。看到《Orange’s 一个操作系统的实现》的第三章的时候,里面并没有采用这种做法,而是转而去用个DOS加载大于512字节的引导程序,那就自己动手写一个吧。

程序思路

  1. 写个少于512字节的引导程序,用于启动引导后,把软盘第2扇区(如果你写的代码多与512字节,需要修改下面的引导,这里简单只拷贝了1个扇区)的数据拷贝到 0x7e00 的位置(0x7e00 == 0x7c00 + 512)。具体实现是调用了bios的13h中断。
  2. 保护模式的寻址方式与实模式的寻址方式不同。 虽然从实模式到保护模式只需要设置cr0寄存器即可,但是切换过去后,其寻址依赖于GDT的实现,所以需切换前先设置好GDT。

bf.asm

    org 07c00h    [BITS 16]START:    mov ax,cs    mov ds,ax    mov es,ax    ;拷贝软盘中的代码到内存区COPY:    mov bx, COPY_CODE_START ;07c00h + 512(0100h) == 07e00h    mov dl,0        ;驱动器号,软驱从0开始:0:软驱A,1:软驱B                    ;磁盘从80h开始,80h:C盘,81h:D盘    mov dh,0        ;磁头号,对于软盘即面号,一个面用一个磁头来读写    mov ch,0        ;磁道号    mov cl,2        ;扇区号    mov al,2        ;读取的扇区数    mov ah,2        ;13h的功能号(2表示读扇区),es:bx指向                    ;接收从扇区读入数据的内存区    int 13h    jc COPY         ;读取失败,CF表示为1,重试读取       jmp LABEL_BEGIN ;把程序读到内存区后,跳转到新的执行点    ;补全512字节    times 510-($-$$) db 0    dw 0xaa55;这个宏用来填充gdt描述符的,每个描述符8个字节,64位。;参数1:段基址,32位;参数2:段大小limit,传32位,只用其低20位。;参数3:段属性,16位,只用高4位与低8位,中间4位为0。%macro Descriptor 3        dw %2 & 0FFFFh        dw %1 & 0FFFFh        db (%1 >> 16) & 0FFh        dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)        db (%1 >> 24) & 0FFh%endmacro;段属性的常量,具体参考《Orange's 一个操作系统的实现》DA_32   equ     4000hDA_DPL0 equ     00hDA_DPL1 equ     20hDA_DPL2 equ     40hDA_DPL3 equ     60hDA_DR   equ     90hDA_DRW  equ     92hDA_DRWA equ     93hDA_C    equ     98hDA_CR   equ     9ahDA_CCO  equ     9chDA_CCOR equ     9ehDA_LDT  equ     82hDA_TaskGate     equ     85hDA_386TSS       equ     89hDA_386CGate     equ     8chDA_386IGate     equ     8ehDA_386TGate     equ     8fhCOPY_CODE_START:;全局描述符GDT,在切换到保护模式前,需先设置好相应的描述符。[SECTION .gdt]LABEL_GDT:         Descriptor 0,        0,      0LABEL_DESC_CODE32: Descriptor 0,SegCode32Len - 1, DA_C + DA_32LABEL_DESC_VIDEO:  Descriptor 0b8000h,  0ffffh,DA_DRWGdtLen  equ $ - LABEL_GDTGdtPtr  dw GdtLen - 1        dd 0SelectorCode32  equ     LABEL_DESC_CODE32 - LABEL_GDTSelectorVideo   equ     LABEL_DESC_VIDEO - LABEL_GDT;这段16位的代码段,目的是实现从实模式到保护模式的切换。[SECTION .s16][BITS 16]LABEL_BEGIN:        mov ax,cs        mov ds,ax        mov es,ax        mov ss,ax        mov sp,0100h        ;设置好进入保护模式后立刻要执行的代码段的描述符        xor eax,eax        mov ax,cs        shl eax,4        add eax,LABEL_SEG_CODE32        mov word [LABEL_DESC_CODE32 + 2],ax        shr eax,16        mov byte [LABEL_DESC_CODE32 + 4],al        mov byte [LABEL_DESC_CODE32 + 7],ah        ;设置GDT        xor eax,eax        mov ax,ds        shl eax,4        add eax,LABEL_GDT        mov dword [GdtPtr + 2],eax        ;加载gdt        lgdt [GdtPtr]        cli        ;打开A20地址线,扩大寻址空间        in al,92h        or al,00000010b        out 92h,al        ;从实模式切换到保护模式        mov eax,cr0        or eax,1        mov cr0,eax        ;跳转到32位的保护模式的代码        jmp dword SelectorCode32:0;这段代码的功能,只是在屏幕右边的中间位置显示一个黑底红色的字母'P'[SECTION .s32][BITS 32]LABEL_SEG_CODE32:        mov ax,SelectorVideo        mov gs,ax        mov edi,(80 * 11 + 79) *2        mov ah,0ch        mov al,'P'    mov [gs:edi],ax        jmp $SegCode32Len equ $ - LABEL_SEG_CODE32

这里为了省去每次都敲一堆命令的麻烦,写了个简单的脚本
bf.sh

#!/bin/bash/usr/bin/nasm bf.asm -o bf.bindd if=bf.bin of=bf.img bs=512 count=2 conv=notruncbochs -f bf.bochs #这里的bf.bochs配置文件,请参考前一节的配置

执行 ./bf.sh 结果下图(屏幕右边中间位置有个红色的’P’):
操作系统实践--启动引导

问题

  1. gdt、gdtr结构如何?
    这个虽然在不同书籍都看过了,没亲自写代码,还是会忘掉。网上的版本请参考: 《GDT 与 LDT》。

  2. gdtr limit字段如何设置?为什么用gdt长度减一?
    参考gdt的limit字段,其实不应该理解为长度,而是与offset类似的,从0开始,比如说计算gdtr最高的地址的时候,就可以用基址+limit计算出来。

  3. 打开a20线还有其它方法吗?
    这个问题还没搞清楚,回头更新这里。

  4. 在16位模式下jmp dword SelectorCode32:0 其中的dword 是修饰哪个?linux内核中用db写二进制是如何实现的?
    按目前的理解,选择子只有13位有效,所以 SelectorCode32这个用16位足以,而后面的偏移量offset,则可以是32位的。这句代码反汇编之后结果为:
    00007e71: 66ea 0000 0000 0800 jmpf 0x0008:0000 0000
    至于linux内核如何实现,后续跟进。我猜想就是直接用类似上面的二进制代码方式实现。

0 0
原创粉丝点击