手机版 | 登陆 | 注册 | 留言 | 设首页 | 加收藏
当前位置: 网站首页 > 音响设备技术 > 文章 当前位置: 音响设备技术 > 文章

操作系统实践

时间:2009-08-16    点击: 次    来源:本站原创    作者:佚名 - 小 + 大

 

操作系统实践

码字辛苦,转载请注明出处

从年初三开始看《自己动手写操作系统》一书,到现在20天,学到了很多东西,特此记录下来。

我喜欢动手写代码,所以看《自己动手写操作系统》一书我选择了按书上的进度自己用MASM来写代码,而不是机械的将书上的代码抄一遍,其间遇到了一系列莫名其妙的问题,不过得益于此,真正理解了很多底层的东西,将汇编(年前学的)和操作系统整个理通了。

首先说一下目前为止我参考的资料和用到的一些软件:

    

参考资料:

1. 《自己动手写操作系统》 以后简称《自》

2. 80x86汇编语言程序设计教程》 以后简称《80

3. LINUX内核0.11详细注释》 以后简称《L

4. NASM中文手册

5. 关于各种文件系统、文件头等的说明资料(百度的,很零散,一大堆)

   

软件:

1.   MASM 我用的是6.11,当然还包括相应的LINKML 6.15

2.   NASM 《自》一书是用NASM写汇编

3.   反汇编工具 我用的是ndisasmW32Dasm

4.   VMware Workstation

5.   Microsoft Virtual PC 写出来的东西总得运行吧,所以VMVPC必备

6.   bochs 调试代码利器,断点、单步执行、读内存......

7.   winimage 方便读写软盘

8.   PDF阅读器 我用的是Foxit Reader 2.0

9.   能写软盘指定扇区的工具 自己用VC写了一个

10. UltraEdit 用来写汇编代码,这个要多说几句,详见补充1

   

除了9,其他都能在网上很容易找到。所有工具的基本使用方法都很简单,bochs相对复杂一点,所以在补充2中介绍一下。我写的这个工具可以在这里下载:http://download.csdn.net/source/1019735 ,因为太简单了,所以没有使用说明,只有功能描述。

   

补充1UE安装后wordfile没有MASM汇编的语法支持,需要到UE官网去下(http://www.ultraedit.com/files/wf/masm.txt)。直接复制打开的文本,粘贴到UE安装目录下wordfile.uew文件的最后面。实际上UE并不是读的这个文件,而是用户文件夹下Application Data\IDMComp\UltraEdit里面的wordfile.uew文件,具体位置也可以这样确定:运行UE –> 高级 -> 配置 -> 导航 -> 编辑器显示 -> 语法高亮,右边窗口中“词语列表的完整路径”就是需要的路径了,将wordfile.uew复制过去后,UE会根据打开文件的扩展名自动选择相应的语法高亮。

      

起先UE被我当成了记事本在用,没有语法高亮,一片黑白,因为影响不是太大,所以没在意,然后发生了一件搞笑的事情使我开始用语法高亮功能了:有天当我写 C++ (VC6里边)的时候,发现运行结果总是不对,就一段很短很简单的代码,我怎么看都没发现哪错了。最后发现是由于最近都是写汇编,所以习惯性的用分号来注释,如此一来编译器是不会报错的,结果理所当然的不对了。因为写汇编的时候没有语法高亮,形成了习惯,所以在找C++代码错误的时候,分号后面的语句被我直接过滤掉了……

     

补充2我用的bochs版本是2.2.pre1。安装好以后建立一个名为XXX.bxrc的文件(记得显示扩展名),然后将下面的内容复制进去(修改自《自》一书):

      

-----------------------------------XXX.bxrc------------------------------------------------

megs: 32

romimage: file=$BXSHARE/BIOS-bochs-latest, address=0xf0000

vgaromimage: $BXSHARE/VGABIOS-elpin-2.40

floppya: 1_44=BOOT.IMG, status=inserted

boot: a

mouse: enabled=0

log: bochsout.txt

keyboard_mapping: enabled=1, map=$BXSHARE/keymaps/x11-pc-us.map

------------------------------------ XXX.bxrc ------------------------------------------

        

注释我都去掉了,其中floppya: 1_44=BOOT.IMG, status=inserted这一句中的BOOT.IMG需要改为自己的软盘映像名,如果软盘映像和XXX.bxrc不在同一文件夹下,那么还得加上路径。log: bochsout.txt这一句是指定生成的LOG文件名,我看不懂LOG文件,所以直接将它路径指向回收站……XXX.bxrc可以放在任何地方,直接双击就可以运行。不过直接双击相当于是在用VMVPCBOOT.IMG软盘启动,并没有调试功能,要调试需要用bochsdbg.exe来运行,于是可以写一个YYY.BAT文件(同样修改自《自》一书):

     

-------------------------------------YYY.BAT------------------------------------------------

ZZZ\bochsdbg.exe -q -f bochsrc.bxrc

------------------------------------ YYY.BAT ------------------------------------------

     

ZZZbochs的安装路径,比如 C: \Bochs-2.2.pre1 . 直接双击YYY.BAT会出现一个命令行窗口,就可以开始调试了。下面是我认为最有用的调试命令(由于百度空间对表格支持相当糟糕,所以只好贴图了):

       


这里有几个注意的地方:

1. 所有的大小写都是不等效的,目前为止没有用过大写字母,所有的一切都是小写。

2. 16进制数加0x前缀,不是h后缀。

3. 这里列出的所有指令中用到的地址都是物理地址,不是线性地址也不是虚拟地址。操作线性地址,虚拟地址的指令是不同的。如果安装bochs的时候安装了帮助手册那么“安装路径\ docs\user\internal-debugger.html”就是所有指令的详细说明。

4. 不管是中断还是单步执行,显示出来的指令都是没有执行的,所以查看的任何信息都是指令执行前的。

5. xp/nuf addrnufn表示有多少个单元数据需要显示, u表示1个单元的大小,可取值为b1个字节)、h2个字节)、w4个字节)、g8个字节), f表示输出格式,可取值为x16进制)、d10进制)、u(无符号10进制)、o8进制)、t2进制)。nuf都是可选的,uf默认和上一次使用该指令时的取值一样或者默认是wx(第一次使用时),n默认为1。如果nuf都使用默认值,那么输入指令的时候就应该去掉斜线。addr也是可选的,默认是下一个地址值。指令获得的值,单元之间是按地址由低到高排列,而单元内是由高到低排列。例如使用xp/2wx 0x7c00 输出 0x5ab000b7 0x05b947b3 那么按写代码的习惯由高到低是 0x05b947b3 5ab000b7 而按内存中由低到高就是 b7 00 b0 5a b3 47 b9 05

    

    这算是前言吧,到这里差不多结束了,最后要说的是,本分类下所有文章都是基于自己的理解(甚至是推测),如果有不同的观点欢迎讨论,如果有错误请指正。


原文网址: http://hi.baidu.com/liuqizealot/blog/item/d8b68982e35fa097f703a66c.html


操作系统实践1--软盘引导扇区初接触

2009-02-18 09:57

    在本文中,我们将写出第一个不依赖操作系统运行的程序,它的作用是调用BIOS的10H号中断9号功能在光标处输出5个红底白字的“Z”,之所以没有像《自》一书中输出“Hello, OS world!”,是因为这样输出比较简单,毕竟它没什么用。

    我会详细解释代码,如果解释比较少就以注释形式放在代码里面,如果需要说的比较多,就以补充形式将解释放到最后。还要说明的是,虽然本系列文章中的汇编代码是用MASM编译,但是所有代码都会脱离MSDOS,不依赖任何操作系统运行,如果需要在MSDOS下运行,那么你可以自己加上条件汇编指令,代码的差异我会在解释代码的时候提及。

下面的代码OSHello.asm可以不做任何修改编译出来在MSDOS下运行,只是这和我们写这段代码的目的相悖。

-----------------------------------OSHello.asm------------------------------------------------
.MODEL SMALL
.CODE
START: ;见补充1

;调用10H号中断的9号功能在光标处输出5个红底白字的“Z”
MOV BH,0H ;见补充2
MOV AL,'Z'   ;要显示字符的ASCII码,同C语法一样可以用’Z’表示
MOV BL,47H ;属性,47H表示红底白字
MOV CX,5H   ;重复输出次数
MOV AH,9    ;第9号功能
INT   10H      ;10H号中断

JMP   $        ;见补充3
ORG 510       ;见补充4
DW   0AA55H ;见补充4
END START
-----------------------------------OSHello.asm 完---------------------------------------------

补充1:众所周知这是程序的入口点,它其实是一个普通标号,在这里是没有用处的,因为引导扇区将被加载到7C00H处,然后直接跳转过来开始执行,也就是说7C00H就是程序的入口点,当然加上也不会错,为了保持习惯就留着了。至于入口标号的作用,暂时放一放,下一篇文章介绍MZ文件头的时候再解释。

补充2:这个参数是显示页号,就是要将“Z”显示到哪一页,因为在此之前并没有任何输出所以是0,如果你将此参数设为1(或其他),那么你将看不到输出,因为被显示到1号页去了,而当前屏幕显示的是0号页。关于页号的详细解释可参阅《80》一书183页。

补充3:这是一个无限跳转,因为后面没指令了,所以只好让程序停在这里。前面这些我知道地球人都知道,我只是想强调一点:大多数情况下用 JMP $ 可以节省很多调试程序的时间,因为它可以迅速定位问题代码位置。最后说一下在用 JMP $ 定位上我犯的一个浪费了我很长时间的愚蠢错误,看代码:
……
JMP ABCDEFG
JMP $
ABCDEFG:
……
我想没必要再多说了。

补充4:先讲讲计算机的启动,《L》一书中的原话:当PC的电源打开后,80x86结构的CPU将自动进入实模式,并从地址0xFFFF0开始自动执行程序代码,这个地址通常是ROM-BIOS中的地址。PC机的BIOS将执行某些系统的检测,并在物理地址0处开始初始化中断向量。此后,它将可启动设备的第一个扇区(磁盘引导扇区,512字节)读入内存地址0x7C00处,并跳转到这个地方。另外所有的资料都指出引导扇区的第511字节必须是55H 第512字节必须是0AAH,因为BIOS要靠此来验证是否引导扇区。这就是代码最后加上“DW   0AA55H”的原因(经过我的测试在VM和VPC下不加0AA55H也能正常运行,bochs下会出错,实机没有测试)。“DW   0AA55H”解释完了,现在解释“ORG 510”,它的作用就是将剩下的空间用0填充,直到510字节,这样做的目的只是为了能在最后两字节写上 0AA55H而已。到这里所有的代码都解释完了,但是关于ORG我还有很多话要说,先看《自》一书中的第一个引导扇区代码boot.asm:

-----------------------------------boot.asm------------------------------------------------
org 07c00h    ; 告诉编译器程序加载到7c00处
mov ax, cs
mov ds, ax
mov es, ax
call DispStr    ; 调用显示字符串例程
jmp $    ; 无限循环
DispStr:
mov ax, BootMessage
mov bp, ax    ; ES:BP = 串地址
mov cx, 16    ; CX = 串长度
mov ax, 01301h   ; AH = 13, AL = 01h
mov bx, 000ch   ; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)
mov dl, 0
int 10h    ; 10h 号中断
ret
BootMessage:   db "Hello, OS world!"
times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw 0xaa55     ; 结束标志
-----------------------------------boot.asm 完---------------------------------------------

注意第一行的红色代码,初接触NASM汇编,我实在想不通为什么有这么一行代码,后来看NASM手册才知道NASM中的ORG和MASM的ORG是不同的:MASM中ORG是改写地址计数器为指定的值,它可不仅仅是只能将值变大中间补0这么简单,它还可以将值变小,然后覆盖已经产生的代码(很好很强大);NASM中的ORG只是简单的将所有内部的地址引用增加一个段内偏移值。

再看看《L》一书中这句话:此后,它将可启动设备的第一个扇区(磁盘引导扇区,512字节)读入内存地址0x7C00处,并跳转到这个地方。这里并没有明确说明是跳转到7C0H:0还是0:7C00H。然后LINUX0.11的bootsect.s中有这样一个符号定义 BOOTSEG = 0x7c0 对这个符号的注释是 original address of boot-sector bootsect的原始地址(是段地址,以下同)看到这里我将开始的跳转错误理解为7C0H:0(实际上是0:7C00H)。

因为这里同时有两个错误的理解,所以想不明白boot.asm编译出来运行的结果为什么和想像中不一样。按我的理解,去掉“org 07c00h”这一句以后也能正常执行,但实际执行的结果却是去掉以后显示出来的是一串乱码,也就是说程序其他指令是正常的,只是BootMessage这个变量的偏移错了。后来在NASM手册中看到了对ORG的说明,知道了ORG的正确含义,对两种运行结果进行分析,得出结论,程序是被加载到0:7C00H处,而不是7C0H:0处。上网一搜果然是这样,后来我发现《自》一书中明确讲了是跳转到0:7C00H,只是我一直没看到,浪费了不少时间。

    这里我还想说一下,去掉“org 07c00h”后 CALL DispStr 和 JMP $ 不会错误跳转的原因:BootMessage编译的时候是以第一条指令(MOV AX,CS)为原点的偏移,以数值形式写入指令中,比如这里为 001FH(也就是说MOV AX,BootMessage被编译成了 MOV AX,001FH),由于程序在运行的时候被加载到了 0:7C00H 处,所以"Hello, OS world!"被加载到了 0*10H + 07C00H + 001FH 处,可见地址相差了07C00H读取的数据当然错了。再看看CALL和JMP,这里的CALL是一个段内直接调用,执行到CALL的时候会将IP入栈(IP总是指向下一条指令的偏移),然后给当前IP加上一个差值,该差值是一个以IP为原点的偏移。返回的时候IP出栈,子程序结束。JMP $ 相对CALL来说就是少了出入栈两个过程而已,跳转过程是当前IP加上以IP为原点的偏移。可以看出他们都是以自身所在位置为原点跳转,所以不会错。

最后说说OSHello.asm(不是boot.asm)的编译和运行,为了方便,将OSHello.asm和MASM放同一路径。MASM是DOS程序(NASM也一样),所以编译过程是 运行CMD -> 进入MASM所在目录(必须和LINK、ML放一起) -> 输入“ml OSHello.asm” 即可完成编译。编译出来的文件名是默认的OSHello.exe,接下来只要写入软盘就可以运行了,但是这个MASM编译LINK链接出来的OSHello.exe是一个带MZ文件头(下篇介绍)的可执行程序,所以需要去掉MZ头,可以使用我写的那个工具,在写入软盘的时候会自动判断MZ头并去掉,于是可以随便新建一个文件,将扩展名改为IMG,将OSHello.exe写入,如果准备用VPC运行还需要点一下“1.44M映像”按钮。软盘做好了,用虚拟机加载映像并启动就可以看到结果了。

本篇到这里就结束了,下一篇讲的是同一个16位代码段中实模式和保护模式的互相跳转。
原文网址: http://hi.baidu.com/liuqizealot/blog/item/1029d73fa0f24ec57c1e716b.html


操作系统实践2--同一个段下实模式和保护模式相互跳转

2009-02-23 12:12

先看代码,代码不会解释的太详细,只把相关代码作用提一下,需要详细解释的仍然以补充形式给出。

general.asm定义了一些宏和符号常量
----------------------------------- general.asm------------------------------------------------
LOADER_SEG EQU 5000H ;loader(本篇的程序)被引导扇区代码加载到内存的位置
                                            ;如果在MSDOS下运行,LOADER_SEG需要设为0

;GDTR用伪描述符结构
GDTR_STRUC STRUC
           LIMIT DW 0 ;低16位段界限
           BASE DD 0 ;高32位基地址
GDTR_STRUC ENDS

;Descriptor 段描述符 8字节
;Base 段基地址,32位
;Limit 段界限,20位
;Attr 段属性,12位
Descriptor STRUC
            LimitL      DW 0 ;段界限1 (0~15)
            BaseL       DW 0 ;段基地址1 (0~15)
            BaseM       DB 0 ;段基地址2 (16~23)
            Attribute   DW 0 ;属性1 + 段界限2 +属性2
            BaseH       DB 0 ;段基地址3 (23~31)
Descriptor ENDS

ATDW = 92H     ;存在的可读写数据段属性
ATCE = 98H     ;存在的只执行代码段属性
DA_32 = 4000H ;32位段

;设置段描述符,其中参数Limit和Base为32位存贮器操作数,Attr为16位常数(高字节的低4位必须为0)
SetDescriptor MACRO DesName,Limit,Base,Attr
PUSH AX
MOV AX,WORD PTR Limit
              MOV &DesName.LimitL,AX ;LimitL赋值
               
              MOV AX,WORD PTR Base ;BaseL赋值
              MOV &DesName.BaseL,AX
               
              MOV AL,BYTE PTR Base+2 ;BaseM赋值              
              MOV &DesName.BaseM,AL
               
              MOV &DesName.Attribute,Attr ;Attr赋值
              MOV AL,BYTE PTR Limit+2
              OR   BYTE PTR &DesName.Attribute+1,AL
               
              MOV AL,BYTE PTR Base+3 ;BaseH赋值
              MOV &DesName.BaseH,AL
              POP AX
ENDM

;打开地址线A20
ENABLEA20 MACRO
          PUSH AX
          IN AL,92H
          OR AL,2
          OUT 92H,AL
          POP AX
ENDM

;关闭地址线A20
DISABLEA20 MACRO
           PUSH AX
           IN AL,92H
           AND AL,0FDH
           OUT 92H,AL
           POP AX
ENDM
----------------------------------- general.asm 完----------------------------------------------


Loader实现的就是实模式和保护模式的相互跳转:
-----------------------------------   loader.asm   ----------------------------------------------

INCLUDE GENERAL.ASM
.386P
       
;16位代码段
CSEG SEGMENT USE16
ASSUME CS:CSEG,DS:DSEG
START:
;MOV BX,REAL-2       ;补充4
;MOV WORD PTR CS:[BX], LOADER_SEG+CSEG

MOV AX,LOADER_SEG+DSEG ;补充1
MOV DS,AX

;设置正确的堆栈段
MOV AX,LOADER_SEG+DSEG_STACK
MOV SS,AX
MOV SP,TopOfStack

;伪描述符基地址赋值,界限定义时已设置
XOR EAX,EAX ;EAX清0
MOV AX,DS
SHL EAX,4
ADD EAX,OFFSET GDT ;EAX为GDT基地址
MOV FGDTR.BASE,EAX

;设置代码段描述符
XOR EAX,EAX ;EAX清0
MOV AX,CS
SHL EAX,4 ;EAX为DES_CODE基地址
MOV SELECTOR_BASE,EAX
MOV SELECTOR_LEN,0FFFFH ;补充5
SetDescriptor <DES_CODE>,SELECTOR_LEN,SELECTOR_BASE,ATCE

;设置显存数据段描述符
MOV SELECTOR_BASE,0B8000H
MOV SELECTOR_LEN,0FFFFH
SetDescriptor <DES_VIDEO>,SELECTOR_LEN,SELECTOR_BASE,ATDW

;设置要显示的字符串所在数据段描述符
XOR EAX,EAX
MOV AX,LOADER_SEG+DSEG_STR
SHL EAX,4
MOV SELECTOR_BASE,EAX
MOV SELECTOR_LEN,0FFFFH
SetDescriptor <DES_STRING>,SELECTOR_LEN,SELECTOR_BASE,ATDW

;设置5MB处读写数据段描述符
MOV SELECTOR_BASE,500000H
MOV SELECTOR_LEN,0FFFFH
SetDescriptor <DES_OUT>,SELECTOR_LEN,SELECTOR_BASE,ATDW

;设置堆栈段描述符
XOR EAX,EAX
MOV AX,SS
SHL EAX,4
MOV SELECTOR_BASE,EAX
MOV SELECTOR_LEN,TopOfStack
SetDescriptor<DES_STACK>,SELECTOR_LEN,SELECTOR_BASE,ATDW

LGDT FGDTR ;加载GDTR

CLI ;关中断

ENABLEA20 ;打开地址线A20

;CRO的PE位置1,进入保护模式
MOV EAX,CR0
OR EAX,1
MOV CR0,EAX

;跳转 补充2
DB 0EAH
DW OFFSET PROTECTED
DW SelectorCode

PROTECTED:
;设置选择子
MOV AX,SelectorString
MOV DS,AX

MOV AX,SelectorOut
MOV ES,AX

MOV AX,SelectorVideo
MOV GS,AX

MOV AX,SelectorStack
MOV SS,AX
MOV SP,TopOfStack

MOV AH,0CH
XOR EDI,EDI
MOV EDI,(80*10+0)*2

MOV ESI,OFFSET PMMessage

;将PMMessage显示出来
_AGAIN:
MOV AL,[ESI]
INC ESI
TEST AL,AL
JZ _NEXT
MOV GS:[EDI],AX
ADD EDI,2
JMP _AGAIN
_NEXT:

CALL DispReturn ;换行
CALL TextRead ;显示5MB处内存
CALL TextWrite ;写5MB处内存
CALL TextRead ;显示更新后的5MB处内存

MOV AX,SelectorString ;补充3
MOV SS,AX

;CRO的PE位置0,回到实模式
MOV EAX,CR0
AND AL,0FEH
MOV CR0,EAX

;跳转 补充4
DB 0EAH
DW OFFSET REAL
DW LOADER_SEG   ;如果在MSDOS下运行,这里LOADER_SEG需要改为CSEG

REAL:
MOV AX,LOADER_SEG+DSEG_STACK
MOV SS,AX

MOV AX,LOADER_SEG+DSEG
MOV DS,AX

DISABLEA20 ;关闭地址线A20

STI ;开中断

;显示1个L,表示回到了实模式
MOV AX,0B800H
MOV GS,AX
MOV EDI,(80*5+0)*2
MOV AH,0CH
MOV AL,'L'
MOV GS:[EDI],AX

JMP $ ;程序到这里结束

TextRead PROC
XOR ESI,ESI
MOV ECX,8
_LOOP:
MOV AL,ES:[ESI]
CALL DispAL
INC ESI
LOOPW _LOOP
CALL DispReturn
RET
TextRead ENDP

TextWrite PROC
PUSH ESI
PUSH EDI
XOR ESI,ESI
XOR EDI,EDI
MOV ESI,OFFSET StrTest
CLD
NUM1:
lodsb
test AL,AL
JZ NUM2
MOV ES:[EDI],AL
INC EDI
JMP NUM1
NUM2:
POP EDI
POP ESI
RET
TextWrite ENDP

DispAL PROC ;将AL中的数据用十六进制数(2个)显示
PUSH ECX
PUSH EDX
MOV AH,0CH
MOV DL,AL ;AL中是读出的字符
SHR AL,4 ;先处理高4位
MOV ECX,2
BEGIN:
AND AL,01111B
CMP AL,9
JA NUM1       ;AL中数据大于9转移
ADD AL,'0'    ;AL小于等于9(ASCII 9后面是“:” 不是字母A)
JMP NUM2
NUM1:
SUB AL,0AH     ;AL大于等于10就先减10
ADD AL,'A'
NUM2:
MOV GS:[EDI],AX
ADD EDI,2

MOV AL,DL
LOOP BEGIN
ADD EDI,2

POP EDX
POP ECX

RET
DispAL ENDP

DispReturn PROC
PUSH EAX
PUSH EBX
MOV EAX,EDI
MOV BL,160
DIV BL
AND EAX,0FFH
INC EAX
MOV BL,160
MUL BL
MOV EDI,EAX
POP EBX
POP EAX
RET
DispReturn ENDP


SegCodeLen = $ - START
CSEG ENDS

;数据段GDT
DSEG SEGMENT USE16
GDT Descriptor <> ;空描述符
DES_CODE   Descriptor <> ;代码段
DES_VIDEO Descriptor <> ;显存段
DES_STRING Descriptor <> ;读取段
DES_OUT Descriptor <>    ;写入段
DES_STACK Descriptor <> ;堆栈段

GdtLen = $ - GDT ;GDT长度

FGDTR GDTR_STRUC<GdtLen,0> ;伪描述符

SELECTOR_BASE DD ?
SELECTOR_LEN DD ?

;GDT选择子
SelectorCode = DES_CODE - GDT ;代码段选择子
SelectorVideo = DES_VIDEO - GDT ;显存段选择子
SelectorString = DES_STRING - GDT   ;要显示的字符串所在数据段选择子
SelectorOut = DES_OUT - GDT ;5MB处读写数据段选择子
SelectorStack = DES_STACK - GDT ;堆栈段选择子
DSEG ENDS

;保存字符串数据段
DSEG_STR SEGMENT
PMMessage DB "In Protect Mode now. @_@",0
StrTest DB "ABCDEFGHIJKLMNOPQRSTUVWXYZ",0
DSEG_STR ENDS

DSEG_STACK SEGMENT USE16
TEMP DB 512 DUP(0)
TopOfStack = $ - TEMP
DSEG_STACK ENDS

END START
----------------------------------- loader.asm 完   -------------------------------------------

    由于保护模式的详细说明内容太多,再加上很多资料都有介绍(比如《80》和《自》),所以这里就不再累述。

为了解释补充1先看一下MZ文件头的结构:

偏移          长度        描述  
00H           WORD        MZ标志,4D5AH(就是“MZ”两个字符)  
02H           WORD        最后扇区的长度(字节数)
04H           WORD        程序总页数(一页512字节,要算文件头)  
06H           WORD        重定位表项数
08H           WORD        文件头的节数(一节16BYTE)  
0AH           WORD        最小分配大小
0CH           WORD        最大分配大小
0EH           WORD        初始SS值
10H           WORD        初始SP值  
12H           WORD        补码校验值
14H           WORD        初始IP值(就是指定程序入口点偏移)
16H           WORD        初始CS值(指定程序入口点段值)
18H           WORD        重定位表的偏移  
1AH           WORD        覆盖数

    这里简化程序的运行过程,只说我们关心的部分。MSDOS从重定位表的偏移来确定重定位表在MZ头的位置,从重定位表项数来确定重定位表的长度,然后利用重定位表的内容给程序中每一个段值的引用加上一个修正值。 重定位表中每两个字节表示一个需要重定位的位置,也叫做重定位指针(不是需要加上的修正值)。比如MOV DS,DSEG这样的代码,重定位表就会有一个重定位指针,指向编译出来的EXE文件中该语句DSEG的值,MSDOS运行该程序的时候就会给它加上一个修正值。这样做的原因是,编译的时候,编译器无法确定程序运行的时候会被加载到什么位置,所以需要运行的时候由操作系统来修正。
    初始IP值和CS值用来确定程序的入口点,这个入口点就是START标号所代表的位置,所以如果没有指定入口标号,MASM无法确定程序的入口点,这里的IP和CS就没有正确的值(都会被置为0),程序无法正常运行,但是呢通过前面的描述可以知道,如果程序的入口在程序的最前面(就像loader一样)那么入口标号是可以省略的。

补充1:本段代码被引导扇区程序(本篇后边给出)加载到LOADER_SEG:0处,引导扇区程序并没有MSDOS的重定位能力,所以在LOADER中每一个对段值的引用都需要加上LOADER_SEG。

补充2:这里用机器码完成了一个跳转,而且是强制作为一个段间跳转处理。这里需要一个段间跳转的原因是,当CR0的PE位被置1后,处理器就已经运行在保护模式下了,但这时CS中还是实模式下的段值,而不是保护模式下的选择子,所以需要这样一个将选择子放入CS的段间跳转。如果不做这样一个跳转,那么当CPU取指令的时候,CS中虽然是一个段值,但是CPU却会当成一个选择子来处理,如此一来,就无法正确取指令。又由于指令预取队列的存在,这个跳转指令在PE位被置1之前就已经在指令预取队列中了,
也就是说,该跳转指令是在实模式下被预取,在保护模式下被执行,这就是该跳转指令能正确执行的原因。由这里的分析可以得出一个结论:该跳转代码后的一定长度的指令都被预取了,所以就算不做跳转,被预取的代码也能正确执行。再想想,保护模式的代码是紧跟在跳转指令之后的一小段,通过前面的分析可以知道就算把该跳转指令去掉,程序也是正常的,当然代码不能这样写,跳转指令还得加上。
    这里的跳转指令如果使用段间直接转移指令 JMP FAR PTR PROTECTED,那么JMP中要送到CS的值MASM会编译成段值而不是选择子,就会出错。再看看下面的段间间接转移指令:

    JMP DWORD PTR [LQZT]                    MOV BX,$+6 ;这里MOV和JMP各占3字节,所以加6
LQZT:                                         或者       JMP DWORD PTR CS:[BX]
    DW OFFSET PROTECTED                  DW OFFSET PROTECTED
    DW SelectorCode                                 DW SelectorCode

    这里LQZT是代码段下的标号,MASM编译的时候会自动加上段超越前缀,所以不用指定。看起来似乎是错的,因为读CS:[LQZT]内存数据的时候,处理器已经运行在保护模式下了,CS中的段值会被当作选择子来寻址。实际上保护模式下,CPU直接从段描述符高速缓冲寄存器获取基址,属性等信息,而此时段描述符高速缓冲寄存器中的基址是正确的,所以这里不会错。回过头来看刚才的这个结论“该跳转代码后的一定长度的指令都被预取了,所以就算不做跳转,被预取的代码也能正确执行”,想想段描述符高速缓冲寄存器可以知道,就算指令不被预取,也不会有问题,而且没有长度限制。

补充3:这里是给堆栈段加载合适的段属性,以使对应段描述符高速缓冲寄存器能有合适的段界限和段属性,这样才能正常的回到实模式。段描述符高速缓冲寄存器(也叫影子寄存器),它不仅仅在保护模式下,起作用,在实模式下仍然起作用。目前我并没有看到任何资料详细介绍了段描述符高速缓冲寄存器,就算在INTER手册上也只看到了简单的几句话,可能是没找到吧。在实模式下段描述符高速缓冲寄存器必须有合适的段界限和段属性,否则,一旦使用对应的段寄存器就会出错。我不知道CPU如何判断是否合适,我只知道下面的属性和界限是合适的(这里说一下BOCHS的dump_cpu能看到段描述符高速缓冲寄存器的值):界限0FFFFH(连0FFFEH都不行),属性93H(数据段,包括堆栈段,当然92H也可以)、9BH(代码段)。CS是不能直接修改的,所以保护模式回到实模式,必须从16位段跳转(下一篇详述)。通过上面的的结论可以知道,如果设置堆栈段描述符的界限的时候不是设置为TopOfStack而是0FFFFH,那么这一步就可以省略。其实对实模式来说,段描述符高速缓冲寄存器,没有什么用处,不知道为什么CPU要这样设计,BOCHS和VPC在模拟实模式下的CPU的时候可能就没有对段描述符高速缓冲寄存器做限制,于是在回实模式前,不更改段属性也不会错。

补充4:这里又用机器码完成了一个跳转,同样强制作为一个段间跳转。当CR0的PE位被置0后,处理器已经运行在实模式下了,但这时CS中还是保护模式下的选择子,所以需要这样一个将段值放入CS的段间跳转。同样这里的跳转可以去掉。再来看看段间直接转移指令和段间间接转移指令。分析在上一个跳转中,不能使用段间直接转移指令的原因,可以很容易得出结论,这里直接使用段间直接转移指令 JMP FAR PTR REAL在MSDOS下运行是可以的,但因为引导扇区程序没有重定位功能,所以这里不能这样写。那段间间接转移指令呢?先看一下这里的段间间接转移指令:

    JMP DWORD PTR [LQZT2]                             MOV BX,$+6 ;这里MOV和JMP各占3字节,所以加6
LQZT2:                                          或者             JMP DWORD PTR CS:[BX]
    DW OFFSET REAL                                       DW OFFSET REAL
    DW LOADER_SEG                                       DW LOADER_SEG

想一下,当CR0的PE位被置0后,处理器已经运行在实模式下了,如果这时候用段间间接跳转指令,那么读CS:[LQZT]内存数据的时候,处理器会将CS中的值作为段值来寻址,但实际上此时CS中却是一个选择子,于是,这里是不能用段间间接转移指令。再回想一下补充2中的内容,如果去掉那一个跳转,CS中将一直是段值,于是这里可以用段间跳转指令了!很有意思,不是吗。
    关于这个跳转还没完。通常我们写汇编代码,习惯上都喜欢将数据段放前边,代码段放后边,这里却将代码段放在了最前边,原因是这样写代码段就处于整个程序的最前边,编译的时候段值为0,这样在写跳转代码的时候最后一个DW值就可以直接是LOADER_SEG,不用再加一个CSEG。就这么简单?当然不是了。我们将跳转代码改为:
DB 0EAH
DW OFFSET REAL
DW LOADER_SEG+CSEG
编译,然后反汇编,找到这个跳转代码,它竟然是 jmp 0000:01c0 这里可以清楚的看到,反汇编出来的跳转代码中段值被编译成了0(LOADER_SEG被丢弃了)。MASM编译出来的指令,跳转指令中的段值只要CSEG这样的段值参与了运算(无论是直接还是间接,甚至ORG都不敷使用),那么其他部分都会被丢弃,其他指令没有这样的问题。MASM这样设计当然有他的原因,但是因此这里如果CSEG不为0会变的比较麻烦,所以代码段被放到了最前边。既然说到这里了,自然得给出解决办法,编译的时候是没办法了,只好在程序运行的时候动点手脚:在START标号后面加上指令MOV BX,REAL - 2MOV WORD PTR CS:[BX], LOADER_SEG+CSEG   也就是Loader.asm中注释掉的指令,这样程序运行后第一件事情就是修改内存,以修正这里的段值。当然了,也可以直接修改生成的EXE文件。

补充5:这里的段界限必须是0FFFFH,不能是SegCodeLen,否则在最后回到实模式后不能调用中断。主要原因还是段描述符高速缓冲寄存器必须有正确的值。这里困扰了好久......

loader.asm到这里就结束了,要运行loader还需要一个引导扇区程序:
----------------------------------- bootLoad.asm -------------------------------------------
;将 loader 模块加载到LOADER_SEG:0H处,然后执行loader
INCLUDE GENERAL.ASM

CSEG SEGMENT
START:
MOV AX,CS
MOV DS,AX

;清屏,并将光标移到左上角,因为BOCHS启动的时候输出了很多信息,所以清屏
MOV AL,0
MOV AH,7
MOV CH,0
MOV CL,0
MOV DH,24
MOV DL,79
MOV BH,07
INT 10H

MOV BH,0
MOV DH,0
MOV DL,0
MOV AH,2
INT 10H

;软驱复位
XOR AH,AH
XOR DL,AL
INT 13H

;读LOADER
load_sys:
MOV AX,LOADER_SEG
MOV ES,AX
MOV BX,0 ;读到LOADER_SEG:0处
MOV DX,0H ;0号驱动器,0号磁头
MOV CX,2H ;从0磁道,2扇区开始读
MOV AH,2H ;读磁盘扇区到内存
MOV AL,8H ;读8个扇区
INT 13H
JNC load_sys_suc
MOV DX,0
MOV AX,0
INT 13H

JMP load_sys

load_sys_suc: ;这里跳转的LOADER_SEG:0 注意代码段必须放最前边
DB 0EAH
DW 0
DW LOADER_SEG

ORG 510   ;填充剩下的空间,使生成的二进制代码恰好为512字节
DW 0AA55H ;结束标志
CSEG ENDS
END START
----------------------------------- bootLoad.asm 完   -------------------------------------------
  
bootLoad.asm没什么好说的,只是要注意当loader程序长了以后,这里读扇区的数量需要做相应的修改。曾经就因为读的扇区不够,loader没读完,产生了一些莫名其妙的问题。

用命令ML bootLoad.asm ML loader.asm 分别编译并链接,然后用写扇区工具将bootloade.exe写入第一个扇区,将loader.exe从第二个扇区开始写入。然后就可以用虚拟机运行了。

下一篇对loader.asm作一点修改,让保护模式处于32位段下。
原文网址: http://hi.baidu.com/liuqizealot/blog/item/44a359af9856bdfcfaed5066.html


操作系统实践3--16位段下实模式和32位段下保护模式相互跳转

2009-02-27 09:17

本篇对上一篇的loader.asm做了一点改动,让保护模式在32位段下。

-----------------------------------   loader32.asm   ---------------------------------------------

INCLUDE GENERAL.ASM ;GENERAL.ASM没有变,在此不再给出

.386P

;16位代码段

CSEG16 SEGMENT USE16

ASSUME CS:CSEG16,DS:DSEG

START:

MOV AX,LOADER_SEG+DSEG

MOV DS,AX

MOV AX,LOADER_SEG+DSEG_STACK

MOV SS,AX

MOV SP,TopOfStack

   

;伪描述符基地址赋值,界限定义时已设置

XOR EAX,EAX ;EAX清0

MOV AX,DS

SHL EAX,4

ADD EAX,OFFSET GDT ;EAX为GDT基地址

MOV FGDTR.BASE,EAX

;设置16位代码段描述符

XOR EAX,EAX ;EAX清0

MOV AX,CS

SHL EAX,4 ;EAX为DES_CODE16基地址

MOV SELECTOR_BASE,EAX

MOV SELECTOR_LEN,0FFFFH ;这里必须为0FFFFH,否则回到实模式后CS影子寄存器界限不对

SetDescriptor <DES_CODE16>,SELECTOR_LEN,SELECTOR_BASE,ATCE

;设置32位代码段描述符

XOR EAX,EAX ;EAX清0

MOV AX,LOADER_SEG+CSEG32

SHL EAX,4 ;EAX为DES_CODE32基地址

MOV SELECTOR_BASE,EAX

MOV SELECTOR_LEN,SegCodeLen32

SetDescriptor <DES_CODE32>,SELECTOR_LEN,SELECTOR_BASE,ATCE+DA_32

;设置显存数据段描述符

MOV SELECTOR_BASE,0B8000H

MOV SELECTOR_LEN,0FFFFH

SetDescriptor <DES_VIDEO>,SELECTOR_LEN,SELECTOR_BASE,ATDW

;设置要显示的字符串所在数据段描述符

XOR EAX,EAX

MOV AX,LOADER_SEG+DSEG_STR

SHL EAX,4

MOV SELECTOR_BASE,EAX

MOV SELECTOR_LEN,0FFFFH

SetDescriptor <DES_STRING>,SELECTOR_LEN,SELECTOR_BASE,ATDW

;设置5MB处读写数据段描述符

MOV SELECTOR_BASE,500000H

MOV SELECTOR_LEN,0FFFFH

SetDescriptor <DES_OUT>,SELECTOR_LEN,SELECTOR_BASE,ATDW

;设置堆栈段描述符

XOR EAX,EAX

MOV AX,SS

SHL EAX,4

MOV SELECTOR_BASE,EAX

MOV SELECTOR_LEN,0FFFFH

SetDescriptor<DES_STACK>,SELECTOR_LEN,SELECTOR_BASE,ATDW

    

LGDT FGDTR ;加载GDTR

CLI ;关中断

ENABLEA20 ;打开地址线A20

;CRO的PE位置1,进入保护模式

MOV EAX,CR0

OR EAX,1

MOV CR0,EAX

;进入32位段下的保护模式   补充1

DB 66H

DB 0EAH

DD OFFSET PROTECTED32

DW SelectorCode32

PROTECTED16:

;CRO的PE位置0,回到实模式

MOV EAX,CR0

AND AL,0FEH

MOV CR0,EAX

;跳回实模式

DB 0EAH

DW OFFSET REAL  

DW LOADER_SEG ;如果在MSDOS下运行,这里LOADER_SEG需要改为CSEG

     

REAL:

MOV AX,LOADER_SEG+DSEG_STACK

MOV SS,AX

MOV AX,LOADER_SEG+DSEG

MOV DS,AX

MOV SP,TopOfStack

     

DISABLEA20 ;关闭地址线A20

STI ;开中断

;调用中断输出5个L,以示回到了实模式

MOV BH,0H

MOV AL,'L'

MOV BL,47H

MOV CX,5H

MOV AH,9

INT 10H

JMP $ ;程序到这里结束

SegCodeLen16 = $ - START

CSEG16 ENDS

;32位代码段

CSEG32 SEGMENT

ASSUME CS:CSEG32,DS:DSEG

PROTECTED32:

;设置选择子

MOV AX,SelectorString

MOV DS,AX

    

MOV AX,SelectorOut

MOV ES,AX

MOV AX,SelectorVideo

MOV GS,AX

MOV AX,SelectorStack

MOV SS,AX

MOV ESP,TopOfStack

MOV AH,0CH

XOR EDI,EDI

MOV EDI,(80*10+0)*2

MOV ESI,OFFSET PMMessage

;将PMMessage显示出来

_AGAIN:

MOV AL,[ESI]

INC ESI

TEST AL,AL

JZ _NEXT

MOV GS:[EDI],AX

ADD EDI,2

JMP _AGAIN

_NEXT:

CALL DispReturn ;换行

CALL TextRead ;显示5MB处内存

CALL TextWrite ;写5MB处内存

CALL TextRead ;显示更新后的5MB处内存

;这里ES影子寄存器都有正确的界限和属性,所以不用修改

  

;跳转到16位段的保护模式 补充2

DB 0EAH

DW OFFSET PROTECTED16

DW 0

DW SelectorCode16

TextRead PROC

XOR ESI,ESI

MOV ECX,8

_LOOP:

MOV AL,ES:[ESI]

CALL DispAL

INC ESI

LOOPW _LOOP

CALL DispReturn

RET

TextRead ENDP

TextWrite PROC

PUSH ESI

PUSH EDI

XOR ESI,ESI

XOR EDI,EDI

MOV ESI,OFFSET StrTest

CLD

NUM1:

lodsb

test AL,AL

JZ NUM2

MOV ES:[EDI],AL

INC EDI

JMP NUM1

NUM2:

POP EDI

POP ESI

RET

TextWrite ENDP

DispAL PROC ;将AL中的数据用十六进制数(2个)显示

PUSH ECX

PUSH EDX

MOV AH,0CH

MOV DL,AL ;AL中是读出的字符

SHR AL,4 ;先处理高4位

MOV ECX,2

BEGIN:

AND AL,01111B

CMP AL,9

JA NUM1       ;AL中数据大于9转移

ADD AL,'0'    ;AL小于等于9(ASCII 9后面是“:” 不是字母A)

JMP NUM2

NUM1:

SUB AL,0AH     ;AL大于等于10就先减10

ADD AL,'A'

NUM2:

MOV GS:[EDI],AX

ADD EDI,2

MOV AL,DL

LOOP BEGIN

ADD EDI,2

POP EDX

POP ECX

RET

DispAL ENDP

DispReturn PROC

PUSH EAX

PUSH EBX

MOV EAX,EDI

MOV BL,160

DIV BL

AND EAX,0FFH

INC EAX

MOV BL,160

MUL BL

MOV EDI,EAX

POP EBX

POP EAX

RET

DispReturn ENDP

SegCodeLen32 = $ - 1

CSEG32 ENDS

;数据段GDT

DSEG SEGMENT USE16

GDT Descriptor <> ;空描述符

DES_CODE16   Descriptor <> ;16位代码段描述符

DES_CODE32   Descriptor <> ;32位代码段描述符

DES_VIDEO Descriptor <> ;显存段描述符

DES_STRING Descriptor <> ;读取段描述符

DES_OUT Descriptor <>    ;写入段描述符

DES_STACK Descriptor <> ;堆栈段描述符

GdtLen = $ - GDT ;GDT长度

FGDTR GDTR_STRUC<GdtLen,0> ;伪描述符

SELECTOR_BASE DD ?

SELECTOR_LEN DD ?

;GDT选择子

SelectorCode16 = DES_CODE16 - GDT ;16位代码段选择子

SelectorCode32 = DES_CODE32 - GDT ;32位代码段选择子

SelectorVideo = DES_VIDEO - GDT ;显存段选择子

SelectorString = DES_STRING - GDT   ;要显示的字符串所在数据段选择子

SelectorOut = DES_OUT - GDT ;5MB处读写数据段选择子

SelectorStack = DES_STACK - GDT ;堆栈段选择子

DSEG ENDS

;保存字符串数据段

DSEG_STR SEGMENT

PMMessage DB "In Protect Mode now. @_@",0

StrTest DB "ABCDEFGHIJKLMNOPQRSTUVWXYZ",0

DSEG_STR ENDS

DSEG_STACK SEGMENT

TEMP DB 512 DUP(0)

TopOfStack = $ - TEMP

DSEG_STACK ENDS

END START

----------------------------------- loader32.asm 完   -------------------------------------------

    先说下为什么回到实模式需要先回到16位段的保护模式下,再回到实模式。因为保护模式在32位段下,所以CS的段描述符高速缓冲寄存器中段属性的D位被置1,表示这是一个32位段。因为在实模式下无法修改段描述符高速缓冲寄存器中的段属性和界限,所以必须在回到实模式前修改,而修改的办法就是跳转到16位段。

补充1:这是一个跳转指令,在保护模式下从16位段跳转到32位段。因为是在保护模式下跳转,所以放入CS的是选择子SelectorCode32。因为PROTECTED32是32位段下的一个标号,32位段的段内偏移是32位,所以跳转中的偏移是DD OFFSET PROTECTED32。0EAH表示一个段间直接跳转,但是由于这是16位段下的跳转,它本应该是使用16位偏移的,而我们需要它使用32位偏移,所以在0EAH前一个字节加上操作数长度前缀66H。另外由于处理器在跳转到32位代码段的时候,如果偏移是16位,那么会在高16位补0,所以这里也可以改为:

DB 0EAH

DD OFFSET PROTECTED32

ORG $-2 ;因为这里OFFSET PROTECTED32没有超过0FFFFH,所以完全可以这样处理

DW SelectorCode32

补充2:这是32位代码段下的段间直接跳转,它需要32位偏移作操作数,而PROTECTED16是16位段下的标号,OFFSET PROTECTED16是16位,所以需要补16位的0。当然也可以使用操作数长度前缀:

DB 66H

DB 0EAH

DW OFFSET PROTECTED16

DW SelectorCode16



原文网址: http://hi.baidu.com/liuqizealot/blog/item/62ae48fbcd525563024f560c.html


操作系统实践4--建立并使用局部描述符表

2009-03-03 09:48

本篇建立了局部描述符表,并让1个32位代码段描述符位于局部描述符表中:

----------------------------- loaderL.asm --------------------------------------------

INCLUDE GENERAL.ASM ;GENERAL.ASM加了几个符号,见补充1

.386P

;16位代码段

CSEG16 SEGMENT USE16

ASSUME CS:CSEG16,DS:DSEG,ES:DSEGLDT

START:

MOV AX,LOADER_SEG+DSEG

MOV DS,AX

MOV AX,LOADER_SEG+DSEG_STACK

MOV SS,AX

MOV SP,TopOfStack

MOV AX,LOADER_SEG+DSEGLDT

MOV ES,AX

   

;伪描述符基地址赋值,界限定义时已设置

XOR EAX,EAX ;EAX清0

MOV AX,DS

SHL EAX,4

ADD EAX,OFFSET GDT ;EAX为GDT基地址

MOV FGDTR.BASE,EAX

;设置16位代码段描述符

XOR EAX,EAX ;EAX清0

MOV AX,CS

SHL EAX,4 ;EAX为DES_CODE16基地址

MOV SELECTOR_BASE,EAX

MOV SELECTOR_LEN,0FFFFH ;这里必须为0FFFFH

SetDescriptor <DES_CODE16>,SELECTOR_LEN,SELECTOR_BASE,ATCE

;设置32位代码段描述符

XOR EAX,EAX ;EAX清0

MOV AX,LOADER_SEG+CSEG32

SHL EAX,4 ;EAX为DES_CODE32基地址

MOV SELECTOR_BASE,EAX

MOV SELECTOR_LEN,SegCodeLen32

SetDescriptor <DES_CODE32>,SELECTOR_LEN,SELECTOR_BASE,ATCE+DA_32

;设置显存数据段描述符

MOV SELECTOR_BASE,0B8000H

MOV SELECTOR_LEN,0FFFFH

SetDescriptor <DES_VIDEO>,SELECTOR_LEN,SELECTOR_BASE,ATDW

;设置堆栈段描述符

XOR EAX,EAX

MOV AX,SS

SHL EAX,4

MOV SELECTOR_BASE,EAX

MOV SELECTOR_LEN,0FFFFH

SetDescriptor<DES_STACK>,SELECTOR_LEN,SELECTOR_BASE,ATDW

;设置LDT在GDT中的描述符

XOR EAX,EAX

MOV AX,LOADER_SEG+DSEGLDT

SHL EAX,4

MOV SELECTOR_BASE,EAX

MOV SELECTOR_LEN,SegLDTCodeLen

SetDescriptor<DES_LDT>,SELECTOR_LEN,SELECTOR_BASE,DA_LDT

    

;设置LDT中的描述符

XOR EAX,EAX

MOV AX,LOADER_SEG+CSEGLDT

SHL EAX,4

MOV SELECTOR_BASE,EAX

MOV SELECTOR_LEN,SegLDTCodeLen

SetDescriptor<ES:LDT_CSEG>,SELECTOR_LEN,SELECTOR_BASE,ATCE+DA_32

    

LGDT FGDTR ;加载GDTR

CLI ;关中断

ENABLEA20 ;打开地址线A20

;CRO的PE位置1,进入保护模式

MOV EAX,CR0

OR EAX,1

MOV CR0,EAX

;进入32位段下的保护模式

DB 66H

DB 0EAH

DD OFFSET PROTECTED32

DW SelectorCode32

PROTECTED16:

;CRO的PE位置0,回到实模式

MOV EAX,CR0

AND AL,0FEH

MOV CR0,EAX

;跳回实模式

DB 0EAH

DW OFFSET REAL  

DW LOADER_SEG

REAL:

MOV AX,LOADER_SEG+DSEG_STACK

MOV SS,AX

MOV AX,LOADER_SEG+DSEG

MOV DS,AX

MOV SP,TopOfStack

     

DISABLEA20 ;关闭地址线A20

STI ;开中断

;调用中断输出5个L,以示回到了实模式

MOV BH,0H

MOV AL,'L'

MOV BL,47H

MOV CX,5H

MOV AH,9

INT 10H

JMP $ ;程序到这里结束

SegCodeLen16 = $ - 1

CSEG16 ENDS

;32位代码段

CSEG32 SEGMENT

ASSUME CS:CSEG32,DS:DSEG

PROTECTED32:

;设置选择子

MOV AX,SelectorString

MOV DS,AX

MOV AX,SelectorVideo

MOV GS,AX

MOV AX,SelectorStack

MOV SS,AX

MOV ESP,TopOfStack

MOV AH,0CH

XOR EDI,EDI

MOV EDI,(80*10+0)*2

MOV ESI,OFFSET PMMessage

;将PMMessage显示出来

_AGAIN:

MOV AL,[ESI]

INC ESI

TEST AL,AL

JZ _NEXT

MOV GS:[EDI],AX

ADD EDI,2

JMP _AGAIN

_NEXT:

CALL DispReturn ;换行

;在使用LDT前必须将正确的选择子装入LDTR 补充2

MOV AX,SelectorLDT

LLDT AX

    

;跳转到LDT记录的32位代码段

DB 0EAH

DD OFFSET LDTSTART

DW SelectorCSEGLDT

DispReturn PROC

PUSH EAX

PUSH EBX

MOV EAX,EDI

MOV BL,160

DIV BL

AND EAX,0FFH

INC EAX

MOV BL,160

MUL BL

MOV EDI,EAX

POP EBX

POP EAX

RET

DispReturn ENDP

SegCodeLen32 = $ - 1

CSEG32 ENDS

;数据段GDT-----------------------------------------------------------

DSEG SEGMENT USE16

GDT Descriptor <> ;空描述符

DES_CODE16   Descriptor <> ;16位代码段描述符

DES_CODE32   Descriptor <> ;32位代码段描述符

DES_VIDEO Descriptor <> ;显存段描述符

DES_STACK Descriptor <> ;堆栈段描述符

DES_LDT Descriptor<> ;LDT在GDT中的描述符

FGDTR GDTR_STRUC<GdtLen,0> ;伪描述符

SELECTOR_BASE DD ?

SELECTOR_LEN DD ?

;GDT选择子

SelectorCode16 = DES_CODE16 - GDT ;16位代码段选择子

SelectorCode32 = DES_CODE32 - GDT ;32位代码段选择子

SelectorVideo = DES_VIDEO - GDT ;显存段选择子

SelectorStack = DES_STACK - GDT ;堆栈段选择子

SelectorLDT = DES_LDT - GDT ;记录LDT的选择子

GdtLen = $ - 1 ;GDT长度

DSEG ENDS

;保存字符串数据段------------------------------------------------

DSEG_STR SEGMENT

PMMessage DB "In Protect Mode now. @_@",0

StrTest DB "ABCDEFGHIJKLMNOPQRSTUVWXYZ",0

DSEG_STR ENDS

;堆栈段----------------------------------------------------------

DSEG_STACK SEGMENT

TEMP DB 512 DUP(0)

TopOfStack = $ - 1

DSEG_STACK ENDS

;LDT段-----------------------------------------------------------

DSEGLDT SEGMENT

LDT LABEL BYTE

LDT_CSEG Descriptor<> ;LDT记录的32位代码段描述符

SelectorCSEGLDT = LDT_CSEG - LDT + SA_TIL ;选择子TI位必须为1

LDTLen = $ - 1

DSEGLDT ENDS

;LDT记录的32位代码段-----------------------------------------------

CSEGLDT SEGMENT

LDTSTART:

MOV AX,SelectorVideo

MOV GS,AX

;输出1个Q

MOV EDI,(80*12+0)*2

MOV AH,0CH

MOV AL,'Q'

MOV GS:[EDI],AX

;跳转到16位段的保护模式,准备跳回实模式

DB 0EAH

DW OFFSET PROTECTED16

DW 0

DW SelectorCode16

SegLDTCodeLen = $ - 1

CSEGLDT ENDS

END START

----------------------------- loaderL.asm 完------------------------------------------

补充1:loaderL.asm中有两个符号DA_LDT和SA_TIL,它们在GENERAL.ASM中定义,需要新加:

DA_LDT = 82h ;局部描述符表段类型值

SA_TIL = 4    ;LDT中的描述符

补充2:这里的操作是将GDT中的LDT选择子装入LDTR。LDTR和GDTR完全不同,GDTR是48位直接记录的是32位基地址和16位段界限,而LDTR是16位记录的是LDT在GDT中的选择子。LDTR更像一个普通的段寄存器,都是装入一个16位的选择子,都有一个高速缓冲寄存器,高速缓冲寄存器都是记录的基地址,界限和属性。


原文网址: http://hi.baidu.com/liuqizealot/blog/item/30a2708de93c311ab31bba91.html


操作系统实践5--段特权级

2009-03-11 10:52

本篇只涉及段特权级的内容,关于各种门也只涉及调用门。
先看几个名词:
CPL(Current Privilege Level)当前特权级,由CS和 SS中的第0和第1位记录
DPL(Descriptor Privilege Level)描述符特权级,由描述符中段属性的第5和第6位记录
RPL(Requested Privilege Level)请求特权级,由选择子的第0和第1位记录
TSS(Task-State Segment)任务状态段,用于保存任务的相关信息。


现在一步一步来讨论特权级的比较:

    1. CPL表示的是程序或者说任务的当前特权级,它不属于某个段,当前的程序或任务不可能同时表现出2种特权级,那么CS和SS的第0位和第1位应该总是相同的。尝试将RPL异于CPL的数据段选择子装入SS会引起异常。另外后面在论述跳转的时候可以看到,它们总是相同的。

    2.DPL表示的是某个段或门的特权级,当程序要访问段A的时候,会将CPL和段A的DPL作比较,以确定程序是否有权限访问段A。

    3.RPL表示选择子是否有权限访问其所指向的段。选择子指向一个段描述符,段描述符指向一个段,段的特权级由段描述符中的DPL决定。当程序需要访问段A的时候,需要先通过段A的选择子加载段A的段描述符,然后才能访问段A,在这个过程中CPU会先将段A选择子中的RPL和段A描述符中的DPL作比较,确定选择子是否有权限访问其所指向的段,成功后才是CPL和段A的DPL的比较。

    RPL、 DPL之间的比较规则与CPL、DPL之间的比较规则一致。举个例子解释前面这句话的意思:比如当前程序要访问一个数据段A,那么CPL不能大于段A的DPL,否则失败(后面会讲到这一点)。这是CPL和段A的DPL的比较规则,那么同样段A的RPL、DPL也遵循这样的规则,也就是段A的RPL不能大于段A的DPL。

    RPL和CPL不会进行比较。

    4.不管什么情况下,相同特权级之间的访问总是不会错的,所以后面的讨论中通常会忽略相同特权级之间的比较。

    5.保护模式下代码段中可以存放数据,但数据段中不能存放代码(跳转不过去)。所以代码段既可以获取代码段中的数据(被读的代码段属性需要可读,就算是获取自身段内数据也需要可读),也可以获取数据段中的数据(数据段总是可读的),还可以跳转到其他代码段。而数据段除了被读取,什么都做不了。

    6.代码段分为一致代码段和非一致代码段,这会对特权级比较产生影响,是否一致由段描述符的第42位决定。数据段和代码不同,它总是非一致的。代码段只在作为被访问一方(或者说目标代码段)时一致性才会对特权级比较产生影响,在作为访问一方时没有区别。是否一致代码段的区别是,在段间跳转过程中,如果目标代码段是一个特权级更高的一致代码段,那么跳转成功,并且CPL不会改变(CS和SS都不会变),于是CPL异于目标代码段的RPL(CPL数值更大,特权级更低);如果目标代码段是一个特权级更高的非一致代码段,那么跳转是会失败的,此时需要使用调用门。另外,如果目标代码段是一个特权级更低的代码段(不论是否一致),那么跳转总是会失败的,除非使用RETF跳转。

    7.如果当前程序要访问一个数据段A,那么CPL不能大于段A的DPL,否则失败。也就是说当前程序不能访问特权级更高的数据段。CPL可以小于段A的DPL,也就是说当前程序可以访问特权级更低的数据段。当前程序对调用门和TSS的访问规则与此一致。

    8.特权级的检查是在选择子被装入段寄存器的时候进行的。先看相对简单一点的数据段A选择子装入DS的情况(装入ES,FS,GS类似,这里以DS为例说明):段A的DPL必须大于RPL,同时还必须大于CPL(第7点),否则产生异常,段A的RPL不和CPL进行比较。

另外,当程序向低特权级跳转时,会检查CPL和DS指向的段的DPL,如果DPL小于CPL,那么DS会被加载空描述符的选择子。

    9.数据段A选择子装入SS的情况(只有数据段选择子才能装入SS,代码段选择子不能装入SS,不管是否可读):段A的RPL和DPL都必须和CPL相等,否则失败。
    来看下为什么会这样。CPU要保证CS和SS中的第0和第1位(CPL)一致,所以段A的RPL必须等于CPL。CPU还要保证当前程序的特权级和当前使用的堆栈段的特权级一致,所以段A的DPL要等于CPL。

    10.代码段A选择子装入DS的情况(装入ES,FS,GS类似,这里以DS为例说明):如果段A不可读,装入失败。如果段A是可读的非一致代码段,那么CPL、段A的RPL都必须等于段A的DPL,否则装入就会失败。如果段A是可读的一致代码段,那么CPL、段A的RPL都可以大于段A的DPL,但不能小于段A的DPL。
    代码段A用段超越前缀CS读取自身段内数据的时候,虽然没有选择子的装入过程,但CPU会检查段A是否可读,如果不可读会产生异常。

    11.最后是代码段的段间跳转。段间跳转可以用JMP、CALL、RETF和调用门。从高特权级到低特权级只能用RETF;从低特权级到高特权级非一致代码段,只能用调用门,而且还必须是用CALL指令来使用调用门;从低特权级到高特权级一致代码段JMP、CALL、调用门都可以。
    从低特权级到高特权级一致代码段和同特权级的段间跳转没什么区别,无须多说。这里通过代码来了解从高特权级到低特权级,以及从低特权级到高特权级非一致代码段。

general.asm添加、修改了一些内容:

================================= general.asm ======================================

LOADER_SEG EQU 5000H   ;程序加载后,所在内存段值

;---------------------------------------------------------------

; 描述符类型值说明

; 其中:

;       DA_ : Descriptor Attribute

;       D    : 数据段

;       C    : 代码段

;       S    : 系统段

;       R    : 只读

;       RW   : 读写

;       A    : 已访问

;       其它 : 可按照字面意思理解

DA_32 = 4000H ;32位段

;DPL

DA_DPL0 = 00H

DA_DPL1 = 20H

DA_DPL2 = 40H

DA_DPL3 = 60H

;---------------------------------------------------------------

;---------------------------------------------------------------

; 存储段描述符类型值说明

DA_DR    = 90H ;存在的只读数据段类型值

DA_DRW   = 92H ;存在的可读写数据段属性值

DA_DRWA = 93H ;存在的已访问可读写数据段类型值

DA_C     = 98H ;存在的只执行代码段属性值

DA_CR    = 9AH ;存在的可执行可读代码段属性值

DA_CCO   = 9CH ;存在的只执行一致代码段属性值

DA_CCOR = 9EH ;存在的可执行可读一致代码段属性值

;--------------------------------------------------------------

;--------------------------------------------------------------

; 系统段描述符类型值说明

DA_LDT        = 82H ;局部描述符表段类型值

DA_TaskGate   = 85H ;任务门类型值

DA_386TSS     = 89H ;可用 386 任务状态段类型值

DA_386CGate   = 8CH ;386 调用门类型值

DA_386IGate   = 8EH ;386 中断门类型值

DA_386TGate   = 8FH ;386 陷阱门类型值

;--------------------------------------------------------------

;--------------------------------------------------------------

;选择子类型值说明

; SA_ 表示Selector Attribute

;RPL

SA_RPL0 = 0

SA_RPL1 = 1

SA_RPL2 = 2

SA_RPL3 = 3

;TI

SA_TIG = 0    ;GDT中的描述符

SA_TIL = 4    ;LDT中的描述符

;-----------------------------------------------------------------

;GDTR用伪描述符结构

GDTR_STRUC STRUC

           LIMIT DW 0 ;低16位段界限

           BASE DD 0 ;高32位基地址

GDTR_STRUC ENDS

                 

;Descriptor 段描述符 8字节

;Base 段基地址,32位,双字DD

;Limit 段界限,20位,双字DD低20位

;Attr 段属性,12位,字DW,其中高字节的低4位始终为0(段界限的高4位)

Descriptor STRUC

            LimitL      DW 0 ;段界限1 (0~15)

            BaseL       DW 0 ;段基地址1 (0~15)

            BaseM       DB 0 ;段基地址2 (16~23)

            Attribute   DW 0    ;属性1 + 段界限2 +属性2

            BaseH       DB 0 ;段基地址3 (23~31)

Descriptor ENDS

;设置段描述符,该宏和以前不同,使用该宏之前,将32位基地址放入EAX,20位界限放入EDX

;Attr为常数(高字节的低4位始终为0),注意EAX,EDX的值会被改变

SetDescriptor MACRO DesName,Attr

              MOV &DesName.LimitL,DX ;LimitL赋值

              MOV &DesName.BaseL,AX ;BaseL赋值

              SHR EAX,16

              MOV &DesName.BaseM,AL ;BaseM赋值

              MOV &DesName.Attribute,Attr     ;Attr赋值

              MOV EDX,16

              OR   BYTE PTR &DesName.Attribute+1,DL ;Limit 16~19位赋值

              MOV &DesName.BaseH,AH ;BaseH赋值

ENDM

;门描述符结构体,8字节

GateDes STRUC

        OffsetL   DW 0 ;32位偏移的低16位

        Selector DW 0 ;选择子

        PCount    DB 0 ;参数计数

        GType     DB 0 ;类型

        OffsetH   DW 0 ;32位偏移的高16位

GateDes ENDS

;设置门描述符,使用该宏之前,将32位偏移放入EAX,EAX会被改变

;GATE_SELECTOR为选择子,GATE_COUNT为参数计数,GATE_TYPE为类型

SetGateDes MACRO GDesName,GATE_SELECTOR,GATE_COUNT,GATE_TYPE

           MOV GDesName.OffsetL,AX ;OffsetL赋值

          

           MOV GDesName.Selector,GATE_SELECTOR ;Selector赋值

           MOV GDesName.PCount,GATE_COUNT ;PCount赋值

          

           MOV GDesName.GType,GATE_TYPE ;GType赋值

          

           SHR EAX,16

           MOV GDesName.OffsetH,AX ;OffsetH赋值         

ENDM

ENABLEA20 MACRO

          PUSH AX

          IN AL,92H

          OR AL,2

          OUT 92H,AL

          POP AX

ENDM

DISABLEA20 MACRO

           PUSH AX

           IN AL,92H

           AND AL,0FDH

           OUT 92H,AL

           POP AX

ENDM

=================================== general.asm 完 =================================

PLevel.asm先通过RETF从高特权级到低特权级,然后通过调用门从低特权级到高特权级非一致代码段

=================================== PLevel.asm =====================================

INCLUDE GENERAL.ASM

.386P

;16位代码段

CSEG16 SEGMENT USE16

ASSUME CS:CSEG16,DS:DSEG

START:

MOV AX,LOADER_SEG+DSEG

MOV DS,AX

MOV AX,LOADER_SEG+DSEG_STACK

MOV SS,AX

MOV SP,TopOfStack

   

;伪描述符基地址赋值,界限定义时已设置

XOR EAX,EAX ;EAX清0

MOV AX,DS

SHL EAX,4

ADD EAX,OFFSET GDT

MOV FGDTR.BASE,EAX

;设置32位代码段描述符

XOR EAX,EAX ;EAX清0

MOV AX,LOADER_SEG + CSEG32

SHL EAX,4

MOV EDX,SegCodeLen32

SetDescriptor <DES_CODE32>,DA_C + DA_32

;设置显存数据段描述符,因为32位RING2代码段要用到该段,所以这里DPL设置为2

MOV EAX,0B8000H

MOV EDX,0FFFFH

SetDescriptor <DES_VIDEO>,DA_DRW + DA_DPL2

;设置堆栈段描述符

XOR EAX,EAX

MOV AX,SS

SHL EAX,4

MOV EDX,0FFFFH

SetDescriptor<DES_STACK>,DA_DRW + DA_32

;设置调用门描述符

XOR EAX,EAX

SetGateDes<DES_GATE>,SelectorGateCode,0,DA_386CGate + DA_DPL2

;设置调用门代码段描述符,就是通过调用门跳转到的高特权级非一致代码段

XOR EAX,EAX

MOV AX,LOADER_SEG + CGATE

SHL EAX,4

MOV EDX,GateLen

SetDescriptor<DES_GATE_CODE>,DA_C + DA_32 + DA_DPL1

;设置TSS描述符

XOR EAX,EAX

MOV AX,LOADER_SEG + DTSS

SHL EAX,4

MOV EDX,TSSLen

SetDescriptor<DES_TSS>,DA_386TSS

;设置32位RING2代码段描述符

XOR EAX,EAX

MOV AX,LOADER_SEG+CSEGR2

SHL EAX,4

MOV EDX,SegCodeRING2Len

SetDescriptor<DES_RING2>,DA_C + DA_DPL2 + DA_32

;设置RING1堆栈段描述符

XOR EAX,EAX

MOV AX,LOADER_SEG+STACK_R1

SHL EAX,4

MOV EDX,TopOfStackR1

SetDescriptor<DES_STACK_R1>,DA_DRWA + DA_DPL1 + DA_32

;设置RING2堆栈段描述符

XOR EAX,EAX

MOV AX,LOADER_SEG+STACK_R2

SHL EAX,4

MOV EDX,TopOfStackR2

SetDescriptor<DES_STACK_R2>,DA_DRWA + DA_DPL2 + DA_32

   

LGDT FGDTR ;加载GDTR

CLI ;关中断

ENABLEA20 ;打开地址线A20

;CRO的PE位置1,进入保护模式

MOV EAX,CR0

OR EAX,1

MOV CR0,EAX

;进入32位段下的保护模式

DB 66H

DB 0EAH

DD OFFSET PROTECTED32

DW SelectorCode32

SegCodeLen16 = $ - 1

CSEG16 ENDS

;32位代码段

CSEG32 SEGMENT

ASSUME CS:CSEG32,DS:DSEG

PROTECTED32:

;设置选择子

MOV AX,SelectorVideo

MOV GS,AX

MOV AX,SelectorStack

MOV SS,AX

MOV ESP,TopOfStack

;在使用调用门变换特权级前必须加载TSS选择子到TSS寄存器TR

;因为这里TSS的DPL被设置为0,所以必须在RING0段下执行LTR指令

MOV AX,SelectorTSS

LTR AX

;将SS ESP CS EIP依次入栈    补充

PUSH SelectorStackR2

PUSH TopOfStackR2

PUSH SelectorCodeR2

PUSH 0

RETF ;跳转到32位RING2代码段

   

SegCodeLen32 = $ - 1

CSEG32 ENDS

;调用门目标32位代码段--------------------------------------------------

CGATE SEGMENT

MOV AX,SelectorVideo

MOV GS,AX

MOV EDI,(80*1+0)*2 ;输出一个T

MOV AH,0CH

MOV AL,'T'

MOV GS:[EDI],AX

RETF ;返回

GateLen = $ - 1

CGATE ENDS

;32位RING2代码段-----------------------------------------------------

CSEGR2 SEGMENT

MOV AX,SelectorVideo

MOV GS,AX

   

MOV EDI,(80*0+0)*2 ;输出一个Z

MOV AH,0CH

MOV AL,'Z'

MOV GS:[EDI],AX

;调用门

DB 9AH

DD 0

DW SelectorGate

JMP $ ;调用门代码段返回后结束程序

SegCodeRING2Len = $ - 1

CSEGR2 ENDS

;描述符数据段-----------------------------------------------------------

DSEG SEGMENT

GDT            Descriptor <> ;空描述符

DES_CODE32     Descriptor <> ;32位代码段描述符

DES_VIDEO      Descriptor <> ;显存段描述符

DES_STACK      Descriptor <> ;堆栈段描述符

DES_STACK_R1   Descriptor <> ;RING1堆栈段描述符

DES_STACK_R2   Descriptor <> ;RING2堆栈段描述符

DES_TSS        Descriptor <> ;TSS描述符

DES_GATE_CODE Descriptor <> ;调用门目标段描述符

DES_RING2      Descriptor <> ;32位RING2代码段描述符

DES_GATE       GateDes    <> ;调用门描述符

FGDTR GDTR_STRUC<0FFFFH,0> ;伪描述符

;选择子

SelectorCode32    EQU DES_CODE32 - GDT ;32位代码段选择子

SelectorVideo     EQU DES_VIDEO - GDT ;显存段选择子

SelectorStack     EQU DES_STACK - GDT ;堆栈段选择子

SelectorTSS       EQU DES_TSS    - GDT ;TSS选择子

SelectorGateCode EQU DES_GATE_CODE - GDT + SA_RPL1 ;调用门目标段选择子

SelectorGate      EQU DES_GATE      - GDT + SA_RPL2 ;调用门选择子

SelectorCodeR2    EQU DES_RING2     - GDT + SA_RPL2 ;32位RING2代码段选择子

SelectorStackR1   EQU DES_STACK_R1 - GDT + SA_RPL1 ;RING1堆栈段选择子

SelectorStackR2   EQU DES_STACK_R2 - GDT + SA_RPL2 ;RING2堆栈段选择子

GdtLen = $ - 1 ;GDT长度

DSEG ENDS

;堆栈段----------------------------------------------------------

DSEG_STACK SEGMENT

TEMP DB 512 DUP(0)

TopOfStack = $ - 1

DSEG_STACK ENDS

;RING1堆栈段-----------------------------------------------------

STACK_R1 SEGMENT

DB 512 DUP(0)

TopOfStackR1 = $ - 1

STACK_R1 ENDS

;RING2堆栈段-----------------------------------------------------

STACK_R2 SEGMENT

DB 512 DUP(0)

TopOfStackR2 = $ - 1

STACK_R2 ENDS

;TSS-------------------------------------------------------------

DTSS SEGMENT

DD 0

DD TopOfStack     ;0级堆栈

DD SelectorStack

DD TopOfStackR1   ;1级堆栈

DD SelectorStackR1

DD TopOfStackR2   ;2级堆栈

DD SelectorStackR2

DD 0              ;CR3

DD 0              ;EIP

DD 0              ;EFLAGS

DD 0              ;EAX

DD 0              ;ECX

DD 0              ;EDX

DD 0              ;EBX

DD 0              ;ESP

DD 0              ;EBP

DD 0              ;ESI

DD 0              ;EDI

DD 0              ;ES

DD 0              ;CS

DD 0              ;SS

DD 0              ;DS

DD 0              ;FS

DD 0              ;GS

DD 0              ;LDT

DW 0              ;调试陷阱标志

DW 0              ;I/O位图基址

DB 0FFH           ;I/O位图结束标志

TSSLen = $ - 1

DTSS ENDS

END START

=================================== PLevel.asm 完 ==================================

补充:32位段下CALL指令入栈CS和SS时是以32位进行的(高16位补0),RETF指令出栈CS和SS也是以32位进行的(高16位被丢弃),所以这里依次入栈了4个32位的操作数。
原文网址: http://hi.baidu.com/liuqizealot/blog/item/97aedaf9a1d47a51242df2cc.html


操作系统实践6--启用内存分页

2009-03-17 09:44

general.asm中添加的符号

----------------------------------------------------------------------------------------------------------------------------------

PageDirBase EQU 200000H ;页目录开始地址:2MB
PageTblBase EQU 201000H ;页表开始地址:2MB+4KB

DA_G1 = 8000H ;段界限粒度4K

PG_P    EQU 1 ;页存在属性位
PG_RWR EQU 0 ;R/W 属性位值, 读/执行
PG_RWW EQU 2 ;R/W 属性位值, 读/写/执行
PG_USS EQU 0 ;U/S 属性位值, 系统级
PG_USU EQU 4 ;U/S 属性位值, 用户级

-----------------------------------------------------------------------------------------------------------------------------------

下面是page.asm

------------------------------------------------ page.asm -----------------------------------------------------------------

INCLUDE GENERAL.ASM
.386P

;16位代码段===============================================================
CSEG16 SEGMENT USE16
ASSUME CS:CSEG16,DS:DGDT,ES:DSEG
START:
MOV AX,LOADER_SEG + DGDT
MOV DS,AX

MOV AX,LOADER_SEG + DSEG
MOV ES,AX

MOV AX,LOADER_SEG + DSEG_STACK
MOV SS,AX
MOV SP,TopOfStack

;获取内存大小------------------------------------------------------------
MOV DI,ARDStruct
MOV EBX,0
MOV EDX,0534D4150H
MOV ECX,20

GetMemInfo:
MOV EAX,0E820H ;INT 15H调用成功后EAX被改变,所以每次都需要赋值
INT 15H

JC GetMemInfoFail ;CF=1获取内存信息出错

CMP ddType,1 ;Type为1,才是OS可用的内存
JNZ CHECK_EBX

MOV EAX,ddBaseAddrLow
ADD EAX,ddLengthLow ;此时EAX中为当前ARDS表示的内存上限
CMP EAX,ddMemSize
JBE CHECK_EBX ;如果EAX比ddMemSize小,就不能改变ddMemSize的值,如果相等没必要改变(应该不会相等)

MOV ddMemSize,EAX ;只有当EAX大于ddMemSize的时候才改变

CHECK_EBX:
CMP EBX,0
JZ GetMemInfoDone ;EBX为0,获取内存信息结束

JMP GetMemInfo

GetMemInfoFail:
MOV ddMemSize,0

GetMemInfoDone: ;获取内存大小结束,ddMemSize中就是内存大小

;伪描述符基地址赋值,界限定义时已设置-----------------------------------
XOR EAX,EAX ;EAX清0
MOV AX,DS
SHL EAX,4
ADD EAX,OFFSET GDT
MOV FGDTR.BASE,EAX

;设置32位代码段描述符--------------------------------------------
XOR EAX,EAX ;EAX清0
MOV AX,LOADER_SEG + CSEG32
SHL EAX,4
MOV EDX,SegCodeLen32
SetDescriptor <DES_CODE32>,DA_C + DA_32

;设置显存数据段描述符-----------------------------------------------
MOV EAX,0B8000H
MOV EDX,0FFFFFH
SetDescriptor <DES_VIDEO>,DA_DRW

;设置堆栈段描述符-------------------------------------------------
XOR EAX,EAX
MOV AX,SS
SHL EAX,4
MOV EDX,0FFFFFH
SetDescriptor<DES_STACK>,DA_DRW + DA_32

;设置数据段描述符
XOR EAX,EAX
MOV AX,LOADER_SEG + DSEG
SHL EAX,4
MOV EDX,0FFFFFH
SetDescriptor<DES_DSEG>,DA_DRW + DA_32

;设置页目录表和页表所在数据段描述符
MOV EAX,PageDirBase ;放在内存地址2M开始处
MOV EDX,1025 ;页表和页目录表最多只可能占用4M+4KB空间
SetDescriptor <DES_PAGING>,DA_DRW + DA_32 + DA_G1 ;粒度位置1,因为可能会超过1M

LGDT FGDTR ;加载GDTR

CLI ;关中断

ENABLEA20 ;打开地址线A20

;CRO的PE位置1,进入保护模式
MOV EAX,CR0
OR EAX,1
MOV CR0,EAX

;进入32位段下的保护模式
DB 66H
DB 0EAH
DD OFFSET PROTECTED32
DW SelectorCode32

SegCodeLen16 = $ - 1
CSEG16 ENDS


;32位代码段===============================================================
CSEG32 SEGMENT
ASSUME CS:CSEG32,DS:DSEG
PROTECTED32:
;加载选择子
MOV AX,SelectorVideo
MOV GS,AX

MOV AX,SelectorStack
MOV SS,AX
MOV ESP,TopOfStack

MOV AX,SelectorDseg
MOV DS,AX

;初始化页目录表和页表--------------------------------------------------
XOR EDX,EDX ;首先计算需要初始化多少个PDE,每个PDE对应一个页表,所以也是页表数
MOV EAX,ddMemSize
MOV EBX,400000H ;4M,一个页表可以表示4M内存空间
DIV EBX
MOV ECX,EAX ;此时ECX为页表个数
TEST EDX,EDX ;如果EDX不为0,ECX需要加1
JZ NO_REMAINDER
INC ECX

NO_REMAINDER:
PUSH ECX ;暂存个数

;先初始化页目录
CLD
MOV AX,SelectorPaging
MOV ES,AX
XOR EDI,EDI
XOR EAX,EAX
MOV EAX,PageTblBase + PG_P + PG_USU + PG_RWW

PDE_AGAIN:
STOSD
ADD EAX,4096 ;每个页表占用4KB(4096BYTE)空间
LOOP PDE_AGAIN

MOV EDI,1000H ;页表是从201000H开始,而页目录表不一定有1024个PDE

;再出示化所有页表(每个页表1024个PET,表示4M内存空间)
POP EAX ;页表个数
MOV EBX,1024 ;每个页表1024个PET
MUL EBX
MOV ECX,EAX ;PET个数=页表数*1024,这里完全可以认为EDX为0
XOR EAX,EAX
MOV EAX,PG_P + PG_USU + PG_RWW ;线性地址对应相等的物理地址,也没有考虑不可用内存

PTE_AGAIN:
STOSD
ADD EAX,4096 ;每一页指向4KB(4096BYTE)空间
LOOP PTE_AGAIN

MOV EAX,PageDirBase
MOV CR3,EAX ;设置CR3,指向页目录表
MOV EAX,CR0
OR EAX,80000000H
MOV CR0,EAX ;将CR0的最高位(第31位,PG)置1,启用分页机制

MOV EDI,(80*5+0)*2 ;输出1个L
MOV AH,0CH
MOV AL,'L'
MOV GS:[EDI],AX
JMP $ ;程序到此结束

SegCodeLen32 = $ - 1
CSEG32 ENDS


;全局描述符表数据段=======================================================
DGDT SEGMENT
GDT            Descriptor <> ;空描述符
DES_CODE32     Descriptor <> ;32位代码段描述符
DES_VIDEO      Descriptor <> ;显存段描述符
DES_STACK      Descriptor <> ;堆栈段描述符
DES_PAGING     Descriptor <> ;页目录表和页表所在段描述符
DES_DSEG       Descriptor <> ;数据段描述符

FGDTR GDTR_STRUC<0FFFFH,0> ;伪描述符

;选择子
SelectorCode32    EQU DES_CODE32 - GDT ;32位代码段选择子
SelectorVideo     EQU DES_VIDEO - GDT ;显存段选择子
SelectorStack     EQU DES_STACK - GDT ;堆栈段选择子
SelectorPaging    EQU DES_PAGING - GDT ;页目录表和页表所在段选择子
SelectorDseg      EQU DES_DSEG   - GDT ;数据段选择子

GdtLen = $ - 1 ;GDT长度
DGDT ENDS


;数据段===============================================================
DSEG SEGMENT
ARDStruct:   ;地址范围描述符结构(Address Range Descriptor Structure)
ddBaseAddrLow   DD 0
ddBaseAddrHigh DD 0
ddLengthLow     DD 0
ddLengthHigh    DD 0
ddType          DD 0

ddMemSize DD 0 ;内存大小

DSEG ENDS


;堆栈段===============================================================
DSEG_STACK SEGMENT
TEMP DB 512 DUP(0)
TopOfStack = $ - 1
DSEG_STACK ENDS

END START

-------------------------------------------------- page.asm 完 -----------------------------------------------


原文网址: http://hi.baidu.com/liuqizealot/blog/item/8aa96d0625b72373020881a8.html


操作系统实践7--保护模式下的中断处理

2009-03-23 14:50

这里以IRQ0(时钟中断)为例说一下保护模式下的中断响应过程,我们假定中断响应过程中不会有错误出现,于是忽略一些检测过程:

1.当产生一个IRQ0,首先由中断屏蔽寄存器IMR中的数据(由写入的OCW1决定)判断是否响应该中断,另外还会判断EOI,如果EOI为0,也不会响应中断;

2.如果步骤1中的判断都是响应,那么中断向量号经由INTR传到CPU(这里中断向量号为20H,可通过向主8259A,端口21H写入ICW2设置IRQ0的中断向量号);

3.CPU接收到中断请求后,根据IF位判断是否响应中断,如果响应,那么CPU会将EOI置0,将IF置0;

4.CPU从中断描述符表寄存器IDTR中获取中断描述表IDT基地址,并根据中断向量号取得响应的中断门描述符(中断向量号乘以8H就是要找的描述符在IDT中的偏移,这里是20H*8H),然后从中断门描述符中取得中断处理程序的选择子和偏移,转移到中断处理程序;

5.中断处理程序返回的时候,CPU自动将IF标志置1,EOI视情况而定,本例中需要手动将其置1;

6.中断处理结束。由上可知在中断响应过程中,CPU不会响应其他的中断。

下面是保护模式下中断处理示例代码interrup.asm
---------------------------- interrup.asm ------------------------------------------
INCLUDE GENERAL.ASM ; GENERAL.ASM没有变
.386P

;16位代码段===============================================================
CSEG16 SEGMENT USE16
ASSUME CS:CSEG16,DS:DGDT
START:
MOV AX,LOADER_SEG + DGDT
MOV DS,AX

MOV AX,LOADER_SEG + DSEG_STACK
MOV SS,AX
MOV SP,TopOfStack

;伪描述符基地址赋值,界限定义时已设置-----------------------------------
XOR EAX,EAX ;EAX清0
MOV AX,DS
SHL EAX,4
ADD EAX,OFFSET GDT
MOV FGDTR.BASE,EAX

;设置32位代码段描述符--------------------------------------------
XOR EAX,EAX ;EAX清0
MOV AX,LOADER_SEG + CSEG32
SHL EAX,4
MOV EDX,SegCodeLen32
SetDescriptor <DES_CODE32>,DA_C + DA_32

;设置显存数据段描述符-----------------------------------------------
MOV EAX,0B8000H
MOV EDX,0FFFFH
SetDescriptor <DES_VIDEO>,DA_DRW

;设置堆栈段描述符-------------------------------------------------
XOR EAX,EAX
MOV AX,SS
SHL EAX,4
MOV EDX,0FFFFFH
SetDescriptor<DES_STACK>,DA_DRW + DA_32

;设置时钟中断门描述符----------------------------------------------
MOV EAX,OFFSET INTERRUPT_CLOCK
SetGateDes<DES_INTER>,SelectorCode32,0,DA_386IGate

;设置示例中断门描述符----------------------------------------------
MOV EAX,OFFSET INTERRUPT_21H
SetGateDes<DES_INT21H>,SelectorCode32,0,DA_386IGate
   
LGDT FGDTR ;加载GDTR

CLI ;关中断

;加载IDTR
XOR EAX,EAX
MOV AX,LOADER_SEG + DGDT
SHL EAX,4
ADD EAX,OFFSET _IDT ;EAX中是IDT基地址
MOV FGDTR.BASE,EAX ;这里将FGDTR重复利用
LIDT FGDTR ;加载IDTR

ENABLEA20 ;打开地址线A20

;CRO的PE位置1,进入保护模式
MOV EAX,CR0
OR EAX,1
MOV CR0,EAX

;进入32位段下的保护模式
DB 66H
DB 0EAH
DD OFFSET PROTECTED32
DW SelectorCode32

SegCodeLen16 = $ - 1
CSEG16 ENDS


;32位代码段===============================================================
CSEG32 SEGMENT
ASSUME CS:CSEG32
PROTECTED32:
;加载选择子
MOV AX,SelectorVideo
MOV GS,AX

MOV AX,SelectorStack
MOV SS,AX
MOV ESP,TopOfStack

;设置可编程中断控制器8259A----------------------------------------------
;这里必须严格按顺序进行
MOV AL,011H
OUT 020H,AL ;主8259A,ICW1
CALL IO_DELAY

OUT 0A0H,AL ;从8259A,ICW1
CALL IO_DELAY

MOV AL,020H ;IRQ0对应中断向量20H(IRQ1对应21H….)
OUT 021H,AL ;主8259A,ICW2
CALL IO_DELAY

MOV AL,028H ;IRQ8对应中断向量28H(IRQ9对应29H…)
OUT 0A1H,AL ;从8259A,ICW2
CALL IO_DELAY

MOV AL,004H ;IR2对应从8259A
OUT 021H,AL ;主8259A,ICW3
CALL IO_DELAY

MOV AL,002H ;对应主8259A的IR2
OUT 0A1H,AL ;从8259A,ICW3
CALL IO_DELAY

MOV AL,001H
OUT 021H,AL ;主8259A,ICW4
CALL IO_DELAY

OUT 0A1H,AL ;从8259A,ICW4
CALL IO_DELAY

MOV AL,11111110B ;屏蔽主8259A除了时钟外的所有中断
OUT 021H,AL ;主8259A,OCW1被写入中断屏蔽寄存器IMR
CALL IO_DELAY

MOV AL,11111111B ;屏蔽从8259A所有中断
OUT 0A1H,AL ;从9259A,OCW1 同上
CALL IO_DELAY

INT 21H ;21H号中断,在屏幕左上角显示一个L
STI ;开中断
JMP $ ;程序到此结束


INTERRUPT_CLOCK: ;时钟中断,变换由21H显示的L
INC BYTE PTR GS:[DI] ;每次将左上角显示字符的ASCII码加1

MOV AL,20H
OUT 20H,AL ;发送EOI给8259A,才能继续接收中断

IRETD ;中断返回,保护模式下的中断返回是IRETD,不能是IRET


INTERRUPT_21H: ;21H号中断,在屏幕左上角显示一个L
MOV AH,0CH
MOV AL,'L'
MOV DI,(80*0+0)*2
MOV GS:[DI],AX

IRETD ;中断返回


IO_DELAY PROC ;等待I/O操作完成
NOP
NOP
NOP
NOP
RET
IO_DELAY ENDP

SegCodeLen32 = $ - 1
CSEG32 ENDS


;全局描述符表数据段=======================================================
DGDT SEGMENT
GDT            Descriptor <> ;空描述符
DES_CODE32     Descriptor <> ;32位代码段描述符
DES_VIDEO      Descriptor <> ;显存段描述符
DES_STACK      Descriptor <> ;堆栈段描述符

_IDT: ;中断描述符表,为简便放到了GDT数据段里面
DB 20H*8H DUP(0) ;本例程只处理IRQ0时钟中断,对应中断向量20H,之前有20H个中断门描述符,占用20H*8H字节
DES_INTER      GateDes    <> ;时钟中断门描述符
DES_INT21H     GateDes    <> ;示例中断门描述符,为简便作为21H号中断

FGDTR GDTR_STRUC<0FFFFH,0> ;伪描述符

;选择子
SelectorCode32    EQU DES_CODE32 - GDT ;32位代码段选择子
SelectorVideo     EQU DES_VIDEO - GDT ;显存段选择子
SelectorStack     EQU DES_STACK - GDT ;堆栈段选择子

GdtLen = $ - 1 ;GDT长度
DGDT ENDS


;堆栈段===============================================================
DSEG_STACK SEGMENT
TEMP DB 512 DUP(0)
TopOfStack = $ - 1
DSEG_STACK ENDS

END START

---------------------------- interrup.asm 完 ----------------------------------------
原文网址: http://hi.baidu.com/liuqizealot/blog/item/308568605e03bf4cebf8f8be.html


操作系统实践8--elf文件格式

2009-04-19 16:53

ELF文件由4部分组成:ELF头(ELF header)、程序头表(Program header table)、节(Sections)和节头表(Section header table)

一.ELF头(ELF header)

它位于EFL文件的最开始处(偏移为0),格式如下:

#define EI_NIDENT 16

typedef struct{

unsigned char e_ident[EI_NIDENT];

Elf32_Half e_type;

Elf32_Half e_machine;

Elf32_Word e_version;

Elf32_Addr e_entry;

Elf32_Off e_phoff;

Elf32_Off e_shoff;

Elf32_Word e_flags;

Elf32_Half e_ehsize;

Elf32_Half e_phentsize;

Elf32_Half e_phnum;

Elf32_Half e_shentsize;

Elf32_Half e_shnum;

Elf32_Half e_shstrndx;

}Elf32_Ehdr;

其中的数据类型:

名称                       大小           对其             用途

========              =====      =====     ==============

Elf32_Addr               4               4          无符号程序地址

Elf32_Half                  2               2          无符号中等大小整数

Elf32_Off                    4               4          无符号文件偏移

Elf32_Sword               4               4          有符号大整数

Elf32_Word                4              4          无符号大整数

unsigned char             1             1          无符号小整数

Elf32_Ehdr各项含义如下:

e_ident

它的各位依次称为EI_MAG0、EI_MAG1、EI_MAG2、EI_MAG3、EI_CLASS、EI_DATA、 EI_VERSION、EI_PAD。

第0,1,2,3个字节是Magic Number(EI_MAG0、EI_MAG1、EI_MAG2、EI_MAG3),其取值必须依次是0x7f、'E'、'L'、'F',表明这是一个ELF格式的二进制文件。

第4个字节(EI_CLASS)用来确定文件的类型或者说是能力,可能的取值为

ELFCLASSNONE       0 Invalid class

ELFCLASS32         1 32-bit objects

ELFCLASS64         2 64-bit objects

只有当EI_CLASS为ELFCLASS32才使用上面的Elf32_Addr…这些数据类型。

第5个字节(EI_DATA)给出字节序特性,可能的取值为ELFDATA2LSB(1)或ELFDATA2MSB(2)。对 Intel x86 这里是1,也就是低地址存放最低有效字节。

第6个字节(EI_VERSION)表明ELF头的版本号,必须是EV_CURRENT(elf.h 中定义)。

第7个字节(EI_PAD)标明了在e_ident中无用字节的开始,也就是第7至第EI_NIDENT-1字节暂时没有意义。

e_type

表示该ELF文件的类型,其可能的取值为

        ET_NONE         0 No file type

        ET_REL          1 Relocatable file

        ET_EXEC         2 Executable file

        ET_DYN          3 Shared object file

        ET_CORE         4 Core file

        ET_LOPROC 0xff00 Processor-specific

        ET_HIPROC 0xffff Processor-specific

e_type = 1,表明它是重定位文件,可以从连接视图去解读它;

e_type = 2,表明它是可执行文件,至少可以从装载运行视图去解读它;

e_type = 3,表明它是共享动态库文件,同样可以至少从装载运行视图去解读它;

e_type = 4,表明它是 Core dump 文件,可以从哪个视图去解读依赖于具体的实现。

其他取值保留。

从装载运行角度,关注的是程序头表,由程序头表的指引把 ELF 文件加载进内存运行它。从连接的角度,关注节头表,由节头表的指引把各个节连接组装起来。

e_machine

指出了运行该程序需要的体系结构,可能的部分取值如下:

                   EM_NONE       0 No machine

                    EM_M32        1 AT&T WE 32100

                    EM_SPARC      2 SPARC

                    EM_386        3 Intel 80386

                    EM_68K        4 Motorola 68000

                    EM_88K        5 Motorola 88000

                    EM_860        7 Intel 80860

                    EM_MIPS       8 MIPS RS3000

e_version

表示该ELF文件的版本,通常是EV_CURRENT。

e_entry

表示程序的入口地址。如果没有入口点,为0。

e_phoff

表示程序头表(program header table)在文件中的偏移量,以字节为单位。没有程序头表,为0。

e_shoff

表示节头表(section header table)在文件中的偏移量,以字节为单位。没有节头表,为0。

e_flags

表示相关文件的特定处理器标志,对IA32,为0。

e_ehsize

表示ELF头大小,以字节为单位。

e_phentsize

程序头表(program header table)中每一个条目(program heade)的大小(以字节为单位)。所有的program header都是同样的大小。

e_phnum

程序头表(program header table)条目个数,没有程序头表e_phnum为0。

e_shentsize

节头表(section header table)中每一个条目(section header)的大小(以字节为单位)。所有的section header都是同样的大小。

e_shnum

节头表(section header table)条目个数,没有程序头表e_shnum为0。

e_shstrndx

表示包含节名称的字符串表所在节数(从0开始计数)。假如没有节名称字符表,e_shstrndx为SHN_UNDEF。

二.程序头(Program Header)

程序头表(Program header table)位于EFL文件偏移e_phoff处,程序头表包含e_phnum个大小为e_phentsize字节的程序头。

程序头结构如下:

typedef struct {

      Elf32_Word p_type;

      Elf32_Off p_offset;

      Elf32_Addr p_vaddr;

      Elf32_Addr p_paddr;

      Elf32_Word p_filesz;

      Elf32_Word p_memsz;

      Elf32_Word p_flags;

      Elf32_Word p_align;

} Elf32_Phdr;

Elf32_Phdr各项含义如下:

p_type

当前程序头描述的段的类型。类型值和含义如下:

PT_NULL              0

PT_LOAD              1

PT_DYNAMIC           2

PT_INTERP            3

PT_NOTE              4

PT_SHLIB             5

PT_PHDR              6

PT_LOPROC            0x70000000

PT_HIPROC            0x7fffffff

PT_NULL表示该程序头未使用,不表示任何段。

PT_LOA表示可载入段,由 p_filesz 和 p_memsz 描述。如果该段的内存大小( p_memsz )比文件大小( p_filesz )要大,则多出的字节为0。文件大小不会比内存大小大。

PT_DYNAMIC表示该段指定动态链接信息。

PT_INTERP表示一个null-terminated路径名的位置和大小(作为解释程序)。这种段类型仅仅对可执行文件有意义(尽管它可能发生在一个共享 object 上);它在一个文件中只能出现一次。如果它出现,它必须先于任何一个可载入段入口。

PT_NOTE表示该段指定辅助信息的位置和大小。

PT_SHLIB保留

PT_PHDR该段(如果出现),指定了程序头表本身的位置和大小(包括在文件中和在该程序的内存映像中)。更进一步来说,它仅仅在该程序头表是程序内存映像的一部分时才有效。如果它出现,它必须先于任何可载入段入口。

PT_LOPROC through PT_HIPROC保留

p_offset

表示段在ELF文件中的偏移。

p_vaddr

表示段首字节在内存中的虚拟地址。

p_paddr

表示段的物理地址,通常保留未用。

p_filesz

该段在ELF文件中的字节数,有可能是0。

p_memsz

该段在内存中的字节数,有可能是0。

p_flags

表示与段相关的标志。

p_align

决定段在文件及内存中的对齐方式。

暂时只用到这么多,以后按需要再补充。

参考资料:http://www.nsfocus.net/index.php?act=magazine&do=view&mid=890
原文网址: http://hi.baidu.com/liuqizealot/blog/item/850881260628661f8b82a11b.html

上一篇:简单方法实现U盘启动到WinPE--菜鸟进阶篇(初步定制WinPE)

下一篇:带VLAN的交换机是三层交换机吗

备案ICP编号  |   QQ:285250603  |  地址:湛江市  |  电话:15322199012  |  
Copyright © 2026 天人文章管理系统 版权所有,授权www.yajiupc.top使用 Powered by 55TR.COM