从头开始编写操作系统(6) 第5章:引导加载器3

来源:互联网 发布:非标自动化设计软件 编辑:程序博客网 时间:2024/06/05 21:18

译自:http://www.brokenthorn.com/Resources/OSDev5.html

5章:引导加载器3
by Mike, 2009

本系列文章旨在向您展示并说明如何从头开发一个操作系统。

请注意:本章计划在近期更新以修正错误并提供更多相关信息。

介绍

欢迎!

在前一章里,我们了解了处理器的不同模式,以及一些BIOS中断。我们也了解了在实模式中段:偏移的寻址方式,并深入解释了实模式。我们也扩展了我们的引导加载器,我们增加了谜一样的OEM参数块,并增加了打印字符串的功能。

在本章里,我们会看看不同的“环”,它表明了应用程序和系统程序的不同。

我们也会看到一段引导和多段引导,以及他们的优缺点。

最后,我们了解一下BIOS INT 0x13, OEM参数块,及读、加载、执行一个程序这个程序将是我们的第二段引导加载器。我们的第二段引导加载器将设置32位环境,并为C内核的加载做好准备。

准备好了吗?

汇编语言的环

在汇编语言中,你可能听说过“环0程序”,“这是一个环3程序”的说法。在操作系统开发中理解不同的环(是什么)是有用的。

环——理论

什么是环呢?在汇编语言中,环是系统中保护与控制的层次。有4个环:环0,环1,环2,环3

0对系统所有部分有绝对控制权,而环3只有很少的控制权。软件的环值越小,控制权越大(保护越少)。

环不只只是一个概念——它是处理器的体系结构。

当计算机启动,当引导加载器运行时,处理器工作在环0,大多数的应用程序,比如DOS应用程序,运行在环3。而操作系统工作在环0,比一般的应用程序有更大的控制权。

切换环

因为环是处理器体系结构的一部分,当处理器状态改变时就可能发生环的切换,在以下情况下,它可能改变

  • 在不同环级的重定向指令,如 far jump, far call, far return
  • 陷阱(trap指令,如INT, SYSCALL, SYSENTER
  • 异常Exceptions

我们在后面会讨论异常处理及 SYSCALL SYSENTER 指令。

多段引导加载器

单段引导加载器

引导加载器、引导扇,只能有512字节。如果引导加载器比512字节少,它直接执行内核的话,它就是一个单段引导加载器

问题是它的大小。512字节太小了,很难在一个16位的引导加载器中设置、加载并执行一个32位的内核。这还不包含错误控制代码。这些代码要包括: GDT, IDT,A20, PMode, 查找并加载 32kernel, 执行 kernel, 和错误控制 。在512字节中放下全部的这些东西,不大可能。所以单段引导加载器必须加载并执行一个16位的内核。

因此大多数的引导加载器都是多段引导加载器。

多段引导加载器

一个多段引导加载器包括一个512字节的引导加载器(单段引导加载器),这个加载器仅仅加载并执行另一个引导加载器——第二段引导加载器。第二段引导加载器往往是16位的,但包含更多的代码。它能够加载并执行一个32位的内核。

这是因为引导加载器的512字节限制。当引导加载器加载了第二段引导加载器所需的全部扇区后,第二段引导加载器就没有了大小的限制。这样他更容易完成加载内核的工作。

我们将使用二段(2 Stage)引导加载器。

从磁盘加载扇区

因为引导加载器被限制到512字节,不可能做太多的工作。正如前一节中所说,我们使用二段引导加载器,也就是说,引导加载器加载并执行我们的第二段程序——内核加载器。

如果你想,可以在第二段加载器中放置一个“选择操作系统”和“高级选项”的菜单J,做吧,我知道你想法要一个

BIOS INT 0x13 0号功能 复位软盘驱动器

BIOS 0x13 中的用于磁盘访问。你可以使用INT 0x130号功能复位软盘驱动器。什么意思?无论软盘控制器在读什么位置,它会立即返回磁盘的第一个扇区。

INT 0x13/AH=0x0 – 磁盘:复位软盘驱动器
AH = 0x0
DL =
待复位驱动器

返回值:
AH =
状态码
CF (
进位标志)成功为0,失败为1

这是一个完整的例子。它重设软盘驱动器,如果出错会再试一次:

.Reset:

        mov            ah, 0                                 ; 复位磁盘

        mov            dl, 0                                 ; drive 0 是软驱

        int            0x13                                  ;BIOS调用

        jc             .Reset                                ; 如果进位标志 (CF) 1,表示有错误发生,再试一次。

为什么这个中断很重要?在读扇区之前,我们要保证从0扇区开始。我们不知道软盘控制从哪读,这不好,这使得每次启动都不一样。复位磁盘到0扇区会使我们每次从同一个扇区开始。

BIOS INT 0x130x02号功能读扇区

INT 0x13/AH=0x02 – 磁盘 : 往内存中读扇区
AH = 0x02
AL =
要读入的扇区数
CH =
柱面号的低8
CL =
扇区号(Bits 0-5). Bits 6-7只对硬盘有效
DH =
磁头号
DL =
驱动器号 (对于硬盘第7位为1)
ES:BX =
保存读到扇区的缓冲区

返回值:
AH =
状态码
AL =
读到的扇区数
CF =
失败为1,成功为0

有不少要考虑的,一些很简单,另一些要详加说明。

CH = 柱面号的低8

什么是柱面(Cylinder? 柱面是(具有相同半径的)一组磁道,为了理解它,看下图:

http://www.brokenthorn.com/Resources/images/cylinder.gif

看看上面的图:

  • 每个磁道被分为512字节的扇区。在软盘上,每磁道有18个扇区
  • 一个柱面是一组有相同半径的磁道(图中红线)
  • 软盘有两个磁头
  • 共有2880个扇区。

这对我们有什么用?柱面数表示单个磁盘的磁道数,对于软盘,它表示要读的磁道

每磁道18个扇区,在软盘上有63个磁道。

如果柱面号比63大,软盘控制器会发送一个异常,因为扇区不存在。因为不存在错误控制代码,CPU会产生另一个异常,最终导致一个三重错误。

CL = 扇区号(Bits0-5). Bits 6-7只对硬盘有效
读取的第一个扇区号。要记住:每磁道只有18个是扇区,该值只能在017之间,否则就增大当前磁道的值,以确保扇区号设置为你需要读取的那个扇区。

如果该值大于18,软盘控制器会因为扇区不存在而产生一个异常。由于没有错误控制,CUP会产生另外一个错误异常,这最终导致一个三重错误。

DH = 磁头号

记住对于某些软盘有两个磁头,或面。对于他们磁头0是正面,磁头1是反面。所以我们从磁头0开始读

如果该值大于2,软盘控制器会因为扇区不存在而产生一个异常。由于没有错误控制,CUP会产生另外一个错误异常,这最终导致一个三重错误。

DL = 驱动器号 (对于硬盘第7位为1)

ES:BX = 保存读到扇区的缓冲区

什么是驱动器号呢?它是一个代表驱动器是数字。驱动器号0总用于软驱。驱动器号1常由于5-1/4" 软盘驱动器。

因为我们的代码在软盘上,我们要从软驱读数据,所以要读的驱动器号为0

ES:BX 存储读取目标地址的段:偏移,注意,基地址表示起始地址。

都知道了,我们来读一个扇区。

读取并执行一个扇区

从磁盘读一个扇区,首先复位驱动器,就像刚刚见到的那样:

.Reset:

        mov            ah, 0                                 ; reset floppy disk function

        mov            dl, 0                                 ; drive 0 is floppy drive

        int            0x13                                  ;call BIOS

        jc             .Reset                                ; If Carry Flag (CF) is set, therewas an error. Try resetting again

 

        mov            ax, 0x1000                            ; we are going to read sector to intoaddress 0x1000:0

        mov            es, ax

        xor            bx, bx

 

.Read:

        mov            ah, 0x02                              ; function 2

        mov            al, 1                                 ; read 1 sector

        mov            ch, 1                                 ; we are reading the second sectorpast us, so its still on track 1

        mov            cl, 2                                 ; sector to read (The second sector)

        mov            dh, 0                                 ; head number

        mov            dl, 0                                 ; drive number. Remember Drive 0 isfloppy drive.

        int            0x13                                  ; call BIOS - Read thesector

        jc             .Read                                 ; Error, so try again

 

        jmp            0x1000:0x0                            ; jump to execute the sector!

注意:如果在读取扇区时发生了错误,并且你还要跳到那里去执行。无论读取是否成功,CPU都会执行指令。这往往意味着CPU要么执行一些非法的或是未知的指令,超出内存之外,这些都会导致三重错误。

上面的代码读取并执行一原始扇区,这对我们来说没有意义。首先,现在我们对PartCopy的配置只能复制512字节数据, 那么我们在哪里以及怎么创建一个原始扇区呢?

另外,我们也不能通过“文件名”来代表这个扇区,因为它不存在,它只是一个原始扇区。

最后,我们现在的引导加载器设置成了FAT12文件系统。Windows会试图从扇区2和扇区3读取一张表(文件分配表File Allocation Tables)。但是对于一个原始扇区,这张表是不存在的,这时Windows会得到一个垃圾值(如果这是那张表的话)。结果呢,当我们使用Windows读软盘时,会发现文件或文件夹有一个损坏的文件名和巨大的尺寸(你将在1.44MB的软盘上有一个2.5GB的文件吗?我见过J )。

当然,我们也需要这样读取一个扇区。在我们读取之前,我们要确定文件在磁盘上起始扇区,扇区数、基地址等等。这是从磁盘上读取文件的基础。

我们接下来看看这个。

FAT12文件系统导航

OEM参数块 详细

在前面的文章中我们在代码里重复了一大段难看的表,是什么呢?

bpbBytesPerSector:    DW 512

bpbSectorsPerCluster: DB1

bpbReservedSectors:    DW1

bpbNumberOfFATs:           DB 2

bpbRootEntries:            DW 224

bpbTotalSectors:           DW 2880

bpbMedia:                  DB 0xF0

bpbSectorsPerFAT:          DW 9

bpbSectorsPerTrack:    DW18

bpbHeadsPerCylinder:   DW2

bpbHiddenSectors:          DD 0

bpbTotalSectorsBig:    DD 0

bsDriveNumber:         DB 0

bsUnused:                  DB 0

bsExtBootSignature:    DB0x29

bsSerialNumber:         DD 0xa0a1a2a3

bsVolumeLabel:         DB "MOS FLOPPY "

bsFileSystem:         DB "FAT12   "

大多数很简单,让我们详细发现一下:

bpbBytesPerSector:    DW 512

bpbSectorsPerCluster: DB1

bpbBytesPerSector 指示每扇区的字节数。必须是2的幂。对于通常的软盘,它是512字节。

bpbSectorsPerCluster 指示每一有多少个扇,这里我们希望每簇有扇。

bpbReservedSectors:    DW1

bpbNumberOfFATs:           DB 2

保留扇是指不包括在FAT12中是扇区数,比如,某个扇区不包含再根目录里。这里,保存有引导加载器的引导扇不包括在根目录中,所以bpbReservedSectors 1

这也表示保留是扇区(我们的引导加载器)不包含在文件分配表中。

bpbNumberOfFATs 表示文件分配表的数目。FAT12文件系统总有两个FAT

通常,你需要创建文件分配表,但是使用VFD我们可使用Windows/VFD 通过格式化来创建这张表。

注意:在创建、删除文件或文件夹时Windows/VFD也会修改这些表。

bpbRootEntries:            DW 224

bpbTotalSectors:           DW 2880

对于软盘,在根目录中最多有224个文件夹。同样,记住,一张软盘有2,880个扇区。

bpbMedia:                  DB 0xF0

bpbSectorsPerFAT:          DW 9

媒体描述字节(bpbMedia)包括磁盘的一些信息。这是该字节的位模式:

  • Bits 0: Sides/Heads = 0 表示单面,1表示双面
  • Bits 1: Size = 0 表示每FAT占用9个扇区,1表示占用8
  • Bits 2: Density = 0 表示有80个磁道,1表示有40个磁道。
  • Bits 3: Type = 0表示是固定磁盘(如硬盘),1表示可移除(如软盘)
  • Bits 4 to 7 不使用,总是1.

0xF0 = 11110000(二进制)。这表示:这是一个单面的,每FAT9扇区,80个磁道,可移除的磁盘。看看bpbSectorsPerFAT 我们会发现它确实是9

bpbSectorsPerTrack:    DW18

bpbHeadsPerCylinder:   DW2

前一章中说:每磁道18个扇区

bpbHeadsPerCylinder 表示每柱面有两个磁头

bpbHiddenSectors:          DD 0

这表示物理磁盘或卷开始之前有多少个扇。

bpbTotalSectorsBig:    DD 0

bsDriveNumber:         DB 0

记得软盘驱动器号是0吗?

bsUnused:                  DB 0

bsExtBootSignature:    DB0x29

引导标志表示BIOS参数块(BIOS ParameterBlock (OEM))的版本,它的值可以是:

  • 0x28 0x29 表示这是 MS/PC-DOS version 4.0 BIOS参数块 (BPB)

我们用0x29,即我们使用的版本。

bsSerialNumber:         DD 0xa0a1a2a3

bsVolumeLabel:         DB "MOS FLOPPY "

bsFileSystem:         DB "FAT12   "

序列号被格式化它的工具赋值,不同的软盘有不同的序列号,没有哪两序列号是相同的。

Microsoft, PC, DR-DOS 产生的序列号是基于时间和日期的:

Low 16 bits = ((seconds + month) << 8) +(hundredths + day_of_month)

 

High 16 bits = (hours << 8) + minutes + year

因为序列号会被重写,我们可以放置任何我们想要的值,这没有关系。

卷标,是磁盘的标签,有些操作系统在这里显示它的名字。注意:这个字符串必须11字节,不能长,也不能短

文件系统字符串具有相同的目的,没什么特别。注意:这个字符串必须8字节,不能长,也不能短

演示

Wow,好多内容, huh? 下面是我为这一章开发的引导加载器,它把所有的东西都放在了一起。

请注意:这个演示不会向它看起来那么工作。它仅仅用于演示目的,并且在现阶段,它不可构建,我计划在本系列的下一次修订中使其成为一个可构建的程序

;*********************************************

;       Boot1.asm

;              - ASimple Bootloader

;

;       OperatingSystems Development Tutorial

;*********************************************

 

bits    16                                                    ; 我们处在16位模式

 

org            0x7c00                                        ; 我们已被BIOS加载到了0x7C00

 

start:          jmploader                                   ; 跳过 OEM块

;*************************************************;

;       OEM 参数块 / BIOS 参数块

;*************************************************;

 

TIMES 0Bh-$+start DB 0

 

bpbBytesPerSector:    DW 512

bpbSectorsPerCluster: DB1

bpbReservedSectors:    DW1

bpbNumberOfFATs:       DB2

bpbRootEntries:        DW224

bpbTotalSectors:       DW2880

bpbMedia:              DB 0xF0

bpbSectorsPerFAT:      DW9

bpbSectorsPerTrack:    DW18

bpbHeadsPerCylinder:   DW2

bpbHiddenSectors:      DD 0

bpbTotalSectorsBig:    DD 0

bsDriveNumber:         DB 0

bsUnused:              DB 0

bsExtBootSignature:    DB0x29

bsSerialNumber:         DD 0xa0a1a2a3

bsVolumeLabel:         DB "MOS FLOPPY "

bsFileSystem:         DB "FAT12   "

 

;***************************************

;       打印字符串

;       DS=>SI: 0终止的字符串

;***************************************

 

Print:

                       lodsb                                 ; 从SI加载下一个字符到AL

                       or                     al, al         ; AL=0?

                       jz                     PrintDone      ; 是,0终止,跳出

                       mov                    ah,     0eh    ; 不是,打印字符

                       int                    10h

                       jmp                    Print          ; 重复,直到到达结尾

PrintDone:

                       ret                                   ; 完成返回

 

;*************************************************;

;       引导加载器入口点

;*************************************************;

 

loader:

 

.Reset:

        mov            ah, 0                 ; 重设软盘驱动器

        mov            dl, 0                 ;drive 0 是软盘驱动器

        int            0x13                  ; BIOS调用

        jc             .Reset                ; 如果进位标志 (CF)为1,则有错误发生,再试一次

 

        mov            ax, 0x1000            ; 我们准备将扇区读入到地址0x1000:0处

        mov            es, ax

        xor            bx, bx

 

        mov            ah, 0x02                              ; 读扇区

        mov            al, 1                                 ; 读1个扇区

        mov            ch, 1                                 ; 我们读第2个扇区,在1磁道

        mov            cl, 2                                 ; 要读的扇区 (第2扇区)

        mov            dh, 0                                 ; 磁头号

        mov            dl, 0                                 ; 驱动器号,0是软驱

        int            0x13                                  ; 调用BIOS -读扇区

       

 

        jmp            0x1000:0x0                            ; 跳转以执行扇区!

 

 

times 510 - ($-$$) db 0                                      ;我们需要512字节,用0填充

 

dw 0xAA55                                                    ; 引导标志

 

;第1扇区结束,第2扇区开始---------------------------------

 

 

org 0x1000                                      ;这个扇区会被引导加载器加载到0x1000:0

 

cli                                             ; 仅仅使系统停机

hlt

总结

我们很详细的了解了如何读取磁盘,及BIOS参数块(BPB)的内容。我们甚至开发了一个将他们组合起来的例子。

我们也了解了汇编语言的不同环,并知道操作系统在环0,这不同于大多数程序,这运行我们执行一些应用程序不能执行的特权指令。

现在,我们了解了所有查找和加载我们的第二段加载器的全部知识!我们会在下一章中,学习FAT12的全部,并加载我们的第二段加载器!我等不及了!J下次见。

原创粉丝点击