质量最好的鞋子:从头开始编写操作系统(6) 第5章:引导加载器3 - xiaoxiaoyaya的专栏 - ...

来源:百度文库 编辑:九乡新闻网 时间:2024/07/04 21:09:50
  从头开始编写操作系统(6) 第5章:引导加载器3 收藏 译自: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, 查找并加载 32 位 kernel, 执行 kernel, 和错误控制 。在512 字节中放下全部的这些东西,不大可能。所以单段引导加载器必须加载并执行一个16 位的内核。 因此大多数的引导加载器都是多段引导加载器。 多段引导加载器 一个多段引导加载器包括一个512 字节的引导加载器(单段引导加载器),这个加载器仅仅加载并执行另一个引导加载器——第二段引导加载器。第二段引导加载器往往是16 位的,但包含更多的代码。它能够加载并执行一个32 位的内核。 这是因为引导加载器的512 字节限制。当引导加载器加载了第二段引导加载器所需的全部扇区后,第二段引导加载器就没有了大小的限制。这样他更容易完成加载内核的工作。 我们将使用二段(2 Stage )引导加载器。 从磁盘加载扇区 因为引导加载器被限制到512 字节,不可能做太多的工作。正如前一节中所说,我们使用二段引导加载器,也就是说,引导加载器加载并执行我们的第二段程序——内核加载器。 如果你想,可以在第二段加载器中放置一个“选择操作系统”和“高级选项”的菜单 J ,做吧,我知道你想法要一个? BIOS INT 0x13 的 0 号功能 – 复位软盘驱动器 BIOS 0x13 中的用于磁盘访问。你可以使用INT 0x13 的0 号功能复位软盘驱动器。什么意思?无论软盘控制器在读什么位置,它会立即返回磁盘的第一个扇区。 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 0x13 的0x02 号功能 – 读扇区 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 ) ? 柱面是(具有相同半径的)一组磁道,为了理解它,看下图:  看看上面的图: 每个磁道被分为512 字节的扇区。在软盘上,每磁道有18 个扇区
一个柱面是一组有相同半径的磁道(图中红线)
软盘有两个磁头
共有2880 个扇区。
这对我们有什么用?柱面数表示单个磁盘的磁道数,对于软盘,它表示要读的磁道 。 每磁道18 个扇区,在软盘上有63 个磁道。 如果柱面号比63 大,软盘控制器会发送一个异常,因为扇区不存在。因为不存在错误控制代码,CPU 会产生另一个异常,最终导致一个三重错误。 CL = 扇区号(Bits 0-5). Bits 6-7 只对硬盘有效
读取的第一个扇区号。要记住:每磁道只有18 个是扇区,该值只能在0 到17 之间,否则就增大当前磁道的值,以确保扇区号设置为你需要读取的那个扇区。 如果该值大于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, there was an error. Try resetting again           mov            ax, 0x1000                            ; we are going to read sector to into address 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 sector past 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 is floppy drive.         int            0x13                                  ; call BIOS - Read the sector         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: DB 1 bpbReservedSectors:    DW 1 bpbNumberOfFATs:            DB 2 bpbRootEntries:             DW 224 bpbTotalSectors:            DW 2880 bpbMedia:                   DB 0xF0 bpbSectorsPerFAT:           DW 9 bpbSectorsPerTrack:    DW 18 bpbHeadsPerCylinder:   DW 2 bpbHiddenSectors:           DD 0 bpbTotalSectorsBig:     DD 0 bsDriveNumber:          DB 0 bsUnused:                   DB 0 bsExtBootSignature:    DB 0x29 bsSerialNumber:         DD 0xa0a1a2a3 bsVolumeLabel:          DB "MOS FLOPPY " bsFileSystem:          DB "FAT12   " 大多数很简单,让我们详细发现一下: bpbBytesPerSector:     DW 512 bpbSectorsPerCluster: DB 1 bpbBytesPerSector 指示每扇区的字节数。必须是2 的幂。对于通常的软盘,它是512 字节。 bpbSectorsPerCluster 指示每一簇 有多少个扇,这里我们希望每簇有扇。 bpbReservedSectors:    DW 1 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:    DW 18 bpbHeadsPerCylinder:   DW 2 前一章中说:每磁道18 个扇区。 bpbHeadsPerCylinder 表示每柱面有两个磁头 bpbHiddenSectors:           DD 0 这表示物理磁盘或卷开始之前有多少个扇。 bpbTotalSectorsBig:     DD 0 bsDriveNumber:          DB 0 记得软盘驱动器号是0 吗? bsUnused:                   DB 0 bsExtBootSignature:    DB 0x29 引导标志表示BIOS 参数块(BIOS Parameter Block (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 ;              - A Simple Bootloader ; ;       Operating Systems Development Tutorial ;*********************************************   bits    16                                                    ; 我们处在16位模式   org            0x7c00                                        ; 我们已被BIOS加载到了0x7C00   start:          jmp loader                                   ; 跳过 OEM块
;*************************************************; ;       OEM 参数块 / BIOS 参数块 ;*************************************************;   TIMES 0Bh-$+start DB 0   bpbBytesPerSector:     DW 512 bpbSectorsPerCluster: DB 1 bpbReservedSectors:    DW 1 bpbNumberOfFATs:       DB 2 bpbRootEntries:        DW 224 bpbTotalSectors:       DW 2880 bpbMedia:               DB 0xF0 bpbSectorsPerFAT:      DW 9 bpbSectorsPerTrack:    DW 18 bpbHeadsPerCylinder:   DW 2 bpbHiddenSectors:       DD 0 bpbTotalSectorsBig:     DD 0 bsDriveNumber:          DB 0 bsUnused:               DB 0 bsExtBootSignature:    DB 0x29 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 下次见。  本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/xiaoxiaoyaya/archive/2011/04/13/6321746.aspx
从头开始编写操作系统(6) 第5章:引导加载器3 - xiaoxiaoyaya的专栏 - ... 从头开始编写操作系统(5) 第4章:引导加载器2 - xiaoxiaoyaya的专栏 - ... 从头开始编写操作系统(4) 第3章:引导加载器 - xiaoxiaoyaya的专栏 - C... 从头开始编写操作系统(3) 第2章:基本理论 - xiaoxiaoyaya的专栏 - CS... 从头开始编写操作系统(2) 第1章:介绍 - xiaoxiaoyaya的专栏 - CSDN... 使用VB6编写COM加载项(方法一:AddinInstance) 女人的美丽从头开始 J2EE项目代码编写规范 - fbtdjs的专栏 - CSDN博客 保健从头开始 强身始于足下3 Eclipse 插件安装方法和插件加载失败解决办法 - TATA的专栏 - CSDN博客 Drupal专业开发指南 第2章 创建一个模块(1) - g089h515r806的专栏 - CSDNBlog 怎样才能拍出好的摄影作品 (1)从头开始 (原创) Photoshop从头开始 数码摄影从头开始 健康从头开始 使用c++开发excel插件(第4章编写一个完整的xll) [转载]从头开始写毛爷爷的故事 从头开始锻炼意志力(1)–选择你的目标 编写超级可读代码的15个最佳实践 - 蒋宇捷的专栏 - CSDN博客 大智慧公式编写教程 - 董昊(昊子)的专栏 - CSDN博客 Drupal专业开发指南 第2章 sdfsdf建一个模块(1) - g089h515r806的专栏 - CSDNBlog 通达信指标公式编写教程 第3节公式编写示例 通达信指标公式编写教程 第3节公式编写示例 通达信指标公式编写教程 第3节公式编写示例