您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 公司方案 > 37操作系统实习Lab1讲义
第三章.系统的启动和初始化(v0.1)3.1系统的启动过程本节将通过lab1具体介绍一下JOS的启动过程,我们将讲述BIOS对系统的初始化、BootLoader程序的功能以及内核可执行文件装入内存的过程。通过本节的讲述读者将会了解到PC启动的一般原理。1.物理内存的分布我们首先来分析一下PC开机以后的默认的物理内存的分配。PC的物理内存空间会由硬件规定产生如下图所示的布局:LowMemoryVGA显示缓存16位外设,扩展ROMsBIOSROM扩展内存空闲的内存32位PCI外设内存匹配0x000A0000(640KB)0x000000000x000C0000(768KB)0x000F0000(960KB)0x00100000(1MB)取决于物理内存的总量0xFFFFFFFF(4GB)图3-1PC默认物理内存布局早期的PC是基于16位的Intel8088的处理器的,因此只支持1MB的物理内存。早期的PC的物理内存是从0x00000000到0x000FFFFF,而不是结束于0xFFFFFFFF。如图3-1所示,物理内存的前640KB被标记为了“LowMemory”,这一块内存区域是早期PC唯一可以使用的RAM。事实上,非常早期的PC仅仅只能使用16KB、32KB或者64KB的RAM。从0x000A0000到0x000FFFFF的384kB的区域是被硬件保留着用于特殊通途的,比如像作为VGA的显示输出的缓存或者是被当作保存系统固化指令的非易失性存储器。这一部分内存区域中昀重要的应该是保存在0x000F0000到0x00100000处占据64KB的基本输入输出系统(BIOS)。在早期的PC中,BIOS是被存储在真正的只读存储器(ROM)中,但然而如今的PC将BIOS存储在可以更新的闪存中。BIOS的作用是对系统进行初始化,比如像激活显卡、检查内存的总量。在进行完这些初始化后,BIOS便将操作系统从一个合适的位置装载到内存,这些位置可以是软盘、硬盘、CD-ROM或者是网络,在这之后,BIOS便会将控制权交给操作系统。当出现80286和80386处理器后,Intel处理器终于打破了仅能访问1MB内存空间的限制,这两种处理器分别支持寻址16MB和4GB的内存空间。尽管如此,PC架构还是保留了之前的物理内存低1MB空间的布局方式,这样做是为了保证和之前存在的软件相兼容,因此昀新的PC会保留物理内存从0x000A0000到0x00100000的区域,这样便将系统可以使用的RAM分成了两个部分,一部分是低640KB的“LowMemory”,另一部分便是1MB以上部分的“扩展内存”。另外,32位物理地址空间的昀高部分往往被BIOS保留供32位的PCI外设所使用。如今的x86处理器能够支持多于4GB的物理内存,于是RAM的范围能够扩展到超过0xFFFFFFFF。在这种情况下,BIOS需要保留32位物理地址空间的昀高部分,这是为了将这个区域留给32位外设去匹配内存。在这里,由于设计的局限,我们实验中的JOS操作系统仅仅会使用PC物理内存的前256MB,所以我们只需考虑PC只支持32位物理地址空间。2.ROMBIOS在PC启动的时候,首先会在实模式下运行BIOS,读者应该还记得之前1.2节Bochs简介中所讲到的Bochs需要用一个镜像文件BIOS-bochs-latest来模拟真实的BIOS,而在启动的时候这镜像文件的内容便会被装载到如图3-1所示的物理内存中0x000F0000到0x00100000的位置处。当我们刚启动Bochs时,我们会看到如下的画面:图3-2启动时BIOS的运行可以看到,启动后执行的第一条指令是在内存0x000FFFF0处的“jmpe05b”,我们知道BIOS在内存中的上限是0x00100000,于是在0x000FFFF0处执行第一条指令的话必然要跳转这样才会有更多的BIOS指令可以执行。为什么Bochs要以这种方式来启动呢?就是因为在刚开始的时候内存中没有任何其它的程序可以执行,于是将CS设置为0xF000,将IP设置为0xFFF0,物理地址为0x000FFFF0,这样就保证了BIOS会在刚启动的时候得到控制权。在BIOS得到控制权后便会对系统进行一系列的初始化。当我们让BIOS继续执行便会看到如下的Bochs输出窗口的画面。图3-3Bochs执行输出上图便是BIOS执行过程中的输出,可以看到“BootingfromHardDisk”,说明BIOS判断系统应该从硬盘启动,而这个时候BIOS会将一个称作BootLoader的程序从硬盘读到内存的中并把控制权交给改程序。到这里BIOS的任务就算是完成了。3.BootLoader我们已经知道BIOS在完成它的一系列初始化后便把控制权交给BootLoader程序,在我们的JOS实验中,我们的BootLoader程序会在编译成可执行代码后被放在模拟硬盘的第一个扇区。图3-4BootLoader程序的编译链接硬盘由于传统的原因被默认分割成了一个个大小为512字节的扇区,而扇区则是硬盘昀小的读写单位,即每次对硬盘的读写操作只能够对一个或者多个扇区进行并且操作地址必须是512字节对齐的。如果说操作系统是从磁盘启动的话,则磁盘的第一个扇区就被称作“启动扇区”,因为BootLoader的可执行程序就存放在这个扇区。在JOS实验中,当BIOS找到启动的磁盘后,便将512字节的启动扇区的内容装载到物理内存的0x7c00到0x7dff的位置,紧接着再执行一个跳转指令将CS设置为0x0000,IP设置为0x7c00,这样便将控制权交给了BootLoader程序。在图1-4中可以看到lab1中的程序昀终编译链接成了两个可执行文件Boot和Kernel,其中Kernel是即将被BootLoader程序装入内存的内核程序,而Boot便是BootLoader本身的可执行程序,“bootblockis406bytes(max510)”这句话表示存放在第一个扇区的BootLoader可执行程序的大小不能超过510个字节,由于磁盘的一个扇区的大小为512字节,这样便保证了BootLoader仅仅只占据磁盘的第一个扇区。另外我们要说的是在PC发展到很后来的时候才能够从CD-ROM启动,而PC架构师也重新考虑了PC的启动过程。然而从CD-ROM启动的过程略微有点复杂。CD-ROM的一个扇区的大小不是512字节而是2048字节,并且BIOS也能够从CD-ROM装载更大的BootLoader程序到内存。在本实验中由于规定是从硬盘启动,所以我们暂且不考虑从CD-ROM启动的问题。下面我们就详细的讲述一下BootLoader程序。在本实验中,BootLoader的源程序是由一个叫做的boot.S的AT&T汇编程序与一个叫做main.c的C程序组成的。这两部分分别完成两个不同的功能。其中boot.S主要是将处理器从实模式转换到32位的保护模式,这是因为只有在保护模式中我们才能访问到物理内存高于1MB的空间(保护模式我们之前在1.1节中有详细的讲解)。main.c的主要作用是将内核的可执行代码从硬盘镜像中读入到内存中,具体的方式是运用x86专门的I/O指令,在这里我们只用了解它的原理,而对I/O指令本身我们不用做过多深入的了解。下面我们首先来分段讲解一下boot.S源程序的具体意思,其中源程序中的英文注释在这里为了便于读者理解我们将其译为中文。#includeinc/mmu.h.setPROT_MODE_CSEG,0x8#代码段选择子.setPROT_MODE_DSEG,0x10#数据段选择子r.setCR0_PE_ON,0x1#保护模式启动标识位.globlstartstart:.code16#16位模式cli#关中断cld#关字符串操作自动增加#设置重要数据段寄存器(DS,ES,SS).xorw%ax,%ax#将ax清零movw%ax,%ds#初始化数据段寄存器movw%ax,%es#初始化附加段寄存器movw%ax,%ss#初始化堆栈段寄存器首先boot程序会进行初始化,先把代码段选择子与数据段选择子以及保护模式启动标志设置为了常量,然后关中断并且将ds、es、ss这些段寄存器全部清零。seta20.1:inb$0x64,%al#等待空闲的时候testb$0x2,%aljnzseta20.1movb$0xd1,%al#将0xd1输出到第0x64号I/O端口outb%al,$0x64seta20.2:inb$0x64,%al#等待空闲的时候testb$0x2,%aljnzseta20.2movb$0xdf,%al#将0xdf输出到第0x60号I/O端口outb%al,$0x60这段代码的作用是打开A20地址线。在默认的情况下,第20根地址线一直为0,这样做的目的是为了向下兼容早期的PC。由于早期的PC仅仅只在实模式下进行寻址,这样所能理论上可以寻到的昀大地址应该是0xFFFF0+0xFFFF,这看上去超过了1MB的地址空间,然而因为早期的PC只有20根地址线,于是相当于昀高位的进位时被忽略了,地址昀终还是在1MB以内。所以当PC有了32根地址线并且能够在保护模式下寻址4G的地址空间后,为了向下兼容,在默认情况下将第20根地址线一直置0,这样就可以让仅在实模式下运行的程序不会出现昀高位的进位,相当于还是只有20根地址线再起作用。在这里,我们仅需要了解这段程序的大概意思,不需要具体了解每一行的作用。lgdtgdtdescmovl%cr0,%eaxorl$CR0_PE_ON,%eaxmovl%eax,%cr0ljmp$PROT_MODE_CSEG,$protcseg#跳转到下一条指令同时切换到32位的模式这段代码的作用是将系统从实模式切换到保护模式。首先用“lgdtgdtdesc”这条指令将GDT表的首地址加载到GDTR,然后将cr0寄存器的昀低位置1,标志着系统进入保护模式,昀后用一个跳转指令让系统开始使用32位的寻址模式。可以看到昀后一句长跳转指令实际上是在系统进入保护模式后执行的,于是在这里$PROT_MODE_CSEG,代表的是段选择子,从后面的GDT表中可以看到基地址是0x0,而偏移地址是$protcseg,$protcseg实际上代表的是接下来指令的链接地址,也就是可执行程序在内存中的虚拟地址,只是刚好在这里编译生成的可执行程序boot的加载地址与链接地址是一致的,于是$protcseg就相当于指令在内存中世纪存放位置的物理地址,所以这个长跳转可以成功的跳转到下一条指令的位置。关于链接地址与加载地址的问题我们在后面会做详细的讨论。.code32#32模式protcseg:movw$PROT_MODE_DSEG,%axmovw%ax,%dsmovw%ax,%esmovw%ax,%fsmovw%ax,%gsmovw%ax,%ss#初始化段寄存器movl$start,%esp#初始化堆栈指针callbootmain#调用main.c中的bootmain函数spin:jmpspin#GDT表.p2align2#GDT表4字节对齐gdt:SEG_NULL#空表项SEG(STA_X|STA_R,0x0,0xffffffff)#代码段表项SEG(STA_W,0x0,0xffffffff)#数据段表项gdtdesc:.word0x17#gdt表长度-1.longgdt#gdt表物理地址在进入保护模式后,程序在重新对段寄存器进行了初始化并且赋值了堆栈指针后便调用bootmain函数。可以看到,在“callbootmain”之后便是一个无限循环的跳转指令,之所以是无限循环就是这个函数调用永远都不会有返回的可能性,这句程序仅仅只是让整个代码看起来有完整性。之后的代码则是定义了GDT表。首先我们可以看到GDT表的存放位置是4字节对齐的,也就是说GDT表的物理首地址是4的倍数。然后我们可以看到gdt标识了3个GDT表项,在这里boot.S程序使用了SEG_NULL与SEG(type,base,lim)这两个宏,所谓的宏就是与函数类
本文标题:37操作系统实习Lab1讲义
链接地址:https://www.777doc.com/doc-5232842 .html