从头开始编写操作系统(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, 查找并加载 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 = 扇区号(Bits0-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, 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下次见。
- 从头开始编写操作系统(6) 第5章:引导加载器3
- 从头开始编写操作系统(7) 第6章:引导加载器4
- 从头开始编写操作系统(5) 第4章:引导加载器2
- 从头开始编写操作系统(4) 第3章:引导加载器
- 从头开始编写操作系统(3) 第2章:基本理论
- 从头开始编写操作系统(1) 第0章:序章
- 从头开始编写操作系统(2) 第1章:介绍
- 从头开始编写操作系统(8) 第7章:系统结构
- 从头开始编写操作系统(9) 第8章:保护模式
- 从头开始编写操作系统(10) 第9章:开启A20
- 从头开始编写操作系统(11) 第10章:为内核做准备1
- 从头开始编写操作系统
- 【从头开始写操作系统系列】实现一个 GDT(3)
- 【从头开始写操作系统系列】实现一个-GDT(1)
- 【从头开始写操作系统系列】实现一个-GDT(2)
- 操作系统编写之引导扇区
- 开始学习编写操作系统
- 操作系统实践(1)——从引导开始
- 通配符匹配问题
- Ext htmleditor类型值 保存到数据库
- 智能算法的通俗解释(转载)
- 演绎与归纳
- 个人成长通关之路:四仁五德六读书
- 从头开始编写操作系统(6) 第5章:引导加载器3
- qvfb图形引擎
- 内核异常
- CDialogBar的具体使用过程一
- yaffs2文件系统制作
- JavaScript+ 围棋+Chrome
- CDialogBar的具体使用过程二
- json解决hibernate中级联对象延迟加载问题net.sf.json.JSONException: org....
- 启发式算法简谈