去年就开始学习Orange’s:一个操作系统的实现,今年记录并总结一下学习中遇到的一些问题,由于我汇编基础几乎为0,对计算机也不是很了解,所以写的这些注释和总结难免会有错误和纰漏,敬请原谅。此篇笔记抄的书上的代码已经实际运行过了,有可能后期添加注释的时候出现bug,如果运行不起来,可以删除所有注释再运行。此篇笔记去年参考了很多文章,可能我现在找不全了,等后续有时间我再把参考文章写在对应位置。如果笔记有侵权的地方,请联系我我会删除。
代码实现了操作系统从实模式跳转到保护模式,在保护模式下显示一个红色的“P”
nasm pmtest1.asm -o pmtest1.bin
编译完成后会出现一个pmtest1.bin
4. 编写bochsrc(参考学习笔记1中进行修改)
#Configuration file for Bochs
#how much memory the emulated machine will have
megs: 32
#filename of ROM images
romimage: file=/usr/local/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/local/share/bochs/VGABIOS-lgpl-latest
#what disk images will be used
floppya: 1_44=a.img, status=inserted
#choose the boot disk.
boot: floppy
#where do we send log messages
log: bochsout.txt
#disable the mouse
mouse: enabled=0
#enable key mapping, using US layout as default
keyboard:keymap=/usr/local/share/bochs/keymaps/x11-pc-us.map
bximage
参考下图选择配置
执行完上述流程后,文件目录下会出现一个a.img
dd if=pmtest1.bin of=a.img bs=512 count=1 conv=notrunc
在对应文件目录下输入下面一条指令
bochs
输入完成后回车,根据提示按6回车后,再输入c,bochs启动完成
;pmtest1.asm
%include "pm.inc"
;常量,宏,以及一些说明
org 07c00h
jmp LABEL_BEGIN
;跳转到LABEL_BEGIN处执行
[SECTION .gdt]
;GDT
;Descriptor段描述符,64位八个字节
;Descriptor的三个参数依次为段基址,段界限,属性
;在pm.inc中有三个参数如何送到对应位置的代码,所以此处直接填写即可
LABEL_GDT: Descriptor 0, 0, 0
; 空描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32
;32位代码段的段基址是0,段地址最大能到的位置是SegCode32Len - 1
; 非一致代码段
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW
;DA_DRW是可读写数据段属性值
; 显存首地址
; GDT 结束
GdtLen equ $ - LABEL_GDT
;$代表当前位置代码汇编后的地址,$-LABLE_GDT是GDT长度
; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 选择子
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
;16位代码段
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
;cs代码段寄存器,存放程序代码段起始地址,与ip搭配可以取下一条指令
;cs:ip组合中,变化的基本是ip
mov ds, ax
;ds为数据段寄存器,一般用于存放数据ds地址对应的数据
mov es, ax
;es扩展段寄存器
mov ss, ax
;ss栈段寄存器,相当于堆栈的首地址,常与sp搭配
mov sp, 0100h
;sp相当于堆栈的指针
; 初始化 32 位代码段描述符
xor eax, eax
;清零eax,xor异或,相同为0,不同为1
mov ax, cs
shl eax, 4
;shl左移指令,将eax左移4位
;此处左移四位代表乘16,物理地址=段值*16+偏移量
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
;将ax内容送到LABEL_DESC_CODE32地址的两个字节之后
;段描述符的第三个字节和第四个字节是段基址1的低十六位
;这个操作是将真实的物理地址的低八位送到段描述符的第三个字节和第四个字节
shr eax, 16
;shl是逻辑左移指令,将一个寄存器或内存单元中的数据向左移位
;shr是逻辑右移指令,将一个寄存器或内存单元中的数据向右移位
;右移16位便于操作高位数据
mov byte [LABEL_DESC_CODE32 + 4], al
;将al中的内容送到LABEL_DESC_CODE32的第五个字节
;LABEL_DESC_CODE32的第五个字节是段基址1的高八位
mov byte [LABEL_DESC_CODE32 + 7], ah
;将ah中的内容送到LABEL_DESC_CODE32的第8个字节
;LABEL_DESC_CODE32的第五个字节是段基址2的八位
; 为加载 GDTR 作准备
xor eax, eax
;清零eax
mov ax, ds
;将ds中的数据送到ax,ds中存储的是初始化的时候cs的内容
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
;计算gdt的物理地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
;gdtr是一个48位寄存器,可以指定gdt在内存中的起始地址和gdt的表长
;对于GDTR寄存器,须使用专门的指令lgdt进行赋值
;将gdt的物理地址送到gdtr的高32位
;gdtr的高32位是gdt的基地址
;gdtr的低16位是gdt的界限
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
;打开A20后,给出100000H~10FFEFH之间的地址才能真正访问内存中对应的位置
; 准备切换到保护模式
mov eax, cr0
;cr0的第1位PE位为0时,系统在实模式下运行,PE位为1时,系统在保护模式下运行
or eax, 1
;将eax与1进行或运算
mov cr0, eax
;将eax中的内容送回cr0,此时PE位已经置为1,系统已经进入保护模式
; 真正进入保护模式
jmp dword SelectorCode32:0
; 执行这一句会把 SelectorCode32 装入 cs,dword是将偏移地址改为32位
; 并跳转到 Code32Selector:0 处
; END of [SECTION .s16]
[SECTION .s32]; 32 位代码段. 由实模式跳入.
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子(目的)
mov edi, (80 * 11 + 79) * 2 ; 屏幕第 11 行, 第 79 列。
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov al, 'P'
mov [gs:edi], ax
; 到此停止
jmp $
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
;include的pm.inc
;描述符类型
DA_32 EQU 4000h
;DA_32=4000h
;DPL是规定访问该段的权限级别(Descriptor Privilege Level),每个段的DPL固定
DA_DPL0 EQU 00h
;DPL=0 00h=0000 0000b
DA_DPL1 EQU 20h
;DPL=1 20h=0010 0000b
DA_DPL2 EQU 40h
;DPL=2 40h=0100 0000b
DA_DPL3 EQU 60h
;DPL=3 60h=0110 0000b
;DPL是八个字节大小的描述符中的第六个字节的第6、7位(从低到高)
;存储段描述符类型
DA_DR EQU 90h ; 存在的只读数据段类型值
DA_DRW EQU 92h ; 存在的可读写数据段属性值
DA_DRWA EQU 93h ; 存在的已访问可读写数据段类型值
DA_C EQU 98h ; 存在的只执行代码段属性值
DA_CR EQU 9Ah ; 存在的可执行可读代码段属性值
DA_CCO EQU 9Ch ; 存在的只执行一致代码段属性值
DA_CCOR EQU 9Eh ; 存在的可执行可读一致代码段属性值
;系统段描述符类型
DA_LDT EQU 82h ; 局部描述符表段类型值
DA_TaskGate EQU 85h ; 任务门类型值
DA_386TSS EQU 89h ; 可用 386 任务状态段类型值
DA_386CGate EQU 8Ch ; 386 调用门类型值
DA_386IGate EQU 8Eh ; 386 中断门类型值
DA_386TGate EQU 8Fh ; 386 陷阱门类型值
;选择子类型值
;段选择子相当于某个段相对于GDT的偏移地址
;GDT是段描述符的集合
;RPL用于特权检查
SA_RPL0 EQU 0 ; ┓
SA_RPL1 EQU 1 ; ┣ RPL
SA_RPL2 EQU 2 ; ┃
SA_RPL3 EQU 3 ; ┛
SA_TIG EQU 0 ; ┓TI
SA_TIL EQU 4 ; ┛
; 描述符
; usage: Descriptor Base, Limit, Attr
; Base: dd
; Limit: dd (low 20 bits available)
; Attr: dw (lower 4 bits of higher byte are always 0)
%macro Descriptor 3
dw %2 & 0FFFFh ; 段界限1
dw %1 & 0FFFFh ; 段基址1
db (%1 >> 16) & 0FFh ; 段基址2
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性1 + 段界限2 + 属性2
db (%1 >> 24) & 0FFh ; 段基址3
%endmacro ; 共 8 字节
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移1
dw %1 ; 选择子
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性
dw ((%2 >> 16) & 0FFFFh) ; 偏移2
%endmacro ; 共 8 字节
上图表示的是代码段和数据段描述符,此外描述符还分为系统段描述符和门描述符
上图表示的是选择子的结构
段选择子的第2位是引用描述符表指示位,标记为TI(Table Indicator),TI=0指示从全局描述符表GDT中读取描述符;TI=1指示从局部描述符表LDT中读取描述符
最低两位是请求特权级RPL(Requested Privilege Level),用于特权检查,以决定对该段能否访问。
DPL,RPL等等后续几节再讲。
上图表示加入描述符和选择子之后的寻址方式
段值+偏移量的形式的逻辑地址经过段机制转化成线性地址,而不是物理地址
上图表示gdtr的结构,gdtr的结构和GdtPtr的结构一样
如果A20 Gate被打开,则当程序员给出100000H-10FFEFH之间的地址的时候,系统将真正访问这块内存区域
如果A20 Gate被禁止,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式即取模方式(8086仿真)。绝大多数IBM PC兼容机默认的A20 Gate是被禁止的。现在许多新型PC上存在直接通过BIOS功能调用来控制A20 Gate的功能
在8086中,试图访问超过1MB以上的内存会发生回卷寻址,即从0000H开始
上图表示CR0的结构
控制寄存器(CR0~CR3)用于控制和确定处理器的操作模式以及当前执行任务的特性,该寄存器也被称为页目录基地址寄存器PDBR
$
表示当前地址,$
-LABLE_GDT即GDT的地址长度
$
在不同的语法中表示不同的意思
在AT&T语法中,使用$
表示立即数
在INTEL语法中,使用$
表示当前地址