Orange‘s:一个操作系统的实现学习笔记2

第三章第一节代码运行实录

  • 前言
  • 代码实现了什么
  • 操作流程
  • 代码+注释
  • 运行效果
  • 一些问题
    • 1. 段描述符的结构
    • 2. 段选择子的结构
    • 3. 地址转换
    • 4. GDTR的结构
    • 5. 打开A20地址线
    • 6. CR0寄存器的结构
    • 7. "$"

前言

去年就开始学习Orange’s:一个操作系统的实现,今年记录并总结一下学习中遇到的一些问题,由于我汇编基础几乎为0,对计算机也不是很了解,所以写的这些注释和总结难免会有错误和纰漏,敬请原谅。此篇笔记抄的书上的代码已经实际运行过了,有可能后期添加注释的时候出现bug,如果运行不起来,可以删除所有注释再运行。此篇笔记去年参考了很多文章,可能我现在找不全了,等后续有时间我再把参考文章写在对应位置。如果笔记有侵权的地方,请联系我我会删除。

代码实现了什么

代码实现了操作系统从实模式跳转到保护模式,在保护模式下显示一个红色的“P”

操作流程

  1. 编写pmtest1.asm(可以直接复制粘贴下面的代码)
  2. 在相同文件夹下编写pm.inc(可以直接复制粘贴下面的代码)
  3. 使用NASM汇编pmtest1.asm,在终端输入下面指令
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

  1. 生成一个虚拟软盘
bximage

参考下图选择配置
Orange‘s:一个操作系统的实现学习笔记2_第1张图片
执行完上述流程后,文件目录下会出现一个a.img

  1. 将生成的二进制导入a.img
dd if=pmtest1.bin of=a.img bs=512 count=1 conv=notrunc
  1. 启动bochs

在对应文件目录下输入下面一条指令

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是八个字节大小的描述符中的第六个字节的第67位(从低到高)

;存储段描述符类型
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 字节

运行效果

Orange‘s:一个操作系统的实现学习笔记2_第2张图片

一些问题

1. 段描述符的结构

Orange‘s:一个操作系统的实现学习笔记2_第3张图片
上图表示的是代码段和数据段描述符,此外描述符还分为系统段描述符和门描述符

  1. 段基址:通过四个字节(32位)进行表示。类似于实模式中的段基址,获取最终的地址直接与段内偏移进行加减
  2. 段界限:通过两个半字节(20位)进行表示。实际上这个表示的是段边界的扩展最值。在保护模式下,段的拓展方向只有上下两种。
    (1)对于数据段和代码段来说,其段的拓展方向是向上,即地址越来越高,此时的段界限用来表示段内偏移的最大值
    (2)对于栈段,段的拓展方向是向下,即地址越来越低,此时的段界限用来表示段内偏移的最小值
    段界限只是一个单位量,不过其单位要么是1字节,要么是4KB,这是由段描述符中的G位来标明的。即最终段的边界是此段界限值×单位
  3. Byte6第八位-G:段界限粒度,G=0代表段界限粒度为字节,G=1代表段界限粒度为4KB
    Byte6第七位-D/B:分为三种情况
    (1)在可执行代码段描述符中,这一位叫做D位。D=1时,在默认情况下指令使用32位地址及32位或者8位操作数,D=0时,在默认情况下使用16位地址及16位或8位操作数
    (2)在向下扩展数据段的描述符中,这一位叫做B位。B=1时,段的上部界限为4GB,B=0时,段的上部界限为64KB
    (3)在描述堆栈段的描述符中,这一位叫做B位。B=1时,隐式的堆栈访问指令使用32位寄存器ESP,B=0时,隐式的堆栈访问指令使用16位寄存器SP
  4. Byte5第八位-P:存在位,P=1表示段在内存中存在,P=0表示段在内存中不存在
    Byte5第六位第七位-DPL:描述特权级,特权级可以是0、1、2、3,数字越小特权级越大
    Byte5第五位-S:S=1是数据段/代码段,S=2是系统段/门描述符
    Byte5第一位第二位第三位第四位-TYPE:详见下图
    Orange‘s:一个操作系统的实现学习笔记2_第4张图片

2. 段选择子的结构

在这里插入图片描述
上图表示的是选择子的结构
段选择子的第2位是引用描述符表指示位,标记为TI(Table Indicator),TI=0指示从全局描述符表GDT中读取描述符;TI=1指示从局部描述符表LDT中读取描述符
最低两位是请求特权级RPL(Requested Privilege Level),用于特权检查,以决定对该段能否访问。
DPL,RPL等等后续几节再讲。

3. 地址转换

Orange‘s:一个操作系统的实现学习笔记2_第5张图片
上图表示加入描述符和选择子之后的寻址方式
段值+偏移量的形式的逻辑地址经过段机制转化成线性地址,而不是物理地址

4. GDTR的结构

在这里插入图片描述
上图表示gdtr的结构,gdtr的结构和GdtPtr的结构一样

5. 打开A20地址线

如果A20 Gate被打开,则当程序员给出100000H-10FFEFH之间的地址的时候,系统将真正访问这块内存区域
如果A20 Gate被禁止,则当程序员给出100000H-10FFEFH之间的地址的时候,系统仍然使用8086/8088的方式即取模方式(8086仿真)。绝大多数IBM PC兼容机默认的A20 Gate是被禁止的。现在许多新型PC上存在直接通过BIOS功能调用来控制A20 Gate的功能
在8086中,试图访问超过1MB以上的内存会发生回卷寻址,即从0000H开始

6. CR0寄存器的结构

在这里插入图片描述
上图表示CR0的结构
控制寄存器(CR0~CR3)用于控制和确定处理器的操作模式以及当前执行任务的特性,该寄存器也被称为页目录基地址寄存器PDBR

  1. CR0中含有控制处理器操作模式和状态的系统控制标志
    (1)CR0的第0位是保护允许位PE(Protedted Enable),用于启动保护模式,如果PE置1,则保护模式启动,如果PE=0,则在实模式下运行
    (2)CR0的第1位是监控协处理位MP(Moniter coprocessor),它与第3位一起决定:当TS=1时操作码WAIT是否产生一个“协处理器不能使用”的出错信号。第3位是任务转换位(Task Switch),当一个任务转换完成之后,自动将它置1。随着TS=1,就不能使用协处理器
    (3)CR0的第2位是模拟协处理器位 EM (Emulate coprocessor),如果EM=1,则不能使用协处理器,如果EM=0,则允许使用协处理器
    (4)CR0的第4位是微处理器的扩展类型位 ET(Processor Extension Type),其内保存着处理器扩展类型的信息,如果
    ET=0,则标识系统使用的是287协处理器,如果 ET=1,则表示系统使用的是387浮点协处理器
    (5)CR0的第31位是分页允许位(Paging Enable),它表示芯片上的分页部件是否允许工作
    (6)CR0的第16位是写保护未即WP位(486系列之后),只要将这一位置0就可以禁用写保护,置1则可将其恢复
  2. CR1保留不用
  3. CR2含有导致页错误的线性地址,保存最后一次出现页故障的全32位线性地址
  4. CR3中含有页目录表物理内存基地址,页目录表总是放在以4K字节为单位的存储器边界上,因此,它的地址的低12位总为0,不起作用,即使写上内容,也不会被理会

7. “$”

$表示当前地址,$-LABLE_GDT即GDT的地址长度
$在不同的语法中表示不同的意思
在AT&T语法中,使用$表示立即数
在INTEL语法中,使用$表示当前地址

你可能感兴趣的:(学习)