Orange‘s:一个操作系统的实现学习笔记1:https://blog.csdn.net/weixin_42193791/article/details/123144920
Orange‘s:一个操作系统的实现学习笔记2:
https://blog.csdn.net/weixin_42193791/article/details/123039173
书中上一小节实现了跳入保护模式,接下来本节将实验在保护模式下访问更高地址的内存,实现对更高地址内存的读写。
代码实现了一个段基址为5MB的段,先读出八个字节的内容,然后写入一个字符串,然后再一次读取用来确定是否写入成功。
参考文章https://www.linuxidc.com/Linux/2014-05/102460.htm,打开链接https://bochs.sourceforge.io/diskimages.html下载freedos安装包,然后将其解压,得到一个一个文件夹,如下图所示:
然后点击进入文件夹,如下图所示:
之后先不要着急进行下一步操作,先来研究一下这里面的bochsrc和之前编写的bochsrc之间的区别,两个文件内容如下:
#之前编写的bochsrc
#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
#freedos自带的bochsrc
megs: 32
romimage: file=$BXSHARE/BIOS-bochs-latest
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
vga: extension=vbe, update_freq=15
floppya: 1_44=a.img, status=inserted
floppyb: 1_44=b.img, status=inserted
ata0-master: type=disk, path=c.img, cylinders=306, heads=4, spt=17
boot: c
log: bochsout.txt
mouse: enabled=0
cpu: ips=15000000
可以发现,megs和vgaromimage和romimage并没有变化,其他的变化参考这篇文章来分析:https://blog.csdn.net/yyttiao/article/details/8168301
floppyb: 1_44=b.img, status=inserted
新增一个软盘floppyb,然后status=inserted表示已经插入。
ata0-master: type=disk, path=c.img, cylinders=306, heads=4, spt=17
首先先来看ata是什么,参考文章:https://blog.csdn.net/xiaojianpitt/article/details/2288123
而上述代码则是ata0-master用于模拟系统中第一个ata通道上连接的第一个ata设备。
boot: c
boot指定虚拟系统启动引导的设备.由硬盘,软件,或者磁盘符号。
其他的变化参考上述文章一一分析就好了。
编写pmtest2.asm
; ==========================================
; pmtest2.asm
; 编译方法:nasm pmtest2.asm -o pmtest2.com
; ==========================================
%include "pm.inc" ; 常量, 宏, 以及一些说明
org 0100h
jmp LABEL_BEGIN
;程序跳转到LABEL_BEGIN执行
[SECTION .gdt]
; GDT
; 段基址, 段界限 , 属性
LABEL_GDT: Descriptor 0, 0, 0
;空描述符
LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW
;Normal 描述符
LABEL_DESC_CODE32: Descriptor 0, SegCode32Len-1, DA_C+DA_32
;非一致代码段, 32位代码段
LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C
;非一致代码段, 16位代码段,由此段跳入保护模式
LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW
;数据段,显示的字符串保存在数据段中
LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32
;Stack, 堆栈段
LABEL_DESC_TEST: Descriptor 0500000h, 0ffffh, DA_DRW
;测试段,在此段内实验访问大内存,段基址为5MB,远远超过实模式下地址上限1MB
LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW
;显存首地址,显存段
;GDT 结束
GdtLen equ $ - LABEL_GDT ; GDT长度
GdtPtr dw GdtLen - 1 ; GDT界限
dd 0 ; GDT基地址
; GDT 选择子
SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT
SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT
SelectorData equ LABEL_DESC_DATA - LABEL_GDT
SelectorStack equ LABEL_DESC_STACK - LABEL_GDT
SelectorTest equ LABEL_DESC_TEST - LABEL_GDT
SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT
; END of [SECTION .gdt]
;数据段
[SECTION .data1]
ALIGN 32
[BITS 32]
LABEL_DATA:
SPValueInRealMode dw 0
; 字符串
PMMessage: db "In Protect Mode now. ^-^", 0
;在保护模式显示的字符串1
OffsetPMMessage equ PMMessage - $$
;$$是当前段开始的地址
;OffsetPMMessage=PMMessage-$$为字符串相对于数据段开始地址的偏移地址
StrTest: db "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0
;保护模式显示的字符串2
OffsetStrTest equ StrTest - $$
;OffsetStrTest=StrTest-$$为字符串相对于数据段开始地址的偏移地址
DataLen equ $ - LABEL_DATA
;DataLen为数据段长度
; END of [SECTION .data1]
;全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512 db 0
;重复定义512个字节的0
TopOfStack equ $ - LABEL_STACK - 1
;栈顶为当前代码行汇编后的地址-LABEL_STACK-1
; END of [SECTION .gs]
;16位代码段,用来跳入保护模式和跳回实模式之后的工作
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0100h
;初始化ds,es,ss,sp
;注意,此时ds内部存储的值是cs的值
;下面偏移地址是相对于初始化的cs的值
mov [LABEL_GO_BACK_TO_REAL+3], ax
;实模式jmp指令结构是byte1:0EAh,byte2~3:Offset,byte4~5:Segment
;此处mov将ax中的内容送到了LABEL_GO_BACK_TO_REAL后第四个字节和第五个字节
;也就是jmp 0:LABEL_REAL_ENTRY变成了jmp ax:LABEL_REAL_ENTRY
mov [SPValueInRealMode], sp
;将实模式下的指针sp存到SPValueInRealMode字符串中
;初始化16位代码段描述符
mov ax, cs
movzx eax, ax
;movzx,movsx要求操作数B所占空间小于操作数A
;movzx将空位补充为0,movsx将空位补充为操作数B的符号位
shl eax, 4
add eax, LABEL_SEG_CODE16
mov word [LABEL_DESC_CODE16 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE16 + 4], al
mov byte [LABEL_DESC_CODE16 + 7], ah
;和上一节一样,计算真实物理地址后送入段描述符
;初始化32位代码段描述符
xor eax, eax
;xor异或指令,两个寄存器对应位相同为0,不同为1
;eax肯定与eax内部数据相同,所以xor eax,eax后eax每一位都为0,实现eax清零
mov ax, cs
shl eax, 4
add eax, LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE32 + 4], al
mov byte [LABEL_DESC_CODE32 + 7], ah
;和上一节一样,计算真实物理地址后送入段描述符
;注意,上述两个代码段(16位代码段和32位代码段)的段基址就是cs中的地址
; 初始化数据段描述符
xor eax, eax
mov ax, ds
;这里有一个很有意思的事情,如果简单来看,ds寄存器保存着数据段的段基址
;那么直接将ds中的内容送到ax中,之后左移4位加偏移是在初始化数据段的描述符
;然而仔细观察,会发现这里ds还是最初cs的值,而且开头很多段的段基址都是0
;原因是数据段包含了段基址是0的段,数据段里面字符串地址都是加上偏移地址
shl eax, 4
add eax, LABEL_DATA
mov word [LABEL_DESC_DATA + 2], ax
shr eax, 16
mov byte [LABEL_DESC_DATA + 4], al
mov byte [LABEL_DESC_DATA + 7], ah
;和上一节一样,计算真实物理地址后送入段描述符
; 初始化堆栈段描述符
xor eax, eax
mov ax, ds
;堆栈段和数据段的段基址是相同的,所以直接将ds寄存器中的内容送到ax
shl eax, 4
add eax, LABEL_STACK
mov word [LABEL_DESC_STACK + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK + 4], al
mov byte [LABEL_DESC_STACK + 7], ah
;和上一节一样,计算真实物理地址后送入段描述符
; 为加载 GDTR 作准备
xor eax, eax
mov ax, ds
;这里gdt的基址和ds都是cs的最开始的值,所以直接将ds寄存器中的内容送到ax
shl eax, 4
add eax, LABEL_GDT ; eax <- gdt 基地址
mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址
; 加载 GDTR
lgdt [GdtPtr]
; 关中断
cli
; 打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
; 准备切换到保护模式
mov eax, cr0
or eax, 1
mov cr0, eax
; 真正进入保护模式
jmp dword SelectorCode32:0
;道理同上述的jmp指令结构
;执行这一句会把 SelectorCode32装入 cs, 并跳转到 Code32Selector:0 处
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
;将ds、es、ss设置成实模式下的地址
mov sp, [SPValueInRealMode]
;将sp(堆栈指针)设置成实模式下的地址
in al, 92h ; `.
and al, 11111101b ; | 关闭 A20 地址线
out 92h, al ; /
sti ; 开中断
mov ax, 4c00h ; `.
;4c00h=0100 1100 0000 0000b
;21h中断的功能由ah寄存器有关
;ah=4c时,21h的功能是代返回码结束,返回码是al中的内容
int 21h ; / 回到 DOS
; END of [SECTION .s16]
;32位代码段
[SECTION .s32]
[BITS 32]
LABEL_SEG_CODE32:
mov ax, SelectorData
mov ds, ax ; 数据段选择子
mov ax, SelectorTest
mov es, ax ; 测试段选择子
mov ax, SelectorVideo
mov gs, ax ; 视频段选择子
;注意,保护模式下,对应的段寄存器存储的都是对应段的索引
;段选择子中保存着段的索引
mov ax, SelectorStack
mov ss, ax ; 堆栈段选择子
mov esp, TopOfStack
;保护模式的栈指针存储在esp寄存器中
;esp寄存器是sp寄存器的32位版,esp的低16位就是sp寄存器
; 下面显示一个字符串
mov ah, 0Ch ; 0000: 黑底 1100: 红字
xor esi, esi
xor edi, edi
;清零esi,edi
mov esi, OffsetPMMessage
;源数据偏移
;数据段中PMMessage字符串的首地址送到esi
mov edi, (80 * 10 + 0) * 2
; 目的数据偏移。屏幕第 10 行, 第 0 列。
cld
;cld是将标志寄存器的DF(方向标志位)置零
;标志寄存器中的DF位,串处理指令指令中,每次操作后,控制si、di是递减或递增
;DF=0,si、di递增,DF=1,si、di递减
.1:
lodsb
;lodsb/lodsw指令是块装入指令,将si指向的存储单元的数据读入累加器
;lodsb是将数据读到al,lodsw是将数据读到ax
;读取完成后,si将自动增加或减少1或2
;常用作处理字符串或数组中的元素逐个处理
test al, al
;test指令是将两个操作数进行逻辑与运算,但是不会改变寄存器内的值
;test指令操作完成后,寄存器中的内容没有改变,指令根据操作结果设置标志位
;test指令常用于判断寄存器内容是否为空
;如果寄存器为空,则逻辑与运算为0,ZF位被置零
;如果ZF位为0,则jz跳转
jz .2
;如果字符串读取结束,则跳转到.2,若未结束则继续进行下列指令
mov [gs:edi], ax
;将ax中的内容送到gs:edi所形成的地址指向的位置
add edi, 2
;esi+2,一次读取两个存储单元(2bit)
jmp .1
;字符串未读取完跳转到.1循环读取
.2: ; 显示完毕
call DispReturn
;调用DIsReturn
call TestRead
;先读取一次地址对应的存储单元中的数据并显示
call TestWrite
;然后将字符串写入地址对应的存储单元
call TestRead
;再读取一次地址对应的存储单元中的数据并显示
; 到此停止
jmp SelectorCode16:0
;返回实模式
; ------------------------------------------------------------------------
TestRead:
xor esi, esi
;清零esi
mov ecx, 8
;将ecx赋值为8
;读取八个bit的数据
.loop:
mov al, [es:esi]
;将es:esi形成的地址中的数据送到al
call DispAL
;调用DispAL
inc esi
;esi+1
loop .loop
;循环8次
call DispReturn
;调用DispReturn
ret
; TestRead 结束-----------------------------------------------------------
; ------------------------------------------------------------------------
TestWrite:
push esi
push edi
;压栈esi、edi
xor esi, esi
xor edi, edi
;清零esi,edi
mov esi, OffsetStrTest ; 源数据偏移
;将StrTest字符串的偏移地址送到esi
cld
.1:
lodsb
test al, al
jz .2
mov [es:edi], al
;读取时候显示地址是gs:edi,这里是es:edi,是将al中的数据送到es:edi地址
inc edi
;edi+1
jmp .1
.2:
pop edi
pop esi
;读取结束后将edi和esi出栈
ret
; TestWrite 结束----------------------------------------------------------
; ------------------------------------------------------------------------
; 显示 AL 中的数字
; 默认地:
; 数字已经存在 AL 中
; edi 始终指向要显示的下一个字符的位置
; 被改变的寄存器:
; ax, edi
; ------------------------------------------------------------------------
DispAL:
push ecx
push edx
mov ah, 0Ch ; 0000: 黑底 1100: 红字
mov dl, al
shr al, 4
mov ecx, 2
.begin:
and al, 01111b
cmp al, 9
ja .1
add al, '0'
jmp .2
.1:
sub al, 0Ah
add al, 'A'
.2:
mov [gs:edi], ax
add edi, 2
mov al, dl
loop .begin
add edi, 2
pop edx
pop ecx
ret
; DispAL 结束-------------------------------------------------------------
; ------------------------------------------------------------------------
DispReturn:
push eax
push ebx
;压栈eax,ebx
mov eax, edi
;将edi送到eax
mov bl, 160
;bl赋值160
div bl
;eax除以bl
and eax, 0FFh
;eax与0FFh相与
inc eax
;eax加1
mov bl, 160
mul bl
;eax乘bl
mov edi, eax
;将eax中的内容送到edi
pop ebx
pop eax
;出栈ebx、eax
;作用大概是回车
ret
; DispReturn 结束---------------------------------------------------------
SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]
;16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
;跳回实模式:
mov ax, SelectorNormal
mov ds, ax
mov es, ax
mov fs, ax
mov gs, ax
mov ss, ax
;为了将寄存器设置为实模式下的值,这里向ax送入normal段的选择子
mov eax, cr0
and al, 11111110b
mov cr0, eax
;PE位置零,跳回实模式
LABEL_GO_BACK_TO_REAL:
jmp 0:LABEL_REAL_ENTRY
;段地址会在程序开始处被设置成正确的值
;此处参照之前jmp指令结构
Code16Len equ $ - LABEL_SEG_CODE16
; END of [SECTION .s16code]
编写pm.inc:
;pm.inc
; 描述符图示
; 图示一
;
; ------ ┏━━┳━━┓高地址
; ┃ 7 ┃ 段 ┃
; ┣━━┫ ┃
; 基
; 字节 7 ┆ ┆ ┆
; 址
; ┣━━┫ ② ┃
; ┃ 0 ┃ ┃
; ------ ┣━━╋━━┫
; ┃ 7 ┃ G ┃
; ┣━━╉──┨
; ┃ 6 ┃ D ┃
; ┣━━╉──┨
; ┃ 5 ┃ 0 ┃
; ┣━━╉──┨
; ┃ 4 ┃ AVL┃
; 字节 6 ┣━━╉──┨
; ┃ 3 ┃ ┃
; ┣━━┫ 段 ┃
; ┃ 2 ┃ 界 ┃
; ┣━━┫ 限 ┃
; ┃ 1 ┃ ┃
; ┣━━┫ ② ┃
; ┃ 0 ┃ ┃
; ------ ┣━━╋━━┫
; ┃ 7 ┃ P ┃
; ┣━━╉──┨
; ┃ 6 ┃ ┃
; ┣━━┫ DPL┃
; ┃ 5 ┃ ┃
; ┣━━╉──┨
; ┃ 4 ┃ S ┃
; 字节 5 ┣━━╉──┨
; ┃ 3 ┃ ┃
; ┣━━┫ T ┃
; ┃ 2 ┃ Y ┃
; ┣━━┫ P ┃
; ┃ 1 ┃ E ┃
; ┣━━┫ ┃
; ┃ 0 ┃ ┃
; ------ ┣━━╋━━┫
; ┃ 23 ┃ ┃
; ┣━━┫ ┃
; ┃ 22 ┃ ┃
; ┣━━┫ 段 ┃
;
; 字节 ┆ ┆ 基 ┆
; 2, 3, 4
; ┣━━┫ 址 ┃
; ┃ 1 ┃ ① ┃
; ┣━━┫ ┃
; ┃ 0 ┃ ┃
; ------ ┣━━╋━━┫
; ┃ 15 ┃ ┃
; ┣━━┫ ┃
; ┃ 14 ┃ ┃
; ┣━━┫ 段 ┃
;
; 字节 0,1┆ ┆ 界 ┆
;
; ┣━━┫ 限 ┃
; ┃ 1 ┃ ① ┃
; ┣━━┫ ┃
; ┃ 0 ┃ ┃
; ------ ┗━━┻━━┛低地址
;
; 图示二
; 高地址………………………………………………………………………低地址
; | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
; |7654321076543210765432107654321076543210765432107654321076543210| <- 共 8 字节
; |--------========--------========--------========--------========|
; ┏━━━┳━━━━━━━┳━━━━━━━━━━━┳━━━━━━━┓
; ┃31..24┃ (见下图) ┃ 段基址(23..0) ┃ 段界限(15..0)┃
; ┃ ┃ ┃ ┃ ┃
; ┃ 基址2┃③│②│ ①┃基址1b│ 基址1a ┃ 段界限1 ┃
; ┣━━━╋━━━┳━━━╋━━━━━━━━━━━╋━━━━━━━┫
; ┃ %6 ┃ %5 ┃ %4 ┃ %3 ┃ %2 ┃ %1 ┃
; ┗━━━┻━━━┻━━━┻━━━┻━━━━━━━┻━━━━━━━┛
; │ \_________
; │ \__________________
; │ \________________________________________________
; │ \
; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
; ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃
; ┣━━╋━━╋━━╋━━╋━━┻━━┻━━┻━━╋━━╋━━┻━━╋━━╋━━┻━━┻━━┻━━┫
; ┃ G ┃D/B ┃ 0 ┃ AVL┃ 段界限 2 (19..16) ┃ P ┃ DPL ┃ S ┃ TYPE ┃
; ┣━━┻━━┻━━┻━━╋━━━━━━━━━━━╋━━┻━━━━━┻━━┻━━━━━━━━━━━┫
; ┃ ③: 属性 2 ┃ ②: 段界限 2 ┃ ①: 属性1 ┃
; ┗━━━━━━━━━━━┻━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━┛
; 高地址 低地址
;
;
; 说明:
;
; (1) P: 存在(Present)位。
; P=1 表示描述符对地址转换是有效的,或者说该描述符所描述的段存在,即在内存中;
; P=0 表示描述符对地址转换无效,即该段不存在。使用该描述符进行内存访问时会引起异常。
;
; (2) DPL: 表示描述符特权级(Descriptor Privilege level),共2位。它规定了所描述段的特权级,用于特权检查,以决定对该段能否访问。
;
; (3) S: 说明描述符的类型。
; 对于存储段描述符而言,S=1,以区别与系统段描述符和门描述符(S=0)。
;
; (4) TYPE: 说明存储段描述符所描述的存储段的具体属性。
;
;
; 数据段类型 类型值 说明
; ----------------------------------
; 0 只读
; 1 只读、已访问
; 2 读/写
; 3 读/写、已访问
; 4 只读、向下扩展
; 5 只读、向下扩展、已访问
; 6 读/写、向下扩展
; 7 读/写、向下扩展、已访问
;
;
; 类型值 说明
; 代码段类型 ----------------------------------
; 8 只执行
; 9 只执行、已访问
; A 执行/读
; B 执行/读、已访问
; C 只执行、一致码段
; D 只执行、一致码段、已访问
; E 执行/读、一致码段
; F 执行/读、一致码段、已访问
;
;
; 系统段类型 类型编码 说明
; ----------------------------------
; 0 <未定义>
; 1 可用286TSS
; 2 LDT
; 3 忙的286TSS
; 4 286调用门
; 5 任务门
; 6 286中断门
; 7 286陷阱门
; 8 未定义
; 9 可用386TSS
; A <未定义>
; B 忙的386TSS
; C 386调用门
; D <未定义>
; E 386中断门
; F 386陷阱门
;
; (5) G: 段界限粒度(Granularity)位。
; G=0 表示界限粒度为字节;
; G=1 表示界限粒度为4K 字节。
; 注意,界限粒度只对段界限有效,对段基地址无效,段基地址总是以字节为单位。
;
; (6) D/B: 此位是一个很特殊的位,在描述可执行段、向下扩展数据段或由SS寄存器寻址的段(通常是堆栈段)的三种描述符中的意义各不相同。
; ⑴ 在描述可执行段的描述符中,D位决定了指令使用的地址及操作数所默认的大小。
; ① D=1表示默认情况下指令使用32位地址及32位或8位操作数,这样的代码段也称为32位代码段;
; ② D=0 表示默认情况下,使用16位地址及16位或8位操作数,这样的代码段也称为16位代码段,它与80286兼容。可以使用地址大小前缀和操作数大小前缀分别改变默认的地址或操作数的大小。
; ⑵ 在向下扩展数据段的描述符中,D位决定段的上部边界。
; ① D=1表示段的上部界限为4G;
; ② D=0表示段的上部界限为64K,这是为了与80286兼容。
; ⑶ 在描述由SS寄存器寻址的段描述符中,D位决定隐式的堆栈访问指令(如PUSH和POP指令)使用何种堆栈指针寄存器。
; ① D=1表示使用32位堆栈指针寄存器ESP;
; ② D=0表示使用16位堆栈指针寄存器SP,这与80286兼容。
;
; (7) AVL: 软件可利用位。80386对该位的使用未左规定,Intel公司也保证今后开发生产的处理器只要与80386兼容,就不会对该位的使用做任何定义或规定。
;
;----------------------------------------------------------------------------
; 描述符类型值说明
; 其中:
; DA_ : Descriptor Attribute
; D : 数据段
; C : 代码段
; S : 系统段
; R : 只读
; RW : 读写
; A : 已访问
; 其它 : 可按照字面意思理解
;----------------------------------------------------------------------------
DA_32 EQU 4000h ; 32 位段
DA_DPL0 EQU 00h ; DPL = 0
DA_DPL1 EQU 20h ; DPL = 1
DA_DPL2 EQU 40h ; DPL = 2
DA_DPL3 EQU 60h ; DPL = 3
;----------------------------------------------------------------------------
; 存储段描述符类型值说明
;----------------------------------------------------------------------------
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 陷阱门类型值
;----------------------------------------------------------------------------
; 选择子图示:
; ┏━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┳━━┓
; ┃ 15 ┃ 14 ┃ 13 ┃ 12 ┃ 11 ┃ 10 ┃ 9 ┃ 8 ┃ 7 ┃ 6 ┃ 5 ┃ 4 ┃ 3 ┃ 2 ┃ 1 ┃ 0 ┃
; ┣━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━┻━━╋━━╋━━┻━━┫
; ┃ 描述符索引 ┃ TI ┃ RPL ┃
; ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━┻━━━━━┛
;
; RPL(Requested Privilege Level): 请求特权级,用于特权检查。
;
; TI(Table Indicator): 引用描述符表指示位
; TI=0 指示从全局描述符表GDT中读取描述符;
; TI=1 指示从局部描述符表LDT中读取描述符。
;
;----------------------------------------------------------------------------
; 选择子类型值说明
; 其中:
; SA_ : Selector Attribute
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 (2 字节)
dw %1 & 0FFFFh ; 段基址 1 (2 字节)
db (%1 >> 16) & 0FFh ; 段基址 2 (1 字节)
dw ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh) ; 属性 1 + 段界限 2 + 属性 2 (2 字节)
db (%1 >> 24) & 0FFh ; 段基址 3 (1 字节)
%endmacro ; 共 8 字节
;
; 门
; usage: Gate Selector, Offset, DCount, Attr
; Selector: dw
; Offset: dd
; DCount: db
; Attr: db
%macro Gate 4
dw (%2 & 0FFFFh) ; 偏移 1 (2 字节)
dw %1 ; 选择子 (2 字节)
dw (%3 & 1Fh) | ((%4 << 8) & 0FF00h) ; 属性 (2 字节)
dw ((%2 >> 16) & 0FFFFh) ; 偏移 2 (2 字节)
%endmacro ; 共 8 字节
; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
上述代码大概写了算比较详细的注释。
将代码编译,在文件夹中打开终端,输入:
nasm pmtest2.asm -o pmtest2.com
首先用bximage虚拟软盘,叫做pm.img。
然后将pm.img放到freedos文件夹下,如下图所示:
然后修改bochsrc,如下面代码所示:
megs: 32
romimage: file=$BXSHARE/BIOS-bochs-latest
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest
vga: extension=vbe, update_freq=15
floppya: 1_44=a.img, status=inserted
floppyb: 1_44=pm.img, status=inserted
ata0-master: type=disk, path=c.img, cylinders=306, heads=4, spt=17
boot: a
log: bochsout.txt
mouse: enabled=0
cpu: ips=15000000
然后在文件夹下启动终端,输入:
bochs
format b:
如下图所示:
然后关闭bochs,然后在Ubuntu的freedos文件夹下打开终端,输入下述指令:
sudo mount -o loop pm.inc
sudo cp pmtest2.com /mnt/floppy
sudo umonut /mnt/floppy
上述操作是将被格式化的pm.img挂载到Ubuntu上,然后将pmtest2.com拷贝到pm.inc。
启动bochs,输入:
bochs
在freedos中输入:
b:\pmtest2.com
可以看到上图已经显示出程序写入的字符串了。(A的asc码转为十六进制为41)
还有一些细节问题需要记录,今天稍微有点累(打游戏上分了,我没有摆烂,扶我起来,我还能卷),先挂在这,明天或者后天再更。