这次的boot.s和上一篇不同,

boot程序的主要功能是把软盘或映像文件中的head内核代码加载到内存某个指定位置处,并在设置好临时GDT表等信息后,把处理器设置成运行在 保护模式下,然后跳转到head代码处去运行内核代码。实际上,boot.s程序会首先利用ROM BIOS中断int 0x13把软盘中的head代码读入到内存0x10000(64KB)位置开始处,然后再把这段head代码移动到内存0开始处。最后设置控制寄存器 CR0中的开启保护运行模式标志,并跳转到内存0处开始执行head代码。boot程序代码在内存中移动head代码的示意图如图4-40所示。

 
图4-40  内核示例代码在物理内
存中的移动和分布情况

代码:

******************************************************************************************************************************************

! boot.s 程序





02 ! 首先利用BIOS中断把内核代码(head代码)加载到内存0x10000处,然后移动到内存0处。





03 ! 最后进入保护模式,并跳转到内存0(head代码)开始处继续运行。





04 BOOTSEG = 0x07c0   ! 引导扇区(本程序)被BIOS加载到内存0x7c00处。





05 SYSSEG  = 0x1000  ! 内核(head)先加载到0x10000处,然后移动到0x0处。





06 SYSLEN  = 17 ! 内核占用的最大磁盘扇区数。





07 entry start





08 start:





09jmpi go,#BOOTSEG   ! 段间跳转至0x7c0:go处。当本程序刚运行时所有段寄存器值





10 go:  mov  ax,cs  ! 均为0。该跳转语句会把CS寄存器加载为0x7c0(原为0)。





11mov  ds,ax   ! 让DS和SS都指向0x7c0段。





12mov  ss,ax





13mov  sp,#0x400  ! 设置临时栈指针。其值需大于程序末端并有一定空间即可。





14





15 ! 加载内核代码到内存0x10000开始处。





16 load_system:





17mov  dx,#0x0000  ! 利用BIOS中断int 0x13功能2从启动盘读取head代码。





18mov  cx,#0x0002 ! DH - 磁头号;DL - 驱动器号;CH - 10位磁道号低8位;





19mov  ax,#SYSSEG ! CL - 位7、6是磁道号高2位,位5~0起始扇区号(从1计)。





20mov  es,ax ! ES:BX - 读入缓冲区位置(0x1000:0x0000)。





21xor  bx,bx   ! AH - 读扇区功能号;AL - 需读的扇区数(17)。





22mov  ax,#0x200+SYSLEN





23int  0x13





24jnc  ok_load  ! 若没有发生错误则跳转继续运行,否则死循环。





25 die: jmp  die





26





27 ! 把内核代码移动到内存0开始处。共移动8KB(内核长度不超过8KB)。





28 ok_load:





29cli  ! 关中断。





30mov  ax, #SYSSEG  ! 移动开始位置DS:SI = 0x1000:0;目的位置ES:DI=0:0。





31mov  ds, ax





32xor  ax, ax





33mov  es, ax





34mov  cx, #0x1000 ! 设置共移动4K次,每次移动一个字(word)。





35sub  si,si





36sub  di,di





37rep  movw  ! 执行重复移动指令。





38 ! 加载IDT和GDT基地址寄存器IDTR和GDTR。





39mov  ax, #BOOTSEG





40mov  ds, ax ! 让DS重新指向0x7c0段。





41lidt idt_48   ! 加载IDTR。6字节操作数:2字节表长度,4字节线性基地址。





42lgdt gdt_48 ! 加载GDTR。6字节操作数:2字节表长度,4字节线性基地址。





43





44 ! 设置控制寄存器CR0(即机器状态字),进入保护模式。段





选择符值8对应GDT表中第2个段描述符。





45mov  ax,#0x0001 ! 在CR0中设置保护模式标志PE(位0)。





46lmsw ax  ! 然后跳转至段选择符值指定的段中,偏移0处。





47jmpi 0,8   ! 注意此时段值已是段选择符。该段的线性基地址是0。





48





49 ! 下面是全局描述符表GDT的内容。其中包含3个段描述符。第1





个不用,另2个是代码和数据段描述符。





50 gdt: .word0,0,0,0 ! 段描述符0,不用。每个描述符项占8字节。





51





52.word0x07FF  ! 段描述符1。8Mb - 段限长值=2047 (2048*4096=8MB)。





53.word0x0000   ! 段基地址=0x00000。





54.word0x9A00   ! 是代码段,可读/执行。





55.word0x00C0 ! 段属性颗粒度=4KB,80386。





56





57.word0x07FF   ! 段描述符2。8Mb - 段限长值=2047 (2048*4096=8MB)。





58.word0x0000 ! 段基地址=0x00000。





59.word0x9200  ! 是数据段,可读写。





60.word0x00C0  ! 段属性颗粒度=4KB,80386。





61 ! 下面分别是LIDT和LGDT指令的6字节操作数。





62 idt_48: .word0 ! IDT表长度是0。





63.word0,0 ! IDT表的线性基地址也是0。





64 gdt_48: .word0x7ff   ! GDT表长度是2KB,可容纳256个描述符项。





65.word0x7c00+gdt,0   ! GDT表的线性基地址在0x7c0段的偏移gdt处。





66 .org 510





67.word0xAA55  ! 引导扇区有效标志。必须处于引导扇区最后2字节处。





************************************************************************************************************************************】





下面是详解:





04---05不解是,上一篇文章解释的详细,不赘述





06 SYSLEN  = 17 ! 内核占用的最大磁盘扇区数。





SYSLEN就是标号,,一个变量。关键是17的来由,程序上的解释是内核占用最大扇区数,在这个等量级内核的大小是8K





真实的不到8K,扇区数,一个扇区的大小是512字节,那么17个应该是8.5K。这个我没有说是话我没有实验。但原则上16应该是



对的,有兄弟试了试16.没问题。





07---14是段寄存器等的设置,参看上篇解析。





5 ! 加载内核代码到内存0x10000开始处。



其中12---13



mov ss,ax



16 mov sp,#0x400 ! arbitra ry value >>512



boot.s是用as86语言编制的,编译成功后,占有512bytes,存放在软盘的第1个扇区中即可



别骂我我也不知道神linus咋就知道编译后512字节,我想到一种流氓办法,随便设个值,编译一次,看大小,确定后改源程序



再编译一次,兄弟水平低,高手指教。。。。。



16 load_system:





17mov  dx,#0x0000  ! 利用BIOS中断int 0x13功能2从启动盘读取head代码。





18mov  cx,#0x0002 ! DH - 磁头号;DL - 驱动器号;CH - 10位磁道号低8位;





19mov  ax,#SYSSEG ! CL - 位7、6是磁道号高2位,位5~0起始扇区号(从1计)。





20mov  es,ax ! ES:BX - 读入缓冲区位置(0x1000:0x0000)。





21xor  bx,bx   ! AH - 读扇区功能号;AL - 需读的扇区数(17)。





22mov  ax,#0x200+SYSLEN





23int  0x13





24jnc  ok_load  ! 若没有发生错误则跳转继续运行,否则死循环。





25 die: jmp  die





这段程序功能加载内核到内存0x10000处(这个值的解释参看上篇)





17---22其实都是对BIOS中断的设置,,BIOS中断13的定义:





************************************************************************************************************





INT 13h AH=02h: Read Sectors From Drive

Parameters:

AH02h
ALSectors To Read Count
CHTrack
CLSector
DHHead
DLDrive
ES:BXBuffer Address Pointer

Results:

CFSet On Error, Clear If No Error
AHReturn Code
ALActual Sectors Read Count








************************************************************************************************************************







这样就能理解啦,AH中是中断的子功能号,AL定义读得扇区数,DL,驱动器,DH磁头,CL扇区,CH磁道。







BX地址指针缓存,那么:







mov dx,#0x0000







从0驱动区0磁头开始,







mov cx,#0x0002







从第二个扇区开始读:







mov ax,#SYSSEG







 mov es,ax







段寄存器即段的基地值ES  为SYSSEG







xor bx,bx







ES:BX清空。







mov ax,#0x200+SYSLEN







这里AH=02H,,AL=SYSLEN







然后就可以调用中断啦:







int 0x13







1这里的疑问:为什么从啥区2开始,扇区1的作用



******************************************************************************************************************************************







MBR(Master Boot Record),中文意为主引导记录














硬盘






的0磁道














第一个扇区称为MBR,它的大小是512字节,而这个区域可以分为两个部分。第一部分为pre-boot区(预启动区),占446字节;第二部分是







Partition







boot.s直接将第二扇区(1扇区等于512字节)开始的17个扇区调入内存0x10000







table区(分区表),占64个字节,该区相当于一个小程序,作用是判断哪个分区被标记为活动分区,然后去读取那个分区的启动区,并运行该区中的代码。







他是不属于任何一个操作系统






,也不能用操作系统提供的磁盘操作命令来读取它。但我们可以用ROM-BIOS






中提供的INT13H的2号功能来读出该扇区的内容,也可用软件工具Norton8.0中的DISKEDIT.EXE来读取。







用INT13H的读磁盘扇区功能的调用参数如下:







入口参数:AH=2 (指定功能号)







AL=要读取的扇区数







DL=磁盘号(0、1-软盘;80、81-硬盘)







DH=磁头














CL高2位+CH=柱面号







CL低6位=扇区号







CS:BX=存放读取数据的内存缓冲地址







出口参数:CS:BX=读取数据存放地址







错误信息:如果出错CF=1 AH=错误代码







用DEBUG读取位于硬盘0柱面、0磁头、1扇区的操作如下:







A>DEBUG







-A 100







XXXX:XXXX MOV AX,0201 (用功能号2读1个扇区)







XXXX:XXXX MOV BX,1000 (把读出的数据放入缓冲区的地址为CS:1000)







XXXX:XXXX MOV CX,0001 (读0柱面,1扇区)







XXXX:XXXX MOV DX,0080 (指定第一物理盘的0磁头)







XXXX:XXXX INT 13







XXXX:XXXX INT 3







XXXX:XXXX (按回车键)







-G=100 (执行以上程序段)







-D 1000 11FF (显示512字节的MBR内容)







************************************************************************************************************************************************



boot.s直接将第二扇区(1扇区等于512字节)开始的17个扇区调入内存0x10000,并跳转到那里以保护模式开始执行。



**********************************************************************************************************************************************



下面是一段程序是把已经载入的内核从0x10000移动到0处:



****************************************************************************



! 把内核代码移动到内存0开始处。共移动8KB(内核长度不超过8KB)。



28 ok_load:



29cli  ! 关中断。



30mov  ax, #SYSSEG  ! 移动开始位置DS:SI = 0x1000:0;目的位置ES:DI=0:0。



31mov  ds, ax



32xor  ax, ax



33mov  es, ax



34mov  cx, #0x1000 ! 设置共移动4K次,每次移动一个字(word)。



35sub  si,si



36sub  di,di



37rep  movw  ! 执行重复移动指令。



********************************************************************************



29cli   关中断



标志寄存器EFLAGS的中断允许标志IF(Interrupt enable Flag)能够禁止为处理器INTR引脚上收到的可屏蔽硬件中断提供服务。



当IF=0时,处理器禁止发送到INTR引脚的中断;当IF=1时,则发送到INTR引脚的中断信号会被处理器处理。



IF标志并不影响发送到NMI引脚的非屏蔽中断,也不影响处理器产生的异常。如同EFLAGS中的其他标志一样,



处理器在响应硬件复位操作时会清除IF标志(IF=0)。



IF标志可以使用指令STI和CLI来设置或清除。只有当程序的CPL小于或等于IOPL时才可执行这两条指令,否则将引发一般保护性异常。



IF标志也会受以下操作的影响:



PUSHF指令会把EFLAGS内容存入堆栈中,并且可以在那里被修改。而POPF指令可用于把已被修改过的标志内容放入EFLAGS寄存器中。



任务切换、POPF和IRET指令会加载EFLAGS寄存器。因此,它们可用来修改IF标志。



当通过中断门处理一个中断时,IF标志会被自动清除(复位),从而会禁止可屏蔽硬件中断。



但如果是通过陷阱门来处理一个中断,则IF标志不会被复位。

30mov  ax, #SYSSEG  ! 移动开始位置DS:SI = 0x1000:0;目的位置ES:DI=0:0。

31mov  ds, ax

32xor  ax, ax

33mov  es, ax

34mov  cx, #0x1000 ! 设置共移动4K次,每次移动一个字(word)。

35sub  si,si

36sub  di,di

37rep  movw  ! 执行重复移动指令。

这里重要认识的是寄存器的作用,SI源变址寄存器,DI目的变址寄存器一般与数据段寄存器DS联用,用来确定数据段某一存储单元的地址

这两个变址寄存器有自动增量和自动减量的功能,可以很方便地用于变址。在串处理指令中,当SI和DI作为隐含的源变址和目的变址寄存器

时,SI和DS联用,DI和附加段寄存器ES联用,分别达到在数据段和附加段中寻址的目的。

35 36变址寄存器SI DI清0, 37重复移动

在as86和at&t中这样的指令是正确的,但是区别是as86是分两行使用的,在at&t中是在一行,

***********************************************************************************************************

 40         mov ax,#BOOTSEG

41 mov ds,ax !让DS重新指向0x7c0段。

42 lidt idt_48 !加载IDTR。6字节操作数:2字节表长度,4字节线性基地址。

43 lgdt gdt_48 !加载GDTR。6字节操作数:2字节表长度,4字节线性基地址。

40,41行将现在运行段转移到BOOTSEG段,42,43加载寄存器,使进入保护模式,
*******************************************************************************************************
!设置控制寄存器CR0(即机器状态字),进入保护模式。段选择符值8对应GDT表中第2个段描述符。
46 mov ax,#0x0001 !在CR0中设置保护模式标志PE(位0)。
47 lmsw ax !然后跳转至段选择符值之指定的段中,偏移0处
48 jmpi 0,8 !注意此时段值已是段选择符,该段的线性基地址是0.。
**********************************************************************************************************
上面的代码。是开启段保护,把控制寄存器CR0的PE位置1。

LMSW - 加载机器状态字

操作码

指令

说明

0F 01 /6

LMSW r/m16

r/m16 加载到 CR0 的机器状态字

说明

将源操作数加载到机器状态字,即寄存器 CR0 的位 0 到 15。源操作数可以是 16 位通用寄存器或内存位置。只有源操作数的低 4 位(也就是 PE、MP、EM 及 TS 标志)会加载到 CR0。CR0 的 PG、CD、NW、AM、WP、NE 及 ET 标志不受影响。操作数大小属性不影响此指令。

如果源操作数的 PE 标志(位 0)设置为 1,则指令导致处理器切换到保护模式。处于保护模式时,不能使用 LMSW 指令清除 PE 标志,并强制切换回实地址模式。

提供的 LMSW 指令用于操作系统软件;不应该在应用程序中使用它。在保护或虚 8086 模式中,此指令只能在 CPL 0 级别执行。

提供此指令是为了同 Intel(R) 286 处理器保持兼容;对于要在奔腾(R) 4、P6 系列、奔腾、Intel486 及 Intel386 处理器上运行的程序与过程,应该使用 MOV(控制寄存器)指令加载整个 CR0 寄存器。MOV CR0 指令可用于设置或清除 CR0 中的 PE 标志,从而让过程或程序在保护与实地址模式之间转换。

此指令是序列化指令。

操作

CR0[0:3] SRC[0:3];

影响的标志

无。

保护模式异常

#GP(0) - 如果当前特权级别不是 0。如果内存操作数有效地址超出 CS、DS、ES、FS 或 GS 段限制。如果 DS、ES、FS、或 GS 寄存器用于访问内存,并且它包含空的段选择器。

#SS(0) - 如果内存操作数有效地址超出 SS 段限制。

#PF(错误代码) - 如果发生页错误。

实地址模式异常

#GP - 如果内存操作数有效地址超出 CS、DS、ES、FS 或 GS 段限制。

虚 8086 模式异常

#GP(0) - 如果当前特权级别不是 0。如果内存操作数有效地址超出 CS、DS、ES、FS 或 GS 段限制。

#SS(0) - 如果内存操作数有效地址超出 SS 段限制。

#PF(错误代码) - 如果发生页错误。

*****************************************************************************************************************************

jmpi与ljmp都是段间跳转指令
jmpi的格式是: jmpi 段内偏移,段选择子
ljmp的格式是: ljmp 段选择子,段内偏移
jmpi 是linux下intel 80x86的汇编指令,而 ljmp 是linux下AT$T格式的汇编指令
 ***********************************************************************************************************************
!下面是全局描述符表GDT的内容。其中包含3个段描述符,第一个不用,另2个是代码和数据段描述符。
 51 gdt:    .word 0,0,0,0                      !段描述符0,不用。每个描述符项占用8字节
 52
 53         .word 0x07FF                       !段描述符1。8MB - 段限长值=2047 移动指令。(2048*4096=8MB)
 54         .word 0x0000                       !段基地址=0x00000。
 55         .word 0x9A00                       !是代码段,可读/执行。
 56         .word 0x00C0                       !段属性颗粒度=4KB,80386.
 57         .word 0x07FF                       !段描述符2。8MB - 段限长值=2047 (2048*4096=8MB)。
 58         .word 0x0000                       !段基地址=0x00000。
 59         .word 0x9200                       !是数据段,可读写。
 60         .word 0x00C0                       !段属性颗粒度=4KB,80386。
********************************************************************************************************************

4.3.4  段描述符

前面我们已经说明了使用段选择符来定位描述符表中的一个描述符。段描述符是GDT和LDT表中的一个数据结构项,用于向处理器提供有关一个段的位置 和大小信息以及访问控制的状态信息。每个段描述符的长度是8字节,含有3个主要字段:段基地址、段限长和段属性。段描述符通常由编译器、链接器、加载器或 者操作系统来创建,但绝不是应用程序。图4-13给出了所有类型段描述符的一般格式。

 
(点击查看大图)图4-13  段描述符通用格式

一个段描述符中各字段和标志的含义如下:

(1)段限长字段Limit(Segment limit field):用于指定段的长度。处理器会把段描述符中两个段限长字段组合成一个20位的值,并根据颗粒度标志G来指定段限长Limit值的实际含义。如 果G=0,则段长度Limit范围可从1B~1MB,单位是1B;如果G=1,则段长度Limit范围可从4KB~4GB,单位是4KB。

根据段类型中的段扩展方向标志E,处理器以两种不同方式使用段限长Limit。对于向上扩展的段(简称上扩段),逻辑地址中的偏移值范围可以从0到 段限长值Limit。大于段限长Limit的偏移值将产生一般保护性异常。对于向下扩展的段(简称下扩段),段限长Limit的含义相反。根据默认栈指针 大小标志B的设置,偏移值范围可从段限长Limit到0xFFFFFFFF或0xFFFF。而小于段限长Limit的偏移值将产生一般保护性异常。对于下 扩段,减小段限长字段中的值会在该段地址空间底部分配新的内存,而不是在顶部分配。80x86的栈总是向下扩展的,因此这种实现方式很适合扩展堆栈。

(2)基地址字段Base(Base address field):该字段定义在4GB线性地址空间中一个段字节0所处的位置。处理器会把3个分立的基地址字段组合形成一个32位的值。段基地址应该对齐16 字节边界。虽然这不是要求的,但通过把程序的代码和数据段对齐在16字节边界上,可以让程序具有最佳性能。

(3)段类型字段TYPE(Type field):用行指定段或门(Gate)的类型、说明段的访问种类以及段的扩展方向。该字段的解释依赖于描述符类型标志S指明是一个应用(代码或数据) 描述符还是一个系统描述符。TYPE字段的编码对代码、数据或系统描述符都不同,如图4-14所示。

 
(点击查看大图)图4-14  代码段、数据段和系统段描述符格式

(4)描述符类型标志S(Descriptor type flag):用于指明一个段描述符是系统段描述符(当S=0)还是代码或数据段描述符(当S=1)。

(5)描述符特权级字段DPL(Descriptor privilege level):用于指明描述符的特权级。特权级范围从0到3。0级特权级最高,3级最低。DPL用于控制对段的访问。

(6)段存在标志P(Segment present):用于指出一个段是在内存中(P=1)还是不在内存中(P=0)。当一个段描述符的P标志为0时,那么把指向这个段描述符的选择符加载进 段寄存器将导致产生一个段不存在异常。内存管理软件可以使用这个标志来控制在某一给定时间实际需要把那个段加载进内存中。这个功能为虚拟存储提供了除分页 机制以外的控制。图4-15给出了当P=0时的段描述符格式。当P标志为0时,操作系统可以自由使用格式中标注为可用(Available)的字段位置来 保存自己的数据,例如有关不存在段实际在什么地方的信息。

 
(点击查看大图)图4-15  当存在位P=0时的段描述符格式

(7)D/B(默认操作大小/默认栈指针大小和/或上界限)标志(Default operation size/default stack pointer size and/or upper bound):根据段描述符描述的是一个可执行代码段、下扩数据段还是一个堆栈段,这个标志具有不同的功能。(对于32位代码和数据段,这个标志应该总是 设置为1;对于16位代码和数据段,这个标志被设置为0。)

可执行代码段。此时这个标志称为D标志并用于指出该段中的指令引用有效地址和操作数的默认长度。如果该标志置位,则默认值是32位地址和32位或8 位的操作数;如果该标志为0,则默认值是16位地址和16位或8位的操作数。指令前缀0x66可以用来选择非默认值的操作数大小;前缀0x67可用来选择 非默认值的地址大小。

栈段(由SS寄存器指向的数据段)。此时该标志称为B(Big)标志,用于指明隐含堆栈操作(如PUSH、POP或CALL)时的栈指针大小。如果 该标志置位,则使用32位栈指针并存放在ESP寄存器中;如果该标志为0,则使用16位栈指针并存放在SP寄存器中。如果堆栈段被设置成一个下扩数据段, 这个B标志也同时指定了堆栈段的上界限。

下扩数据段。此时该标志称为B标志,用于指明堆栈段的上界限。如果设置了该标志,则堆栈段的上界限是0xFFFFFFFF(4GB);如果没有设置该标志,则堆栈段的上界限是0xFFFF(64KB)。

(8)颗粒度标志G(Granularity):该字段用于确定段限长字段Limit值的单位。如果颗粒度标志为0,则段限长值的单位是字节;如果 设置了颗粒度标志,则段限长值使用4KB单位。(这个标志不影响段基地址的颗粒度,基地址的颗粒度总是字节单位。)若设置了G标志,那么当使用段限长来检 查偏移值时,并不会去检查偏移值的12位最低有效位。例如,当G=1时,段限长为0表明有效偏移值为0~4095。

(9)可用和保留位(Available and reserved bits):段描述符第2个双字的位20可供系统软件使用;位21是保留位并应该总是设置为0。
*************************************************************************************************************************************************
这就是很全的解释。。。。













 

Logo

更多推荐