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

本次实践的目的:打破开机引导程序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     4000h

DA_DPL0 equ     00h
DA_DPL1 equ     20h
DA_DPL2 equ     40h
DA_DPL3 equ     60h

DA_DR   equ     90h
DA_DRW  equ     92h
DA_DRWA equ     93h
DA_C    equ     98h
DA_CR   equ     9ah
DA_CCO  equ     9ch
DA_CCOR equ     9eh

DA_LDT  equ     82h
DA_TaskGate     equ     85h
DA_386TSS       equ     89h
DA_386CGate     equ     8ch
DA_386IGate     equ     8eh
DA_386TGate     equ     8fh

COPY_CODE_START:

;全局描述符GDT,在切换到保护模式前,需先设置好相应的描述符。
[SECTION .gdt]
LABEL_GDT:         Descriptor 0,        0,      0
LABEL_DESC_CODE32: Descriptor 0,SegCode32Len - 1, DA_C + DA_32
LABEL_DESC_VIDEO:  Descriptor 0b8000h,  0ffffh,DA_DRW

GdtLen  equ $ - LABEL_GDT
GdtPtr  dw GdtLen - 1
        dd 0

SelectorCode32  equ     LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo   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.bin
dd if=bf.bin of=bf.img bs=512 count=2 conv=notrunc
bochs -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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值