
本科生实验报告
实验课程: 操作系统原理实验
任课教师: 刘宁
实验题目:保护模式
专业名称: 计算机科学与技术
学生姓名:罗弘杰
学生学号: 22336173
实验地点: 实验中心D503
实验时间: 2024/4/12
实验资料:lab3 · NelsonCheung/SYSU-2023-Spring-Operating-System - 码云 - 开源中国 (gitee.com)
实验要求
学习从LBA和C/H/S的磁盘寻址方式,以及使用IO和中断实现的磁盘读取
学习进入保护模式的方式
学习gdb调试源码级程序
实验任务
复现example1, 说说怎么做并截图
在example1的基础上将LBA28的寻址方式改为CHS,同时给出逻辑扇区号向CHS的转换公式
利用gdb进行实验资料例子2的debug分析
实验过程
任务一
在实验资料的基础上编写Mbr和bootloader.asm,mbr负责加载bootloader,bootloader的任务是打印字符。
我的理解是:操作系统为了性能需求,启动时只会自动加载512B的MBR, 其余磁盘操作由MBR内容管理和控制
过程:

编写bootloader.asm和mbr.asm两个文件,然后编译为可执行文件
写入到虚拟机的磁盘上,mbr.asm写的位置时0号扇区,数量为1,bootloader为扇区1,数量为5.
然后使用QEMU命令启动虚拟机

任务二

先复习磁盘工作原理:
磁盘空间 \(Space = c *s *h *512(扇区容量)\) 先确定柱面,在柱面上确定磁头,在磁头确定的磁道上找到扇区
编号差异:LBA规则的扇区编号从0开始,但是C/H/S的编号从1开始,从CHS到LBA转换要-1.
CHS->LBA \(LBA = (c*HPC+h)*SPT+s-1\) HPC是每个柱面的磁头数目,SPT是每个磁道上的扇区总数
由此可推出反变换公式:
0
1
2
C = LBA //(HPC * SPT) #注意是整除
H = [(LBA +1)//每磁道扇区总数SPT]mod HPC
S = (LBA +1)% 每磁道扇区总数SPT
在例一中, 读取的LBA从1-5,HPC为18, SPT为63,所以柱面是0,磁道也是0, S从1递增到6;并根据中断读取的参数要求重新编写mbr_chs.asm,编译以后在qemu上运行
mov ax, 2 ; 物理扇区第2位
load_bootloader:
call asm_read_hard_disk ; 读取硬盘
inc ax
cmp ax, 5
jle load_bootloader
jmp 0x0000:0x7e00 ; 跳转到bootloader
jmp $ ; 死循环
asm_read_hard_disk:
; 从硬盘读取一个逻辑扇区
; 参数列表
; ax=起始扇区号
; 返回值
; bx=bx+512
; ax=ax+1
mov dl, 80h
mov dh, 0; 磁头号
mov ch, 0; 柱面号
mov cl, al; 扇区号
mov ah, 2; 功能号
mov al, 1; 扇区数
int 13h; 调用int 13h中断
add bx, 512; bx=bx+512, 读取下一个扇区
ret
在这里逻辑扇区和物理扇区编号的差异是要注意的细节(逻辑从0开始,物理从1开始),然后按照任务1的方式,再编译并写入磁盘,启动qemu

实现相同的功能。
任务三
复现实验资料中进入保护模式的程序,然后使用gdb调试。
首先编写boot.inc的头文件
; 常量定义区
; _____________Loader_____________
; 加载器扇区数
LOADER_SECTOR_COUNT equ 5
; 加载器起始扇区
LOADER_START_SECTOR equ 1
; 加载器被加载地址
LOADER_START_ADDRESS equ 0x7e00
; _____________GDT_____________
; GDT起始位置
GDT_START_ADDRESS equ 0x8800 ; gdt表的起始位置
然后重新编写bootloader.asm,在输出字符后进入保护模式

%include "boot.inc"
[bits 16]
mov ax, 0xb800 ;显存段地址
mov gs, ax
mov ah, 0x03 ;青色
mov ecx, bootloader_tag_end - bootloader_tag
xor ebx, ebx
mov esi, bootloader_tag
output_bootloader_tag:
mov al, [esi]
mov word[gs:bx], ax
inc esi
add ebx,2
loop output_bootloader_tag
;空描述符
mov dword [GDT_START_ADDRESS+0x00],0x00
mov dword [GDT_START_ADDRESS+0x04],0x00
;创建描述符,这是一个数据段,对应0~4GB的线性地址空间
mov dword [GDT_START_ADDRESS+0x08],0x0000ffff ; 基地址为0,段界限为0xFFFF
mov dword [GDT_START_ADDRESS+0x0c],0x00cf9200 ; 粒度为4KB,存储器段描述符
;建立保护模式下的堆栈段描述符
mov dword [GDT_START_ADDRESS+0x10],0x00000000 ; 基地址为0x00000000,界限0x0
mov dword [GDT_START_ADDRESS+0x14],0x00409600 ; 粒度为1个字节
;建立保护模式下的显存描述符
mov dword [GDT_START_ADDRESS+0x18],0x80007fff ; 基地址为0x000B8000,界限0x07FF
mov dword [GDT_START_ADDRESS+0x1c],0x0040920b ; 粒度为字节
;创建保护模式下平坦模式代码段描述符
mov dword [GDT_START_ADDRESS+0x20],0x0000ffff ; 基地址为0,段界限为0xFFFF
mov dword [GDT_START_ADDRESS+0x24],0x00cf9800 ; 粒度为4kb,代码段描述符
;初始化描述符表寄存器GDTR
mov word [pgdt], 39 ;描述符表的界限
lgdt [pgdt]
; _____________Selector_____________
;平坦模式数据段选择子
DATA_SELECTOR equ 0x8
;平坦模式栈段选择子
STACK_SELECTOR equ 0x10
;平坦模式视频段选择子
VIDEO_SELECTOR equ 0x18
VIDEO_NUM equ 0x18
;平坦模式代码段选择子
CODE_SELECTOR equ 0x20
in al,0x92 ;南桥芯片内的端口
or al,0000_0010B
out 0x92,al ;打开A20
cli ;禁用中断
mov eax,cr0
or eax,1
mov cr0,eax ;设置PE位
jmp dword CODE_SELECTOR:protect_mode_begin
;16位的描述符选择子:32位偏移
;清流水线并串行化处理器
[bits 32]
protect_mode_begin:
mov eax, DATA_SELECTOR ;加载数据段(0..4GB)选择子
mov ds, eax
mov es, eax
mov eax, STACK_SELECTOR
mov ss, eax
mov eax, VIDEO_SELECTOR
mov gs, eax
mov ecx, protect_mode_tag_end - protect_mode_tag
mov ebx, 80 * 2
mov esi, protect_mode_tag
mov ah, 0x3
output_protect_mode_tag:
mov al, [esi]
mov word[gs:ebx], ax
add ebx, 2
inc esi
loop output_protect_mode_tag
jmp $ ; 死循环
pgdt dw 0 ; pgdt 将占据48位,前16位为界限,在这里是39,后32位是起始地址
dd GDT_START_ADDRESS
bootloader_tag db 'bootloader'
bootloader_tag_end:
protect_mode_tag db 'enter protect mode'
protect_mode_tag_end:
代码分析:
进入保护模式需要4个过程:
-
准备GDT,用lgdt指令加载GDTR信息。
-
打开第21根地址线。//扩大内存访问空间是保护模式的出现背景
-
开启cr0的保护模式标志位。
-
远跳转,进入保护模式。
怎么做:
-
GDTR是一个x86架构专用寄存器,是48位存储全局描述符表的寄存器,在这里我们先把GDT信息存储在内存中,然后使用lgdt指令加载到该寄存器,修改全局描述符表信息。
0 1
mov word [pgdt], 39 ;描述符表的界限 lgdt [pgdt]
-
南桥芯片0x92端口的第二位控制第二十条地址线的开关,将其置为1就能打开第二十条地址线;
使用与方法可以将其置为一
0 1 2
in al,0x92 ;南桥芯片内的端口 or al,0000_0010B ;将第二位置为1 out 0x92,al ;打开A20
-
cr0是专用寄存器,将其最低位(protection enable)置为1,就可以启用保护模式。
禁用中断保证当前代码执行
0 1 2 3
cli ;禁用中断 mov eax,cr0 or eax,1 mov cr0,eax ;设置PE位
-
jmp dword CODE_SELECTOR:protect_mode_begin用于执行跳转到指定代码段的指定地址,进入保护模式下的代码执行。0 1 2 3 4 5 6 7 8 9 10 11 12 13
jmp dword CODE_SELECTOR:protect_mode_begin ;16位的描述符选择子:32位偏移 ;清流水线并串行化处理器 [bits 32] protect_mode_begin: mov eax, DATA_SELECTOR ;加载数据段(0..4GB)选择子 mov ds, eax mov es, eax mov eax, STACK_SELECTOR mov ss, eax mov eax, VIDEO_SELECTOR mov gs, eax
-
编写MBR.asm
与之前的相似,更改了硬盘读取函数的传参方式
gdb调试:
调用qemu后,没有显示内容

断点1:准备GDT,用lgdt指令加载GDTR信息。
根据实验资料;
通过调试,先获取PGDT在内存的位置,然后查看里面的内容,确认是39


断点2:打开第21根地址线。
怎么打开:参照以下资料将0x92第二位置为1


断点3:开启cr0的保护模式标志位。

修改前是16,修改后是17,16和17相比只有最后一位不一样,17说明进入保护模式了

断点4:远跳转,进入保护模式。

此时,jmp指令将CODE_SELECTOR送入cs,将protect_mode_begin + LOADER_START_ADDRESS送入eip,进入保护模式。然后我们将选择子放入对应的段寄存器。
可以查看跳转到保护模式时各个寄存器的状态

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.